From af58ec8ee7ac8eab7df7d96a44b3ed6343a20a89 Mon Sep 17 00:00:00 2001 From: Carlos Baez Date: Thu, 24 Oct 2024 16:37:26 +0200 Subject: [PATCH] Squashed commit of the following: commit bd541c43303ad68fe100c8be339a3417058ad8c6 Author: Carlos Baez Date: Thu Oct 24 16:36:23 2024 +0200 update wit for version 1.36.1 --- .cargo/config.toml | 3 + .config/nextest.toml | 2 +- .github/actions/diffs/action.yml | 7 +- .github/actions/ts-e2e/action.yml | 14 +- .github/workflows/release-notes-generator.yml | 2 +- .github/workflows/split-cluster-bisect.yml | 53 + Cargo.lock | 1905 +++++---- Cargo.toml | 25 +- README.md | 2 +- apps/core/src/hooks/useFormatCoin.ts | 39 +- .../transaction/getBalanceChangeSummary.ts | 2 +- apps/wallet/src/background/accounts/index.ts | 4 +- .../dapp-interface/WalletStandardInterface.ts | 12 +- .../src/shared/analytics/ampli/index.ts | 187 +- .../src/shared/utils/from-exported-keypair.ts | 4 +- apps/wallet/src/shared/utils/index.ts | 6 +- apps/wallet/src/ui/app/QredoSigner.ts | 8 +- apps/wallet/src/ui/app/WalletSigner.ts | 8 +- .../src/ui/app/background-client/index.ts | 4 +- .../ui/app/components/address-input/index.tsx | 2 +- .../ui/app/components/buynlarge/HomePanel.tsx | 4 +- .../src/ui/app/components/coin-icon/index.tsx | 10 +- .../known-scam-overlay/useDappPreflight.ts | 4 +- .../app/helpers/filterAndSortTokenBalances.ts | 22 +- .../src/ui/app/hooks/useGetAllBalances.ts | 18 + .../src/ui/app/hooks/useSupportedCoins.ts | 13 + .../ui/app/hooks/useValidSwapTokensList.ts | 68 + apps/wallet/src/ui/app/index.tsx | 10 +- .../TransactionDetails/Command.tsx | 4 +- .../ui/app/pages/home/interstitial/index.tsx | 2 +- .../app/pages/home/tokens/TokensDetails.tsx | 49 +- .../home/transactions/QredoTransaction.tsx | 4 +- .../app/pages/home/usdc-promo/UsdcPromo.tsx | 55 + .../pages/home/usdc-promo/UsdcPromoBanner.tsx | 83 + .../app/pages/home/usdc-promo/useUsdcPromo.ts | 31 + .../src/ui/app/pages/swap/AssetData.tsx | 49 +- .../ui/app/pages/swap/CoinsSelectionPage.tsx | 78 + .../src/ui/app/pages/swap/FromAssets.tsx | 74 - .../src/ui/app/pages/swap/GasFeesSummary.tsx | 63 + .../src/ui/app/pages/swap/ToAssetSection.tsx | 167 - .../wallet/src/ui/app/pages/swap/ToAssets.tsx | 61 - apps/wallet/src/ui/app/pages/swap/index.tsx | 722 ++-- .../ui/app/pages/swap/useSwapTransaction.ts | 98 + apps/wallet/src/ui/app/pages/swap/utils.ts | 235 +- .../slices/transaction-requests/index.ts | 4 +- .../src/ui/app/shared/InputWithAction.tsx | 4 +- apps/wallet/src/ui/app/shared/text/index.tsx | 5 +- bridge/evm/contracts/BridgeCommittee.sol | 4 +- bridge/evm/contracts/BridgeConfig.sol | 33 +- bridge/evm/contracts/BridgeLimiter.sol | 2 +- bridge/evm/contracts/SuiBridge.sol | 4 +- .../contracts/interfaces/IBridgeCommittee.sol | 4 + .../contracts/interfaces/IBridgeConfig.sol | 23 + .../contracts/interfaces/IBridgeLimiter.sol | 4 + .../evm/contracts/interfaces/ISuiBridge.sol | 5 + .../contracts/utils/CommitteeUpgradeable.sol | 10 +- bridge/evm/deploy_configs/11155111.json | 6 +- bridge/evm/deploy_configs/31337.json | 7 +- bridge/evm/deploy_configs/31338.json | 7 +- bridge/evm/deploy_configs/example.json | 11 - bridge/evm/deploy_configs/mainnet.json | 207 + bridge/evm/deploy_configs/sepolia.json | 6 +- bridge/evm/script/deploy_bridge.s.sol | 95 +- bridge/evm/test/BridgeBaseTest.t.sol | 16 +- bridge/evm/test/BridgeCommitteeTest.t.sol | 2 +- bridge/evm/test/BridgeConfigTest.t.sol | 4 +- bridge/evm/test/BridgeLimiterTest.t.sol | 6 +- .../evm/test/CommitteeUpgradeableTest.t.sol | 2 +- bridge/evm/test/SuiBridgeTest.t.sol | 8 +- bridge/runbook/validator_runbook.md | 187 +- consensus/config/src/parameters.rs | 6 +- .../parameters_test__parameters.snap | 1 + consensus/core/src/authority_node.rs | 46 +- consensus/core/src/authority_service.rs | 15 +- consensus/core/src/base_committer.rs | 26 +- consensus/core/src/block_manager.rs | 521 ++- consensus/core/src/block_verifier.rs | 147 +- consensus/core/src/broadcaster.rs | 2 +- consensus/core/src/commit.rs | 20 +- consensus/core/src/commit_consumer.rs | 42 +- consensus/core/src/commit_observer.rs | 50 +- consensus/core/src/commit_syncer.rs | 228 +- consensus/core/src/core.rs | 389 +- consensus/core/src/core_thread.rs | 65 +- consensus/core/src/dag_state.rs | 582 ++- consensus/core/src/error.rs | 46 +- consensus/core/src/leader_schedule.rs | 314 +- consensus/core/src/leader_scoring.rs | 91 +- consensus/core/src/leader_timeout.rs | 7 +- consensus/core/src/lib.rs | 9 +- consensus/core/src/linearizer.rs | 233 +- consensus/core/src/metrics.rs | 25 +- .../core/src/network/connection_monitor.rs | 73 +- consensus/core/src/network/metrics.rs | 210 +- consensus/core/src/network/mod.rs | 3 +- consensus/core/src/network/tonic_network.rs | 54 +- consensus/core/src/network/tonic_tls.rs | 39 +- consensus/core/src/round_prober.rs | 33 +- consensus/core/src/storage/rocksdb_store.rs | 7 +- consensus/core/src/subscriber.rs | 23 +- consensus/core/src/synchronizer.rs | 208 +- consensus/core/src/test_dag_builder.rs | 156 +- crates/mysten-common/src/lib.rs | 1 + crates/mysten-common/src/logging.rs | 47 + crates/mysten-common/src/metrics.rs | 4 +- crates/mysten-metrics/src/metered_channel.rs | 9 +- crates/mysten-metrics/src/monitored_mpsc.rs | 12 +- crates/shared-crypto/src/intent.rs | 3 +- .../src/analytics_processor.rs | 1 + .../src/handlers/checkpoint_handler.rs | 2 + .../src/handlers/df_handler.rs | 49 +- .../src/handlers/event_handler.rs | 2 + .../sui-analytics-indexer/src/handlers/mod.rs | 2 +- .../src/handlers/move_call_handler.rs | 2 + .../src/handlers/object_handler.rs | 2 + .../src/handlers/package_handler.rs | 2 + .../src/handlers/transaction_handler.rs | 2 + .../handlers/transaction_objects_handler.rs | 2 + .../src/handlers/wrapped_object_handler.rs | 2 + crates/sui-analytics-indexer/src/lib.rs | 4 +- crates/sui-authority-aggregation/src/lib.rs | 101 +- crates/sui-benchmark/tests/simtest.rs | 21 +- crates/sui-bridge-cli/src/main.rs | 3 +- crates/sui-bridge-indexer/Cargo.toml | 3 + crates/sui-bridge-indexer/config.yaml | 4 - .../src/eth_bridge_indexer.rs | 586 ++- crates/sui-bridge-indexer/src/lib.rs | 39 + crates/sui-bridge-indexer/src/main.rs | 88 +- crates/sui-bridge-indexer/src/metrics.rs | 23 +- .../down.sql | 1 + .../up.sql | 11 + crates/sui-bridge-indexer/src/models.rs | 15 +- .../src/postgres_manager.rs | 15 +- crates/sui-bridge-indexer/src/schema.rs | 14 + crates/sui-bridge-indexer/src/storage.rs | 22 +- .../src/sui_bridge_indexer.rs | 95 +- .../sui-bridge-indexer/src/sui_datasource.rs | 31 +- .../src/sui_transaction_queries.rs | 7 +- crates/sui-bridge-watchdog/Cargo.toml | 18 + .../src/eth_bridge_status.rs | 58 + .../src/eth_vault_balance.rs | 75 + crates/sui-bridge-watchdog/src/lib.rs | 62 + crates/sui-bridge-watchdog/src/metrics.rs | 41 + .../src/sui_bridge_status.rs | 48 + crates/sui-bridge/abi/bridge_committee.json | 140 +- .../abi/bridge_committee_upgradeable.json | 276 +- crates/sui-bridge/abi/bridge_config.json | 155 +- crates/sui-bridge/abi/bridge_limiter.json | 160 +- crates/sui-bridge/abi/bridge_vault.json | 51 +- crates/sui-bridge/abi/sui_bridge.json | 170 +- crates/sui-bridge/src/abi.rs | 9 + crates/sui-bridge/src/action_executor.rs | 9 +- .../src/client/bridge_authority_aggregator.rs | 203 +- crates/sui-bridge/src/client/bridge_client.rs | 12 +- crates/sui-bridge/src/config.rs | 18 +- crates/sui-bridge/src/crypto.rs | 4 + crates/sui-bridge/src/e2e_tests/basic.rs | 107 +- crates/sui-bridge/src/e2e_tests/test_utils.rs | 420 +- crates/sui-bridge/src/eth_syncer.rs | 10 +- crates/sui-bridge/src/metrics.rs | 51 +- crates/sui-bridge/src/monitor.rs | 35 + crates/sui-bridge/src/node.rs | 49 +- crates/sui-bridge/src/server/mock_handler.rs | 48 +- crates/sui-bridge/src/sui_client.rs | 26 +- crates/sui-bridge/src/sui_mock_client.rs | 14 +- crates/sui-bridge/src/sui_syncer.rs | 65 +- .../sui-bridge/src/sui_transaction_builder.rs | 69 +- crates/sui-bridge/src/test_utils.rs | 1 + crates/sui-bridge/src/types.rs | 26 + crates/sui-bridge/src/utils.rs | 34 +- crates/sui-cluster-test/src/cluster.rs | 34 +- crates/sui-config/Cargo.toml | 1 + crates/sui-config/data/fullnode-template.yaml | 17 + crates/sui-config/src/node.rs | 32 +- .../sui-config/src/object_storage_config.rs | 20 +- crates/sui-config/src/p2p.rs | 10 + crates/sui-core/src/authority.rs | 382 +- .../authority/authority_per_epoch_store.rs | 332 +- .../sui-core/src/authority/authority_store.rs | 2 +- .../src/authority/authority_test_utils.rs | 10 +- .../shared_object_congestion_tracker.rs | 155 +- .../src/authority/test_authority_builder.rs | 9 +- crates/sui-core/src/authority_server.rs | 322 +- .../checkpoints/checkpoint_executor/mod.rs | 2 + crates/sui-core/src/checkpoints/mod.rs | 3 +- crates/sui-core/src/consensus_adapter.rs | 101 +- crates/sui-core/src/consensus_handler.rs | 504 ++- .../consensus_manager/mysticeti_manager.rs | 69 +- .../consensus_types/consensus_output_api.rs | 187 +- crates/sui-core/src/consensus_types/mod.rs | 4 - crates/sui-core/src/consensus_validator.rs | 8 +- crates/sui-core/src/execution_cache.rs | 20 +- .../src/execution_cache/object_locks.rs | 58 +- .../src/execution_cache/passthrough_cache.rs | 8 + .../unit_tests/writeback_cache_tests.rs | 75 +- .../src/execution_cache/writeback_cache.rs | 19 +- crates/sui-core/src/execution_driver.rs | 7 +- crates/sui-core/src/generate_format.rs | 55 + .../src/jsonrpc_index.rs} | 457 ++- crates/sui-core/src/lib.rs | 2 + .../sui-core/src/par_index_live_object_set.rs | 104 + crates/sui-core/src/rest_index.rs | 274 +- crates/sui-core/src/scoring_decision.rs | 6 +- crates/sui-core/src/test_authority_clients.rs | 2 +- crates/sui-core/src/test_utils.rs | 6 +- crates/sui-core/src/traffic_controller/mod.rs | 170 +- .../src/traffic_controller/nodefw_client.rs | 4 +- crates/sui-core/src/transaction_manager.rs | 2 +- .../sui-core/src/transaction_orchestrator.rs | 67 +- .../unit_tests/authority_aggregator_tests.rs | 50 +- .../src/unit_tests/authority_tests.rs | 153 +- .../unit_tests/congestion_control_tests.rs | 1 + .../src/unit_tests/consensus_tests.rs | 136 +- .../move_package_management_tests.rs | 2 +- .../unit_tests/move_package_publish_tests.rs | 16 +- .../src/unit_tests/mysticeti_manager_tests.rs | 17 + .../src/unit_tests/transaction_deny_tests.rs | 19 +- crates/sui-core/src/verify_indexes.rs | 64 +- crates/sui-core/tests/staged/sui.yaml | 74 +- crates/sui-data-ingestion-core/Cargo.toml | 1 + .../sui-data-ingestion-core/src/executor.rs | 8 + crates/sui-data-ingestion-core/src/lib.rs | 24 +- crates/sui-data-ingestion-core/src/reducer.rs | 59 + crates/sui-data-ingestion-core/src/tests.rs | 1 + .../src/worker_pool.rs | 65 +- crates/sui-data-ingestion/Cargo.toml | 1 + .../src/bin/archival_ingestion.rs | 38 +- crates/sui-data-ingestion/src/lib.rs | 3 +- crates/sui-data-ingestion/src/main.rs | 13 +- .../src/workers/archival.rs | 184 +- crates/sui-data-ingestion/src/workers/blob.rs | 1 + .../src/workers/kv_store.rs | 14 +- crates/sui-data-ingestion/src/workers/mod.rs | 2 +- crates/sui-deepbook-indexer/Cargo.toml | 6 +- crates/sui-deepbook-indexer/config.yaml | 4 - crates/sui-deepbook-indexer/src/config.rs | 2 +- crates/sui-deepbook-indexer/src/error.rs | 7 + crates/sui-deepbook-indexer/src/events.rs | 19 + crates/sui-deepbook-indexer/src/lib.rs | 2 + crates/sui-deepbook-indexer/src/main.rs | 6 + crates/sui-deepbook-indexer/src/metrics.rs | 8 + .../up.sql | 22 +- crates/sui-deepbook-indexer/src/models.rs | 33 +- crates/sui-deepbook-indexer/src/schema.rs | 22 + crates/sui-deepbook-indexer/src/server.rs | 144 + .../src/sui_datasource.rs | 6 + .../src/sui_deepbook_indexer.rs | 158 +- crates/sui-deepbook-indexer/src/types.rs | 34 +- crates/sui-e2e-tests/Cargo.toml | 1 + crates/sui-e2e-tests/tests/bridge_tests.rs | 105 - crates/sui-e2e-tests/tests/rest.rs | 428 ++ crates/sui-e2e-tests/tests/simulator_tests.rs | 10 - .../tests/traffic_control_tests.rs | 93 +- crates/sui-faucet/Cargo.toml | 2 + crates/sui-faucet/src/faucet/simple_faucet.rs | 21 +- crates/sui-faucet/src/metrics.rs | 96 +- crates/sui-faucet/src/metrics_layer.rs | 115 +- crates/sui-faucet/src/server.rs | 2 +- ...000000000000000000000000000000000000000001 | Bin 0 -> 14381 bytes ...000000000000000000000000000000000000000002 | Bin 0 -> 66985 bytes ...000000000000000000000000000000000000000003 | Bin 0 -> 44338 bytes ...00000000000000000000000000000000000000000b | Bin 0 -> 19826 bytes ...00000000000000000000000000000000000000dee9 | Bin 0 -> 33346 bytes ...000000000000000000000000000000000000000001 | Bin 0 -> 15602 bytes ...000000000000000000000000000000000000000002 | Bin 0 -> 66985 bytes ...000000000000000000000000000000000000000003 | Bin 0 -> 44338 bytes ...00000000000000000000000000000000000000000b | Bin 0 -> 19826 bytes ...00000000000000000000000000000000000000dee9 | Bin 0 -> 33346 bytes crates/sui-framework-snapshot/manifest.json | 20 + .../sui-framework/docs/move-stdlib/ascii.md | 2 +- crates/sui-framework/docs/move-stdlib/bcs.md | 2 +- .../sui-framework/docs/move-stdlib/option.md | 7 +- .../sui-framework/docs/move-stdlib/string.md | 11 +- .../docs/move-stdlib/type_name.md | 32 +- .../sui-framework/docs/move-stdlib/vector.md | 5 +- .../docs/sui-framework/authenticator_state.md | 22 +- .../docs/sui-framework/balance.md | 10 +- .../docs/sui-framework/bls12381.md | 22 +- .../docs/sui-framework/borrow.md | 13 +- .../sui-framework/docs/sui-framework/clock.md | 6 +- .../sui-framework/docs/sui-framework/coin.md | 107 +- .../docs/sui-framework/config.md | 2 +- .../docs/sui-framework/deny_list.md | 35 +- .../docs/sui-framework/display.md | 25 +- .../docs/sui-framework/dynamic_field.md | 27 +- .../sui-framework/dynamic_object_field.md | 15 +- .../docs/sui-framework/ecdsa_k1.md | 13 +- .../docs/sui-framework/ecdsa_r1.md | 13 +- .../sui-framework/docs/sui-framework/ecvrf.md | 7 +- .../docs/sui-framework/ed25519.md | 6 +- .../docs/sui-framework/groth16.md | 33 +- .../docs/sui-framework/group_ops.md | 26 +- .../sui-framework/docs/sui-framework/hash.md | 4 +- .../sui-framework/docs/sui-framework/hex.md | 6 +- .../sui-framework/docs/sui-framework/kiosk.md | 96 +- .../docs/sui-framework/kiosk_extension.md | 36 +- .../sui-framework/docs/sui-framework/math.md | 2 +- .../docs/sui-framework/object.md | 10 +- .../docs/sui-framework/package.md | 11 +- .../sui-framework/docs/sui-framework/pay.md | 17 +- .../docs/sui-framework/priority_queue.md | 6 +- .../docs/sui-framework/random.md | 19 +- .../sui-framework/docs/sui-framework/sui.md | 2 +- .../docs/sui-framework/table_vec.md | 6 +- .../sui-framework/docs/sui-framework/token.md | 107 +- .../docs/sui-framework/transfer.md | 10 +- .../docs/sui-framework/transfer_policy.md | 44 +- .../docs/sui-framework/tx_context.md | 2 +- .../sui-framework/docs/sui-framework/url.md | 2 +- .../sui-framework/docs/sui-framework/vdf.md | 14 +- .../docs/sui-framework/vec_map.md | 27 +- .../docs/sui-framework/versioned.md | 9 +- .../docs/sui-framework/zklogin_verified_id.md | 7 +- .../sui-framework/zklogin_verified_issuer.md | 16 +- .../docs/sui-system/staking_pool.md | 434 +++ .../docs/sui-system/sui_system.md | 62 + .../docs/sui-system/sui_system_state_inner.md | 58 + .../docs/sui-system/validator.md | 172 + .../docs/sui-system/validator_set.md | 81 + .../packages/move-stdlib/sources/address.move | 12 +- .../packages/move-stdlib/sources/ascii.move | 318 +- .../packages/move-stdlib/sources/bcs.move | 8 +- .../move-stdlib/sources/bit_vector.move | 188 +- .../packages/move-stdlib/sources/debug.move | 8 +- .../move-stdlib/sources/fixed_point32.move | 187 +- .../packages/move-stdlib/sources/hash.move | 8 +- .../packages/move-stdlib/sources/macros.move | 244 +- .../packages/move-stdlib/sources/option.move | 426 +- .../packages/move-stdlib/sources/string.move | 231 +- .../move-stdlib/sources/type_name.move | 210 +- .../packages/move-stdlib/sources/u128.move | 222 +- .../packages/move-stdlib/sources/u16.move | 192 +- .../packages/move-stdlib/sources/u256.move | 174 +- .../packages/move-stdlib/sources/u32.move | 202 +- .../packages/move-stdlib/sources/u64.move | 212 +- .../packages/move-stdlib/sources/u8.move | 156 +- .../move-stdlib/sources/unit_test.move | 45 +- .../packages/move-stdlib/sources/uq32_32.move | 160 + .../packages/move-stdlib/sources/vector.move | 661 ++-- .../move-stdlib/tests/ascii_tests.move | 424 +- .../packages/move-stdlib/tests/bcs_tests.move | 204 +- .../move-stdlib/tests/bit_vector_tests.move | 374 +- .../move-stdlib/tests/fixedpoint32_tests.move | 216 +- .../move-stdlib/tests/hash_tests.move | 28 +- .../move-stdlib/tests/integer_tests.move | 537 ++- .../move-stdlib/tests/option_tests.move | 546 +-- .../move-stdlib/tests/string_tests.move | 140 +- .../move-stdlib/tests/type_name_tests.move | 220 +- .../move-stdlib/tests/u128_tests.move | 214 +- .../packages/move-stdlib/tests/u16_tests.move | 184 +- .../move-stdlib/tests/u256_tests.move | 212 +- .../packages/move-stdlib/tests/u32_tests.move | 194 +- .../packages/move-stdlib/tests/u64_tests.move | 203 +- .../packages/move-stdlib/tests/u8_tests.move | 145 +- .../move-stdlib/tests/uq32_32_tests.move | 257 ++ .../move-stdlib/tests/vector_tests.move | 1400 +++---- .../sui-framework/sources/address.move | 162 +- .../sources/authenticator_state.move | 666 ++-- .../packages/sui-framework/sources/bag.move | 150 +- .../sui-framework/sources/balance.move | 290 +- .../packages/sui-framework/sources/bcs.move | 466 +-- .../sui-framework/sources/borrow.move | 170 +- .../packages/sui-framework/sources/clock.move | 143 +- .../packages/sui-framework/sources/coin.move | 1080 +++--- .../sui-framework/sources/config.move | 476 ++- .../sources/crypto/bls12381.move | 504 +-- .../sources/crypto/ecdsa_k1.move | 207 +- .../sources/crypto/ecdsa_r1.move | 76 +- .../sui-framework/sources/crypto/ecvrf.move | 32 +- .../sui-framework/sources/crypto/ed25519.move | 22 +- .../sui-framework/sources/crypto/groth16.move | 214 +- .../sources/crypto/group_ops.move | 237 +- .../sui-framework/sources/crypto/hash.move | 16 +- .../sui-framework/sources/crypto/hmac.move | 12 +- .../sources/crypto/poseidon.move | 62 +- .../sui-framework/sources/crypto/vdf.move | 63 +- .../sources/crypto/zklogin_verified_id.move | 159 +- .../crypto/zklogin_verified_issuer.move | 122 +- .../sui-framework/sources/deny_list.move | 782 ++-- .../sui-framework/sources/display.move | 340 +- .../sui-framework/sources/dynamic_field.move | 290 +- .../sources/dynamic_object_field.move | 370 +- .../packages/sui-framework/sources/event.move | 34 +- .../packages/sui-framework/sources/hex.move | 152 +- .../sui-framework/sources/kiosk/kiosk.move | 986 +++-- .../sources/kiosk/kiosk_extension.move | 414 +- .../sources/kiosk/transfer_policy.move | 552 ++- .../sui-framework/sources/linked_table.move | 348 +- .../packages/sui-framework/sources/math.move | 71 +- .../sui-framework/sources/object.move | 355 +- .../sui-framework/sources/object_bag.move | 160 +- .../sui-framework/sources/object_table.move | 152 +- .../sui-framework/sources/package.move | 620 ++- .../packages/sui-framework/sources/pay.move | 138 +- .../sui-framework/sources/priority_queue.move | 318 +- .../sui-framework/sources/prover.move | 3 +- .../sui-framework/sources/random.move | 638 +-- .../packages/sui-framework/sources/sui.move | 100 +- .../packages/sui-framework/sources/table.move | 140 +- .../sui-framework/sources/table_vec.move | 215 +- .../sources/test/test_scenario.move | 691 ++-- .../sources/test/test_utils.move | 38 +- .../packages/sui-framework/sources/token.move | 1291 +++--- .../sui-framework/sources/transfer.move | 275 +- .../sui-framework/sources/tx_context.move | 275 +- .../packages/sui-framework/sources/types.move | 12 +- .../packages/sui-framework/sources/url.move | 50 +- .../sui-framework/sources/vec_map.move | 364 +- .../sui-framework/sources/vec_set.move | 201 +- .../sui-framework/sources/versioned.move | 143 +- .../sui-framework/tests/balance_tests.move | 26 + .../sui-framework/tests/display_tests.move | 39 + .../sui-framework/tests/hex_tests.move | 62 + .../sui-system/sources/staking_pool.move | 286 ++ .../sui-system/sources/sui_system.move | 22 +- .../sources/sui_system_state_inner.move | 18 +- .../sui-system/sources/validator.move | 59 +- .../sui-system/sources/validator_set.move | 41 +- .../sui-system/tests/staking_pool.move | 317 ++ .../sui-system/tests/sui_system_tests.move | 55 + .../packages_compiled/move-stdlib | Bin 14348 -> 15569 bytes .../packages_compiled/sui-system | Bin 41971 -> 44241 bytes crates/sui-framework/published_api.txt | 108 + crates/sui-framework/src/lib.rs | 19 +- .../tests/stable/call/dynamic_fields.exp | 326 +- .../tests/stable/call/dynamic_fields.move | 97 +- .../tests/stable/consistency/balances.move | 24 +- .../checkpoints/transaction_blocks.move | 2 +- .../epochs/transaction_blocks.move | 4 +- .../stable/consistency/object_at_version.exp | 30 +- .../consistency/tx_address_objects.move | 8 +- .../tests/stable/epoch/epoch_start_to_end.exp | 231 ++ .../stable/epoch/epoch_start_to_end.move | 182 + .../tests/stable/epoch/pagination.exp | 194 + .../tests/stable/epoch/pagination.move | 120 + .../tests/stable/epoch/system_state.exp | 16 +- .../event_connection/combo_filter_error.exp | 2 +- .../event_connection/combo_filter_error.move | 12 +- .../event_connection/event_connection.exp | 208 +- .../event_connection/event_connection.move | 100 +- .../event_connection/nested_emit_event.exp | 58 +- .../event_connection/nested_emit_event.move | 60 +- .../stable/event_connection/no_filter.exp | 244 +- .../stable/event_connection/no_filter.move | 6 +- .../stable/event_connection/pagination.exp | 152 +- .../stable/event_connection/pagination.move | 49 +- .../stable/event_connection/tx_digest.exp | 130 +- .../stable/event_connection/tx_digest.move | 55 +- .../stable/event_connection/type_filter.exp | 144 +- .../stable/event_connection/type_filter.move | 60 +- .../event_connection/type_param_filter.exp | 104 +- .../event_connection/type_param_filter.move | 30 +- .../tests/stable/events/sending_module.exp | 45 + .../tests/stable/events/sending_module.move | 44 + .../stable/objects/full_objects_history.exp | 6 +- .../stable/objects/full_objects_history.move | 2 +- .../tests/stable/objects/wrap_unwrap.exp | 107 + .../tests/stable/objects/wrap_unwrap.move | 90 + .../tests/stable/packages/bcs.exp | 23 + .../tests/stable/packages/bcs.move | 20 + .../transaction_block_effects/events.exp | 244 +- .../transaction_block_effects/events.move | 48 +- .../transactions/filters/affected_address.exp | 749 ++++ .../filters/affected_address.move | 92 + .../stable/transactions/filters/kind.move | 10 +- .../stable/transactions/filters/sent.exp | 206 +- .../stable/transactions/filters/sent.move | 41 - .../transactions/filters/transaction_ids.move | 6 +- .../stable/transactions/programmable.move | 2 +- .../transactions/scan_limit/alternating.move | 14 +- .../transactions/scan_limit/both_cursors.move | 6 +- .../transactions/scan_limit/equal/first.move | 16 +- .../transactions/scan_limit/equal/last.move | 14 +- .../scan_limit/ge_page/first.move | 14 +- .../transactions/scan_limit/ge_page/last.move | 14 +- .../scan_limit/invalid_limits.move | 8 +- .../scan_limit/le_page/first.move | 12 +- .../transactions/scan_limit/le_page/last.move | 12 +- .../transactions/scan_limit/require.move | 20 +- .../transactions/filters/affected_object.exp | 390 ++ .../transactions/filters/affected_object.move | 150 + .../with_tx_sent_addr_filter.graphql | 4 +- ...r.graphql => affected_addr_filter.graphql} | 2 +- ... => input_object_sent_addr_filter.graphql} | 2 +- ...ilter.graphql => sent_addr_filter.graphql} | 2 +- crates/sui-graphql-rpc/schema.graphql | 115 +- crates/sui-graphql-rpc/src/commands.rs | 33 +- crates/sui-graphql-rpc/src/config.rs | 59 +- crates/sui-graphql-rpc/src/consistency.rs | 8 +- crates/sui-graphql-rpc/src/main.rs | 21 +- crates/sui-graphql-rpc/src/server/builder.rs | 33 +- .../sui-graphql-rpc/src/test_infra/cluster.rs | 93 +- crates/sui-graphql-rpc/src/types/address.rs | 21 +- crates/sui-graphql-rpc/src/types/coin.rs | 4 +- .../src/types/coin_metadata.rs | 4 +- .../src/types/dynamic_field.rs | 149 +- crates/sui-graphql-rpc/src/types/epoch.rs | 104 +- crates/sui-graphql-rpc/src/types/event/mod.rs | 30 +- .../sui-graphql-rpc/src/types/move_object.rs | 4 +- .../sui-graphql-rpc/src/types/move_package.rs | 153 +- .../src/types/move_registry/named_type.rs | 11 +- crates/sui-graphql-rpc/src/types/object.rs | 148 +- crates/sui-graphql-rpc/src/types/query.rs | 24 +- crates/sui-graphql-rpc/src/types/stake.rs | 4 +- .../src/types/suins_registration.rs | 4 +- .../src/types/transaction_block/filter.rs | 47 +- .../src/types/transaction_block/mod.rs | 153 +- .../src/types/transaction_block/tx_lookups.rs | 48 +- crates/sui-graphql-rpc/staging.graphql | 116 +- crates/sui-graphql-rpc/tests/e2e_tests.rs | 12 +- .../tests/move_registry_e2e.rs | 17 +- .../snapshot_tests__schema.graphql.snap | 115 +- .../snapshot_tests__staging.graphql.snap | 116 +- .../src/indexer_builder.rs | 42 +- .../tests/indexer_test_utils.rs | 6 + .../tests/indexer_tests.rs | 32 +- crates/sui-indexer/Cargo.toml | 8 +- crates/sui-indexer/README.md | 15 +- .../pg/2023-10-06-204335_tx_indices/up.sql | 3 - .../down.sql | 15 + .../2024-09-10-195655_drop-df-columns/up.sql | 15 + .../pg/2024-09-12-213234_watermarks/down.sql | 1 + .../pg/2024-09-12-213234_watermarks/up.sql | 34 + .../down.sql | 1 + .../up.sql | 1 + .../down.sql | 1 + .../2024-09-19-011238_raw_checkpoints/up.sql | 6 + .../down.sql | 1 + .../up.sql | 1 + .../down.sql | 1 + .../metadata.toml | 1 + .../up.sql | 3 + .../down.sql | 1 + .../metadata.toml | 1 + .../up.sql | 3 + .../down.sql | 1 + .../metadata.toml | 1 + .../up.sql | 3 + .../down.sql | 1 + .../metadata.toml | 1 + .../up.sql | 3 + .../down.sql | 1 + .../metadata.toml | 1 + .../up.sql | 3 + .../down.sql | 1 + .../metadata.toml | 1 + .../up.sql | 3 + .../down.sql | 1 + .../metadata.toml | 1 + .../up.sql | 3 + .../down.sql | 1 + .../metadata.toml | 1 + .../up.sql | 3 + .../down.sql | 1 + .../metadata.toml | 1 + .../up.sql | 3 + .../down.sql | 1 + .../metadata.toml | 1 + .../up.sql | 3 + .../down.sql | 1 + .../metadata.toml | 1 + .../up.sql | 3 + .../down.sql | 1 + .../metadata.toml | 1 + .../up.sql | 3 + .../down.sql | 1 + .../metadata.toml | 1 + .../up.sql | 3 + .../down.sql | 1 + .../metadata.toml | 1 + .../up.sql | 3 + .../down.sql | 1 + .../metadata.toml | 1 + .../up.sql | 3 + .../down.sql | 1 + .../metadata.toml | 1 + .../up.sql | 3 + .../down.sql | 1 + .../2024-09-30-153705_add_event_sender/up.sql | 1 + .../down.sql | 7 + .../up.sql | 18 + .../down.sql | 1 + .../up.sql | 1 + crates/sui-indexer/src/apis/mod.rs | 2 +- crates/sui-indexer/src/apis/read_api.rs | 2 +- .../ingestion_backfill_task.rs | 98 + .../ingestion_backfills/mod.rs | 17 + .../ingestion_backfills/raw_checkpoints.rs | 34 + .../tx_affected_objects.rs | 48 + .../src/backfill/backfill_instances/mod.rs | 47 + .../backfill_instances/sql_backfill.rs | 36 + .../sql_backfills/event_sender.sh | 6 + .../sql_backfills/full_objects_history.sh | 6 + .../sql_backfills/tx_affected_addresses.sh | 7 + .../system_state_summary_json.rs | 56 + .../src/backfill/backfill_runner.rs | 94 + .../sui-indexer/src/backfill/backfill_task.rs | 12 + crates/sui-indexer/src/backfill/mod.rs | 34 + crates/sui-indexer/src/config.rs | 298 +- crates/sui-indexer/src/db.rs | 115 +- crates/sui-indexer/src/errors.rs | 3 + .../src/handlers/checkpoint_handler.rs | 310 +- crates/sui-indexer/src/handlers/committer.rs | 57 +- crates/sui-indexer/src/handlers/mod.rs | 272 +- .../src/handlers/objects_snapshot_handler.rs | 127 + .../handlers/objects_snapshot_processor.rs | 235 -- crates/sui-indexer/src/handlers/pruner.rs | 263 +- .../sui-indexer/src/handlers/tx_processor.rs | 109 +- crates/sui-indexer/src/indexer.rs | 43 +- crates/sui-indexer/src/indexer_reader.rs | 395 +- crates/sui-indexer/src/lib.rs | 4 +- crates/sui-indexer/src/main.rs | 51 +- crates/sui-indexer/src/metrics.rs | 22 + crates/sui-indexer/src/models/epoch.rs | 171 +- crates/sui-indexer/src/models/events.rs | 25 +- crates/sui-indexer/src/models/mod.rs | 2 + crates/sui-indexer/src/models/objects.rs | 235 +- .../sui-indexer/src/models/raw_checkpoints.rs | 26 + crates/sui-indexer/src/models/tx_indices.rs | 40 +- crates/sui-indexer/src/models/watermarks.rs | 76 + crates/sui-indexer/src/restorer/archives.rs | 2 - .../src/restorer/formal_snapshot.rs | 11 +- crates/sui-indexer/src/schema.rs | 37 +- crates/sui-indexer/src/store/indexer_store.rs | 38 +- crates/sui-indexer/src/store/mod.rs | 39 + .../sui-indexer/src/store/package_resolver.rs | 46 +- .../sui-indexer/src/store/pg_indexer_store.rs | 1047 +++-- .../src/store/pg_partition_manager.rs | 12 +- crates/sui-indexer/src/test_utils.rs | 247 +- crates/sui-indexer/src/types.rs | 214 +- crates/sui-indexer/tests/ingestion_tests.rs | 230 +- crates/sui-indexer/tests/read_api_tests.rs | 50 + crates/sui-json-rpc-api/src/deepbook.rs | 14 + .../tests/rpc_server_tests.rs | 43 +- crates/sui-json-rpc-types/src/sui_event.rs | 54 +- .../sui-json-rpc-types/src/sui_transaction.rs | 14 + crates/sui-json-rpc/Cargo.toml | 1 + crates/sui-json-rpc/src/authority_state.rs | 12 +- crates/sui-json-rpc/src/axum_router.rs | 16 +- crates/sui-json-rpc/src/balance_changes.rs | 28 + crates/sui-json-rpc/src/coin_api.rs | 127 +- crates/sui-json-rpc/src/error.rs | 13 +- crates/sui-json-rpc/src/lib.rs | 12 +- crates/sui-json-rpc/src/object_changes.rs | 4 + crates/sui-json-rpc/src/read_api.rs | 102 +- .../src/transaction_execution_api.rs | 14 +- crates/sui-move-build/src/lib.rs | 10 +- crates/sui-move/src/new.rs | 27 +- crates/sui-network/Cargo.toml | 2 + crates/sui-network/build.rs | 18 + crates/sui-network/src/discovery/builder.rs | 14 +- crates/sui-network/src/discovery/mod.rs | 232 +- crates/sui-network/src/discovery/server.rs | 78 +- crates/sui-network/src/discovery/tests.rs | 134 +- crates/sui-network/src/state_sync/mod.rs | 15 +- crates/sui-network/src/utils.rs | 26 +- crates/sui-node/Cargo.toml | 1 + crates/sui-node/src/lib.rs | 133 +- crates/sui-node/src/metrics.rs | 16 +- crates/sui-open-rpc/spec/openrpc.json | 160 +- crates/sui-package-resolver/src/lib.rs | 172 +- ...ge_resolver__tests__cross_module_enum.snap | 23 - ...resolver__tests__cross_module_layout.snap} | 0 ...e_resolver__tests__cross_package_enum.snap | 110 - ...esolver__tests__cross_package_layout.snap} | 0 ...ts__multiple_linkage_contexts_layout.snap} | 0 ...ge_resolver__tests__relinking_layout.snap} | 0 ..._resolver__tests__simple_type_layout.snap} | 2 +- ...esolver__tests__upgraded_package_enum.snap | 34 - ...lver__tests__upgraded_package_layout.snap} | 0 ...raded_package_non_defining_id_layout.snap} | 0 ...tests__value_nesting_boundary_layout.snap} | 0 crates/sui-protocol-config/src/lib.rs | 99 +- ...ocol_config__test__Mainnet_version_61.snap | 327 ++ ...ocol_config__test__Mainnet_version_62.snap | 328 ++ ...ocol_config__test__Mainnet_version_63.snap | 328 ++ ...ocol_config__test__Mainnet_version_64.snap | 329 ++ ...ocol_config__test__Mainnet_version_65.snap | 330 ++ ...ocol_config__test__Testnet_version_61.snap | 328 ++ ...ocol_config__test__Testnet_version_62.snap | 329 ++ ...ocol_config__test__Testnet_version_63.snap | 329 ++ ...ocol_config__test__Testnet_version_64.snap | 330 ++ ...ocol_config__test__Testnet_version_65.snap | 330 ++ ...sui_protocol_config__test__version_61.snap | 338 ++ ...sui_protocol_config__test__version_62.snap | 339 ++ ...sui_protocol_config__test__version_63.snap | 339 ++ ...sui_protocol_config__test__version_64.snap | 340 ++ ...sui_protocol_config__test__version_65.snap | 340 ++ crates/sui-proxy/src/admin.rs | 26 +- crates/sui-proxy/src/lib.rs | 139 +- crates/sui-proxy/src/peers.rs | 49 +- crates/sui-rest-api/Cargo.toml | 5 +- crates/sui-rest-api/openapi/openapi.json | 414 +- crates/sui-rest-api/src/accept.rs | 14 + crates/sui-rest-api/src/accounts.rs | 4 +- crates/sui-rest-api/src/checkpoints.rs | 2 +- crates/sui-rest-api/src/client/mod.rs | 4 + crates/sui-rest-api/src/client/sdk.rs | 91 +- crates/sui-rest-api/src/coins.rs | 4 +- crates/sui-rest-api/src/committee.rs | 2 +- crates/sui-rest-api/src/error.rs | 5 +- crates/sui-rest-api/src/health.rs | 22 +- crates/sui-rest-api/src/info.rs | 50 +- crates/sui-rest-api/src/lib.rs | 54 +- crates/sui-rest-api/src/objects.rs | 6 +- crates/sui-rest-api/src/openapi.rs | 63 +- crates/sui-rest-api/src/reader.rs | 16 +- crates/sui-rest-api/src/system.rs | 34 +- .../src/transactions/execution.rs | 170 +- crates/sui-rest-api/src/transactions/mod.rs | 16 +- .../src/transactions/resolve/literal.rs | 787 ++++ .../src/transactions/resolve/mod.rs | 838 ++++ crates/sui-rosetta/Cargo.toml | 2 + crates/sui-rosetta/src/construction.rs | 2 +- crates/sui-rosetta/src/lib.rs | 21 +- crates/sui-rosetta/src/operations.rs | 12 +- crates/sui-rosetta/src/types.rs | 6 +- .../unit_tests/balance_changing_tx_tests.rs | 3 +- .../sui-rosetta/src/unit_tests/lib_tests.rs | 147 + .../sui-rosetta/src/unit_tests/types_tests.rs | 60 +- .../test_coin_no_symbol/Move.toml | 9 + .../sources/test_coin.move | 24 + .../tests/custom_coins/test_coin_utils.rs | 11 +- .../sui-rosetta/tests/custom_coins_tests.rs | 76 +- crates/sui-rosetta/tests/end_to_end_tests.rs | 18 +- crates/sui-sdk/examples/event_api.rs | 7 +- crates/sui-sdk/src/apis.rs | 2 +- crates/sui-snapshot/src/reader.rs | 2 +- crates/sui-source-validation/src/toolchain.rs | 6 +- .../sui-storage/src/http_key_value_store.rs | 86 +- crates/sui-storage/src/lib.rs | 3 - .../sui-storage/src/object_store/http/gcs.rs | 2 +- .../sui-storage/src/object_store/http/s3.rs | 4 +- crates/sui-storage/src/object_store/mod.rs | 11 +- crates/sui-storage/tests/key_value_tests.rs | 20 +- crates/sui-swarm-config/Cargo.toml | 1 + .../src/network_config_builder.rs | 2 + .../src/node_config_builder.rs | 22 +- ...ests__genesis_config_snapshot_matches.snap | 2 +- ...ests__network_config_snapshot_matches.snap | 140 +- ..._populated_genesis_snapshot_matches-2.snap | 30 +- crates/sui-swarm/src/memory/swarm.rs | 11 +- crates/sui-tls/Cargo.toml | 1 + crates/sui-tls/src/lib.rs | 36 +- crates/sui-tls/src/verifier.rs | 40 +- crates/sui-tool/src/db_tool/db_dump.rs | 2 +- crates/sui-tool/src/db_tool/index_search.rs | 10 +- crates/sui-transaction-checks/src/deny.rs | 2 +- .../src/test_adapter.rs | 16 +- crates/sui-types/Cargo.toml | 3 +- crates/sui-types/src/digests.rs | 56 +- crates/sui-types/src/dynamic_field.rs | 6 +- crates/sui-types/src/dynamic_field/visitor.rs | 531 +++ crates/sui-types/src/error.rs | 5 +- crates/sui-types/src/event.rs | 3 +- .../sui-types/src/executable_transaction.rs | 23 +- crates/sui-types/src/lib.rs | 2 +- crates/sui-types/src/message_envelope.rs | 60 + crates/sui-types/src/messages_consensus.rs | 49 +- crates/sui-types/src/messages_grpc.rs | 44 +- crates/sui-types/src/object.rs | 1 + .../sui-types/src/object/balance_traversal.rs | 14 +- .../sui-types/src/object/bounded_visitor.rs | 122 +- ...rsions.rs => sui_sdk_types_conversions.rs} | 2 +- crates/sui-types/src/traffic_control.rs | 6 + crates/sui-types/src/transaction.rs | 24 +- crates/sui-types/src/transaction_executor.rs | 21 + .../tests__constant_name_change.snap | 20 +- .../tests__constant_value_changed.snap | 20 +- .../tests__friend_entry_changed.snap | 26 +- .../tests__friend_function_change.snap | 26 +- .../tests__large_package_equality_check.snap | 20 +- ...e_package_equality_check_alpha_rename.snap | 21 +- ..._package_equality_check_local_shuffle.snap | 20 +- .../tests__large_package_inclusion_check.snap | 21 +- ...kage_invalid_equality_inclusion_check.snap | 20 +- ...sts__private_entry_and_friend_changes.snap | 28 +- ...ests__private_entry_fun_entry_removed.snap | 26 +- .../tests__public_fun_param_alpha_rename.snap | 20 +- .../tests__public_fun_param_permute.snap | 20 +- .../snapshots/tests__public_fun_rename.snap | 20 +- .../tests__struct_field_name_change.snap | 21 +- .../tests__struct_field_reorder.snap | 20 +- ...__struct_field_reorder_no_name_change.snap | 20 +- .../tests__struct_layout_change.snap | 20 +- .../snapshots/tests__struct_name_change.snap | 21 +- .../tests/tests.rs | 46 +- crates/sui/src/client_commands.rs | 26 +- crates/sui/src/keytool.rs | 18 + crates/sui/src/sui_commands.rs | 39 +- .../fixtures/upgrade_errors/all_v1/Move.toml | 6 + .../all_v1/sources/UpgradeErrors.move | 95 + .../fixtures/upgrade_errors/all_v2/Move.toml | 6 + .../all_v2/sources/UpgradeErrors.move | 89 + .../upgrade_errors/entry_linking_v1/Move.toml | 6 + .../sources/UpgradeErrors.move | 10 + .../upgrade_errors/entry_linking_v2/Move.toml | 6 + .../sources/UpgradeErrors.move | 9 + .../friend_linking_v1/Move.toml | 6 + .../sources/UpgradeErrors.move | 15 + .../friend_linking_v2/Move.toml | 6 + .../sources/UpgradeErrors.move | 12 + .../struct_missing_v1/Move.toml | 6 + .../sources/UpgradeErrors.move | 13 + .../struct_missing_v2/Move.toml | 6 + .../sources/UpgradeErrors.move | 11 + ...upgrade_compatibility_tests__all_fail.snap | 23 + ...e_compatibility_tests__struct_missing.snap | 6 + .../unit_tests/upgrade_compatibility_tests.rs | 65 + crates/sui/src/upgrade_compatibility.rs | 413 ++ crates/sui/tests/cli_tests.rs | 2 +- crates/suins-indexer/Cargo.toml | 5 + crates/suins-indexer/README.md | 12 + crates/suins-indexer/src/lib.rs | 86 +- crates/suins-indexer/src/main.rs | 1 + crates/suiop-cli/Cargo.toml | 4 + crates/suiop-cli/src/cli/ci/image.rs | 121 +- crates/suiop-cli/src/cli/ci/mod.rs | 2 +- .../suiop-cli/src/cli/incidents/incident.rs | 11 +- crates/suiop-cli/src/cli/incidents/mod.rs | 2 + crates/suiop-cli/src/cli/incidents/notion.rs | 225 ++ .../suiop-cli/src/cli/incidents/selection.rs | 55 +- crates/suiop-cli/src/cli/incidents/user.rs | 60 + crates/suiop-cli/src/cli/mod.rs | 1 + crates/suiop-cli/src/cli/notion/ids.rs | 72 + crates/suiop-cli/src/cli/notion/mod.rs | 241 ++ .../suiop-cli/src/cli/notion/models/block.rs | 615 +++ .../src/cli/notion/models/block/tests.rs | 305 ++ .../notion/models/block/tests/callout.json | 43 + .../models/block/tests/emoji_object.json | 4 + .../block/tests/external_file_object.json | 6 + .../models/block/tests/file_object.json | 7 + .../notion/models/block/tests/heading_1.json | 175 + .../suiop-cli/src/cli/notion/models/error.rs | 74 + crates/suiop-cli/src/cli/notion/models/mod.rs | 276 ++ .../suiop-cli/src/cli/notion/models/paging.rs | 21 + .../src/cli/notion/models/properties.rs | 520 +++ .../cli/notion/models/properties/formulas.rs | 30 + .../src/cli/notion/models/properties/tests.rs | 62 + .../properties/tests/date_property.json | 8 + .../properties/tests/formula_date_value.json | 11 + .../tests/formula_number_value.json | 8 + .../tests/null_select_property.json | 5 + .../properties/tests/rollup_property.json | 32 + .../properties/tests/select_property.json | 9 + .../properties/tests/text_with_link.json | 25 + .../suiop-cli/src/cli/notion/models/search.rs | 531 +++ .../suiop-cli/src/cli/notion/models/tests.rs | 225 ++ .../src/cli/notion/models/tests/error.json | 6 + .../src/cli/notion/models/tests/issue_15.json | 89 + .../src/cli/notion/models/tests/page.json | 58 + .../cli/notion/models/tests/query_result.json | 43 + .../models/tests/rich_text_mention_date.json | 21 + .../rich_text_mention_date_with_end.json | 21 + ...h_text_mention_date_with_end_and_time.json | 21 + .../rich_text_mention_date_with_time.json | 21 + .../tests/rich_text_mention_user_person.json | 26 + .../notion/models/tests/rich_text_text.json | 19 + .../notion/models/tests/search_results.json | 209 + .../notion/models/tests/unknown_error.json | 6 + .../suiop-cli/src/cli/notion/models/text.rs | 131 + .../suiop-cli/src/cli/notion/models/users.rs | 37 + crates/suiop-cli/src/cli/notion/properties.rs | 520 +++ crates/suiop-cli/src/cli/pulumi/init.rs | 10 +- crates/suiop-cli/src/cli/pulumi/mod.rs | 34 +- crates/suiop-cli/src/cli/pulumi/setup.rs | 2 +- crates/suiop-cli/src/cli/slack/mod.rs | 7 +- crates/suiop-cli/src/cli/slack/slack_api.rs | 39 +- crates/suiop-cli/src/main.rs | 2 +- crates/telemetry-subscribers/Cargo.toml | 16 +- .../src/bin/import-trace.rs | 24 +- .../src/file_exporter.rs | 17 +- crates/telemetry-subscribers/src/lib.rs | 28 +- crates/test-cluster/Cargo.toml | 1 - crates/test-cluster/src/lib.rs | 293 +- crates/typed-store-workspace-hack/Cargo.toml | 1 + crates/typed-store/src/rocks/mod.rs | 98 +- crates/typed-store/src/traits.rs | 4 +- .../src/routes/signature-analyzer.tsx | 4 +- docker/deterministic-canary/Dockerfile | 2 +- docker/sui-bridge-indexer/Dockerfile | 2 +- docker/sui-graphql-rpc-staging/Dockerfile | 33 + docker/sui-graphql-rpc/Dockerfile | 2 +- docker/sui-indexer-tidb/Dockerfile | 2 +- docker/sui-indexer/Dockerfile | 2 +- .../docker-compose-antithesis.yaml | 2 + docker/sui-node/Dockerfile | 11 +- docker/sui-proxy/Dockerfile | 20 + docker/sui-services/Dockerfile | 6 +- docker/sui-source-service/Dockerfile | 2 +- docker/sui-tools/Dockerfile | 2 +- docs/content/concepts/events.mdx | 107 - docs/content/concepts/object-model.mdx | 2 +- docs/content/concepts/research-papers.mdx | 145 +- .../sui-move-concepts/conventions.mdx | 528 +-- .../packages/custom-policies.mdx | 37 +- .../concepts/transactions/prog-txn-blocks.mdx | 4 +- docs/content/guides.mdx | 17 +- docs/content/guides/developer.mdx | 9 + .../developer/advanced/custom-indexer.mdx | 10 +- .../developer/advanced/graphql-migration.mdx | 43 +- .../developer/advanced/randomness-onchain.mdx | 164 +- .../developer/app-examples/coin-flip.mdx | 12 +- .../developer/app-examples/images/styles.png | Bin 0 -> 11641 bytes .../images/trustless-accept-escrow.png | Bin 0 -> 72945 bytes .../images/trustless-cancel-escrow.png | Bin 0 -> 60283 bytes .../images/trustless-escrow-locked.png | Bin 0 -> 43815 bytes .../images/trustless-lock-bear.png | Bin 0 -> 23708 bytes .../images/trustless-my-locked.png | Bin 0 -> 26789 bytes .../images/trustless-new-bear.png | Bin 0 -> 37565 bytes .../app-examples/images/trustless-objects.png | Bin 0 -> 80159 bytes .../images/trustless-start-escrow.png | Bin 0 -> 47171 bytes .../images/trustless-unlock-bear.png | Bin 0 -> 28848 bytes .../developer/app-examples/tic-tac-toe.mdx | 19 +- .../developer/app-examples/trusted-swap.mdx | 6 - .../developer/app-examples/trustless-swap.mdx | 2079 +++++++++- .../app-examples/trustless-swap/backend.mdx | 163 - .../app-examples/trustless-swap/frontend.mdx | 817 ---- .../trustless-swap/indexer-api.mdx | 632 --- .../developer/app-examples/weather-oracle.mdx | 47 +- docs/content/guides/developer/coin.mdx | 2 + .../guides/developer/coin/in-game-token.mdx | 4 +- .../content/guides/developer/coin/loyalty.mdx | 2 +- .../guides/developer/first-app/build-test.mdx | 2 +- .../developer/getting-started/get-coins.mdx | 4 +- .../developer/getting-started/graphql-rpc.mdx | 6 +- .../developer/images/stablecoinsuccess.png | Bin 0 -> 27336 bytes .../guides/developer/images/stablecoinui.png | Bin 0 -> 20020 bytes docs/content/guides/developer/nft.mdx | 3 +- .../developer/nft/asset-tokenization.mdx | 6 +- docs/content/guides/developer/stablecoins.mdx | 234 ++ .../guides/developer/sui-101/building-ptb.mdx | 94 +- .../guides/developer/sui-101/shared-owned.mdx | 4 +- .../developer/sui-101/sign-and-send-txn.mdx | 4 +- .../guides/developer/sui-101/sponsor-txn.mdx | 7 +- .../guides/developer/sui-101/using-events.mdx | 400 +- docs/content/guides/operator.mdx | 8 +- docs/content/guides/operator/archives.mdx | 9 +- .../operator/bridge-node-configuration.mdx | 292 ++ .../guides/operator/data-management.mdx | 72 + docs/content/guides/operator/monitoring.mdx | 25 + docs/content/guides/operator/snapshots.mdx | 4 +- .../content/guides/operator/sui-full-node.mdx | 191 +- docs/content/guides/operator/updates.mdx | 39 + .../references/contribute/mdx-components.mdx | 66 +- docs/content/sidebars/concepts.js | 1 - docs/content/sidebars/guides.js | 24 +- docs/content/snippets/deepbook.mdx | 17 +- .../standards/deepbookv2/routing-a-swap.mdx | 6 +- docs/content/standards/deepbookv3-sdk.mdx | 11 +- .../standards/deepbookv3-sdk/pools.mdx | 106 +- .../standards/deepbookv3/query-the-pool.mdx | 80 +- docs/content/standards/wallet-standard.mdx | 14 +- docs/site/docusaurus.config.js | 18 +- docs/site/src/components/BetaTag/index.tsx | 10 +- docs/site/src/components/EffortBox/index.tsx | 45 + docs/site/src/css/custom.css | 31 + docs/site/src/pages/index.js | 17 + docs/site/src/plugins/effort/index.js | 29 + .../src/plugins/inject-code/injectLoader.js | 260 +- docs/site/src/plugins/inject-code/utils.js | 39 +- docs/site/src/theme/Admonition/Types.js | 30 + .../src/theme/CodeBlock/Content/String.js | 124 + .../theme/CodeBlock/Content/styles.module.css | 80 + docs/site/src/theme/MDXComponents/Details.js | 59 + docs/site/src/theme/MDXContent/index.js | 2 + docs/site/vercel.json | 3 +- examples/custom-indexer/rust/local_reader.rs | 1 + examples/custom-indexer/rust/remote_reader.rs | 1 + .../ui/src/components/NewMultiSigGame.tsx | 6 +- .../trading/api/helpers/create-demo-data.ts | 4 +- .../api/helpers/create-demo-escrows.ts | 4 +- examples/trading/api/sui-utils.ts | 4 +- .../contracts/escrow/sources/lock.move | 2 +- .../contracts/escrow/sources/shared.move | 13 + .../src/components/escrows/Escrow.tsx | 3 +- .../src/components/escrows/EscrowList.tsx | 12 +- .../src/components/locked/ApiLockedList.tsx | 23 +- .../components/locked/LockOwnedObjects.tsx | 3 +- .../src/components/locked/partials/Locked.tsx | 2 +- .../src/hooks/useTransactionExecution.ts | 2 +- .../trading/frontend/src/mutations/escrow.ts | 103 +- .../trading/frontend/src/mutations/locked.ts | 4 + .../trading/frontend/src/networkConfig.ts | 19 + .../frontend/src/routes/EscrowDashboard.tsx | 1 - .../frontend/src/routes/LockedDashboard.tsx | 2 +- examples/usdc-transfer-app/App.js | 163 + examples/usdc-transfer-app/favicon.ico | Bin 0 -> 918 bytes examples/usdc-transfer-app/global.css | 64 + examples/usdc-transfer-app/index.html | 14 + examples/usdc-transfer-app/index.js | 10 + examples/usdc-transfer-app/lib/App-stub.js | 17 + examples/usdc-transfer-app/package.json | 23 + external-crates/move/Cargo.lock | 23 + external-crates/move/Cargo.toml | 1 + .../crates/module-generation/src/generator.rs | 30 +- .../move/crates/move-analyzer/Cargo.toml | 1 + .../editors/code/language-configuration.json | 2 +- .../editors/code/package-lock.json | 31 +- .../move-analyzer/editors/code/package.json | 8 +- .../editors/code/tests/runTests.ts | 4 + .../src/analysis/typing_analysis.rs | 86 +- .../move-analyzer/src/completions/utils.rs | 8 +- .../crates/move-analyzer/src/diagnostics.rs | 14 +- .../move/crates/move-analyzer/src/symbols.rs | 366 +- .../tests/colon_colon_completion.exp | 116 +- .../crates/move-analyzer/tests/consts.exp | 24 + .../crates/move-analyzer/tests/consts.ide | 12 + .../crates/move-analyzer/tests/docstring.exp | 171 +- .../crates/move-analyzer/tests/docstring.ide | 20 + .../crates/move-analyzer/tests/dot_calls.exp | 45 +- .../move-analyzer/tests/dot_completion.exp | 68 +- .../move/crates/move-analyzer/tests/enums.exp | 53 +- .../move/crates/move-analyzer/tests/enums.ide | 4 + .../move-analyzer/tests/implicit_uses.exp | 12 +- .../crates/move-analyzer/tests/imports.exp | 21 +- .../crates/move-analyzer/tests/macros.exp | 37 +- .../crates/move-analyzer/tests/mod_access.exp | 18 +- .../tests/move-2024/sources/structs.move | 37 + .../crates/move-analyzer/tests/structs.exp | 122 + .../crates/move-analyzer/tests/structs.ide | 56 + .../crates/move-analyzer/tests/symbols.exp | 221 +- .../crates/move-analyzer/tests/symbols.ide | 28 + .../move-analyzer/tests/symbols/Move.toml | 2 +- .../tests/symbols/sources/M1.move | 8 +- .../tests/symbols/sources/M2.move | 2 +- .../tests/symbols/sources/M3.move | 4 +- .../tests/symbols/sources/M4.move | 31 +- .../tests/symbols/sources/M6.move | 45 +- .../tests/symbols/sources/M7.move | 2 +- .../tests/symbols/sources/M8.move | 8 + .../tests/symbols/sources/M9.move | 2 +- .../trace-adapter/src/adapter.ts | 166 +- .../trace-adapter/src/runtime.ts | 347 +- .../trace-adapter/src/source_map_utils.ts | 46 +- .../trace-adapter/src/trace_utils.ts | 270 +- .../trace-debug/src/extension.ts | 48 +- .../move-binary-format/src/compatibility.rs | 217 +- .../src/compatibility_mode.rs | 259 ++ .../src/file_format_common.rs | 16 +- .../move/crates/move-binary-format/src/lib.rs | 1 + .../src/unit_tests/compatibility_tests.rs | 79 - .../move-bytecode-source-map/Cargo.toml | 1 + .../src/source_map.rs | 20 + .../move-bytecode-source-map/src/utils.rs | 37 +- .../move/crates/move-cli/Cargo.toml | 5 + .../move/crates/move-cli/src/base/test.rs | 6 + .../crates/move-cli/src/sandbox/utils/mod.rs | 8 - .../tracing-unit-tests/Move.toml | 9 + .../tracing-unit-tests/NO_TEMPDIR | 0 .../tracing_tests/tracing-unit-tests/args.exp | 28 + .../tracing_tests/tracing-unit-tests/args.txt | 2 + .../0x1__calls__test_call_order.json | 1 + .../0x1__calls__test_call_return_order.json | 1 + ...0x1__calls__test_complex_nested_calls.json | 1 + .../0x1__calls__test_return_order.json | 1 + .../new_traces/0x1__errors__aborter.json | 1 + .../new_traces/0x1__errors__bad_cast.json | 1 + .../new_traces/0x1__errors__div_0.json | 1 + .../0x1__errors__fail_during_abort.json | 1 + .../new_traces/0x1__errors__overshift_l.json | 1 + .../new_traces/0x1__errors__overshift_r.json | 1 + .../new_traces/0x1__errors__underflow.json | 1 + ...0x1__natives__get_orig_type_name_test.json | 1 + .../0x1__natives__get_type_name_test.json | 1 + .../0x1__packs__test_gen_pack_order.json | 1 + .../0x1__packs__test_gen_unpack_order.json | 1 + .../0x1__packs__test_pack_order.json | 1 + .../0x1__packs__test_unpack_order.json | 1 + ...ces__nested_struct_reference_mutation.json | 1 + ...ferences__pass_mut_assign_in_other_fn.json | 1 + .../0x1__references__test_struct_borrow.json | 1 + ...1__references__test_vector_mut_borrow.json | 1 + ...eferences__test_vector_mut_borrow_pop.json | 1 + .../0x1__calls__test_call_order.json | 1 + .../0x1__calls__test_call_return_order.json | 1 + ...0x1__calls__test_complex_nested_calls.json | 1 + .../0x1__calls__test_return_order.json | 1 + .../saved_traces/0x1__errors__aborter.json | 1 + .../saved_traces/0x1__errors__bad_cast.json | 1 + .../saved_traces/0x1__errors__div_0.json | 1 + .../0x1__errors__fail_during_abort.json | 1 + .../0x1__errors__overshift_l.json | 1 + .../0x1__errors__overshift_r.json | 1 + .../saved_traces/0x1__errors__underflow.json | 1 + ...0x1__natives__get_orig_type_name_test.json | 1 + .../0x1__natives__get_type_name_test.json | 1 + .../0x1__packs__test_gen_pack_order.json | 1 + .../0x1__packs__test_gen_unpack_order.json | 1 + .../0x1__packs__test_pack_order.json | 1 + .../0x1__packs__test_unpack_order.json | 1 + ...ces__nested_struct_reference_mutation.json | 1 + ...ferences__pass_mut_assign_in_other_fn.json | 1 + .../0x1__references__test_struct_borrow.json | 1 + ...1__references__test_vector_mut_borrow.json | 1 + ...eferences__test_vector_mut_borrow_pop.json | 1 + .../tracing-unit-tests/sources/calls.move | 75 + .../tracing-unit-tests/sources/errors.move | 44 + .../tracing-unit-tests/sources/natives.move | 17 + .../sources/references.move | 74 + .../tracing-unit-tests/sources/structs.move | 44 + .../move-cli/tests/tracing_testsuite.rs | 26 + .../crates/move-compiler/src/cfgir/ast.rs | 42 +- .../crates/move-compiler/src/cfgir/cfg.rs | 16 +- .../move-compiler/src/cfgir/translate.rs | 8 +- .../crates/move-compiler/src/cfgir/visitor.rs | 37 +- .../src/command_line/compiler.rs | 4 +- .../move-compiler/src/diagnostics/mod.rs | 4 +- .../src/expansion/alias_map_builder.rs | 6 +- .../crates/move-compiler/src/expansion/ast.rs | 80 +- .../crates/move-compiler/src/expansion/mod.rs | 1 + .../src/expansion/name_validation.rs | 424 ++ .../src/expansion/path_expander.rs | 5 +- .../move-compiler/src/expansion/translate.rs | 444 +-- .../move/crates/move-compiler/src/hlir/ast.rs | 84 +- .../move-compiler/src/hlir/translate.rs | 1 + .../crates/move-compiler/src/linters/mod.rs | 16 + .../src/linters/redundant_ref_deref.rs | 155 + .../src/linters/self_assignment.rs | 211 + .../crates/move-compiler/src/naming/ast.rs | 82 +- .../move-compiler/src/naming/translate.rs | 5 +- .../crates/move-compiler/src/parser/ast.rs | 76 +- .../crates/move-compiler/src/parser/lexer.rs | 2 +- .../crates/move-compiler/src/parser/syntax.rs | 12 +- .../crates/move-compiler/src/shared/mod.rs | 21 +- .../src/sui_mode/linters/coin_field.rs | 2 +- .../move-compiler/src/sui_mode/typing.rs | 1 + .../src/to_bytecode/translate.rs | 18 +- .../crates/move-compiler/src/typing/ast.rs | 74 +- .../crates/move-compiler/src/typing/core.rs | 4 +- .../move-compiler/src/typing/translate.rs | 3 +- .../move-compiler/src/typing/visitor.rs | 26 +- .../linter/correct_redundant_ref_deref.move | 10 + .../linter/false_negative_self_assigment.move | 26 + .../linter/incorrect_redundant_ref_deref.exp | 172 + .../linter/incorrect_redundant_ref_deref.move | 140 + .../linter/move_2024/ref_deref_complex.exp | 72 + .../linter/move_2024/ref_deref_complex.move | 53 + .../move_2024/ref_deref_conditional.exp | 113 + .../move_2024/ref_deref_conditional.move | 89 + .../ref_deref_conditional_valid.move | 85 + .../linter/move_2024/ref_deref_fields.exp | 80 + .../linter/move_2024/ref_deref_fields.move | 71 + .../move_2024/ref_deref_fields_valid.move | 49 + .../tests/linter/redundant_ref_deref.exp | 24 + .../tests/linter/redundant_ref_deref.move | 20 + .../tests/linter/ref_deref_negative.move | 21 + .../tests/linter/ref_deref_path.exp | 16 + .../tests/linter/ref_deref_path.move | 7 + .../tests/linter/ref_deref_triple.exp | 16 + .../tests/linter/ref_deref_triple.move | 7 + .../linter/suppress_self_assignment.move | 15 + .../suppressed_case_redundant_ref_deref.move | 17 + .../linter/true_negative_self_assignment.move | 63 + .../linter/true_positive_self_assignment.exp | 318 ++ .../linter/true_positive_self_assignment.move | 55 + .../move-core-types/src/annotated_value.rs | 40 +- .../move-core-types/src/annotated_visitor.rs | 494 ++- .../src/unit_tests/visitor_test.rs | 467 ++- .../move/crates/move-docgen/src/docgen.rs | 21 +- .../tests/sources/const_string_test.move | 15 + .../sources/const_string_test.spec_inline.md | 56 + .../const_string_test.spec_inline_no_fold.md | 56 + .../const_string_test.spec_separate.md | 56 + .../move-ir-to-bytecode-syntax/src/lexer.rs | 2 +- .../move-ir-to-bytecode-syntax/src/syntax.rs | 32 +- .../move-ir-to-bytecode/src/compiler.rs | 116 +- .../move/crates/move-ir-types/src/ast.rs | 60 +- .../move-model/src/builder/model_builder.rs | 1 + .../move-model/src/builder/module_builder.rs | 30 +- .../move/crates/move-model/src/model.rs | 10 + .../src/compilation/compiled_package.rs | 9 +- .../move-package/src/lock_file/schema.rs | 19 +- .../crates/move-package/src/package_hooks.rs | 28 +- .../src/resolution/dependency_cache.rs | 4 +- .../src/resolution/dependency_graph.rs | 272 +- .../crates/move-package/src/resolution/mod.rs | 29 +- .../src/resolution/resolution_graph.rs | 2 +- .../src/resolution/resolving_table.rs | 6 + .../src/source_package/manifest_parser.rs | 90 +- .../src/source_package/parsed_manifest.rs | 21 +- .../tests/test_dependency_graph.rs | 34 +- .../move-package/tests/test_lock_file.rs | 12 +- .../crates/move-package/tests/test_runner.rs | 19 +- .../test_sources/basic_no_deps/Move.locked | 2 +- .../Move.locked | 2 +- .../dep_dev_dep_diamond/Move.locked | 22 +- .../test_sources/dep_good_digest/Move.locked | 6 +- .../Move.locked | 18 +- .../Move.toml | 8 +- .../deps_only/C/Move.toml | 8 +- .../Move.toml | 8 +- .../Move.locked | 18 +- .../Move.resolved | 2 +- .../Move.toml | 8 +- .../Move.locked | 20 +- .../Move.resolved | 2 +- .../Move.toml | 8 +- .../Move.locked | 22 +- .../deps_only/C/Move.toml | 8 +- .../deps_only/C/Move.toml | 8 +- .../Move.locked | 28 +- .../diamond_problem_dep_override/Move.locked | 18 +- .../Move.locked | 26 +- .../Move.locked | 30 +- .../diamond_problem_no_conflict/Move.locked | 16 +- .../direct_and_indirect_dep/Move.locked | 16 +- .../tests/test_sources/external/Move.locked | 12 +- .../tests/test_sources/external/Move.resolved | 2 +- .../tests/test_sources/external/Move.toml | 8 +- .../test_sources/external_bad_dep/Move.locked | 12 +- .../test_sources/external_bad_dep/Move.toml | 4 +- .../test_sources/external_broken/Move.toml | 4 +- .../test_sources/external_dev_dep/Move.locked | 20 +- .../external_dev_dep/Move.resolved | 2 +- .../test_sources/external_dev_dep/Move.toml | 8 +- .../test_sources/external_failing/Move.toml | 4 +- .../external_no_resolver/Move.toml | 4 +- .../test_sources/external_overlap/Move.locked | 14 +- .../external_overlap/Move.resolved | 2 +- .../test_sources/external_overlap/Move.toml | 8 +- .../external_overlap_fail/Move.toml | 8 +- .../external_overlap_fail_symmetric/Move.toml | 8 +- .../Move.locked | 19 + .../Move.progress | 6 + .../Move.resolved | 180 + .../external_package_batch_response/Move.toml | 8 + .../deps_only/bar/Move.toml | 5 + .../deps_only/bar/sources/bar.move | 1 + .../deps_only/foo/Move.toml | 5 + .../deps_only/foo/sources/foo.move | 1 + .../sources/root.move | 1 + .../external_resolver_config/Move.locked | 6 + .../external_resolver_config/Move.resolved | 64 + .../external_resolver_config/Move.toml | 6 + .../sources/Root.move | 11 + .../Move.resolved | 3 + .../Move.toml | 5 + .../sources/Root.move | 11 + .../Move.resolved | 3 + .../Move.toml | 5 + .../sources/Root.move | 11 + .../test_sources/external_silent/Move.locked | 12 +- .../test_sources/external_silent/Move.toml | 4 +- .../multiple_deps_no_rename/Move.locked | 10 +- .../nested_deps_git_local/Move.locked | 10 +- .../nested_deps_local_local/Move.locked | 10 +- .../nested_deps_override/Move.locked | 10 +- .../nested_deps_shared_override/Move.locked | 20 +- .../tests/test_sources/one_dep/Move.locked | 6 +- .../one_dep_bad_digest/Move.locked | 6 +- .../test_sources/one_dep_override/Move.locked | 6 +- .../test_sources/package_hooks/Move.resolved | 2 +- .../test_sources/package_hooks/Move.toml | 2 +- .../package_hooks_subdir/Move.resolved | 1 - .../package_hooks_subdir/Move.toml | 5 - .../Move.resolved | 2 +- .../test_sources/resolve_pkg_name/Move.locked | 16 +- .../Move.resolved | 10 +- .../deps_only/A-rename/Move.lock | 6 +- .../deps_only/B/Move.lock | 6 +- .../deps_only/C-rename-v1/Move.lock | 2 +- .../deps_only/C-rename-v2/Move.lock | 2 +- .../Move.resolved | 2 +- .../deps_only/C/Move.toml | 8 +- .../test_sources/resolve_version/Move.locked | 10 +- .../resolve_version_diamond/Move.locked | 16 +- .../resolve_version_diamond_deep/Move.locked | 20 +- .../Move.locked | 20 +- .../Move.locked | 18 +- .../Move.resolved | 2 +- .../Move.toml | 2 +- .../Move.locked | 18 +- .../tests/test_sources/resolvers/silent.sh | 10 +- .../test_sources/resolvers/successful.sh | 10 +- .../test_sources/resolvers/successful_dep.sh | 14 +- .../successful_package_batch_response.sh | 49 + .../resolvers/successful_subst.sh | 10 +- .../successful_subst_name_resolution.sh | 10 +- .../resolvers/successful_version.sh | 10 +- .../move/crates/move-prover/src/lib.rs | 2 +- .../src/function_target_pipeline.rs | 69 +- .../move/crates/move-trace-format/Cargo.toml | 34 + .../crates/move-trace-format/src/format.rs | 388 ++ .../crates/move-trace-format/src/interface.rs | 39 + .../move/crates/move-trace-format/src/lib.rs | 6 + .../move-trace-format/src/memory_tracer.rs | 196 + .../move/crates/move-unit-test/Cargo.toml | 1 + .../move/crates/move-unit-test/src/lib.rs | 12 + .../move-unit-test/src/test_reporter.rs | 32 +- .../crates/move-unit-test/src/test_runner.rs | 54 +- .../move-vm-integration-tests/Cargo.toml | 1 - .../src/tests/compatibility_tests.rs | 10 +- .../move/crates/move-vm-profiler/src/lib.rs | 4 +- .../move/crates/move-vm-runtime/Cargo.toml | 2 +- .../crates/move-vm-runtime/src/interpreter.rs | 150 +- .../move/crates/move-vm-runtime/src/lib.rs | 1 + .../move/crates/move-vm-runtime/src/loader.rs | 12 +- .../crates/move-vm-runtime/src/runtime.rs | 7 + .../crates/move-vm-runtime/src/session.rs | 42 + .../move-vm-runtime/src/tracing2/mod.rs | 107 + .../move-vm-runtime/src/tracing2/tracer.rs | 1738 +++++++++ .../move/crates/move-vm-types/Cargo.toml | 3 - .../move-vm-types/src/values/values_impl.rs | 138 + .../v0/crates/move-vm-runtime/Cargo.toml | 1 - .../v1/crates/move-vm-runtime/Cargo.toml | 1 - .../v2/crates/move-vm-runtime/Cargo.toml | 1 - external-crates/tests.sh | 2 + narwhal/network/src/connectivity.rs | 381 -- narwhal/network/src/lib.rs | 1 - narwhal/primary/src/metrics.rs | 7 +- narwhal/primary/src/primary.rs | 15 +- narwhal/worker/src/tests/worker_tests.rs | 296 +- narwhal/worker/src/worker.rs | 10 +- nre/sui_for_node_operators.md | 12 + package.json | 3 +- pnpm-lock.yaml | 3450 +++++++++-------- rust-toolchain.toml | 2 +- .../check-protocol-compatibility.sh | 92 +- scripts/simtest/cargo-simtest | 4 +- scripts/simtest/config-patch | 4 +- sdk/bcs/CHANGELOG.md | 16 + sdk/bcs/README.md | 30 +- sdk/bcs/package.json | 2 +- sdk/bcs/src/b58.ts | 10 +- sdk/bcs/src/b64.ts | 10 +- sdk/bcs/src/bcs-type.ts | 18 +- sdk/bcs/src/hex.ts | 10 +- sdk/bcs/src/index.ts | 12 +- sdk/bcs/src/utils.ts | 18 +- sdk/bcs/tests/bcs.test.ts | 4 +- sdk/bcs/tests/builder.test.ts | 10 +- sdk/bcs/tests/encodings.test.ts | 34 +- sdk/create-dapp/CHANGELOG.md | 47 + sdk/create-dapp/package.json | 2 +- sdk/dapp-kit/CHANGELOG.md | 52 + sdk/dapp-kit/package.json | 2 +- sdk/dapp-kit/src/hooks/useSuiClientQuery.ts | 66 +- .../wallet/useReportTransactionEffects.ts | 4 +- .../wallet/useSignAndExecuteTransaction.ts | 6 +- .../src/hooks/wallet/useUnsafeBurnerWallet.ts | 4 +- .../useSignAndExecuteTransaction.test.tsx | 4 +- sdk/deepbook-v3/CHANGELOG.md | 88 + sdk/deepbook-v3/package.json | 2 +- sdk/deepbook-v3/src/client.ts | 30 + sdk/deepbook-v3/src/transactions/deepbook.ts | 19 + sdk/deepbook-v3/src/utils/constants.ts | 58 +- sdk/deepbook/CHANGELOG.md | 30 + sdk/deepbook/package.json | 2 +- sdk/docs/package.json | 5 +- sdk/docs/pages/bcs/index.mdx | 30 +- .../pages/dapp-kit/sui-client-provider.mdx | 7 +- .../wallet-components/ConnectModal.mdx | 1 + .../useReportTransactionEffects.mdx | 2 +- .../wallet-hooks/useSignTransaction.mdx | 2 +- .../typescript/cryptography/keypairs.mdx | 15 + .../typescript/cryptography/multisig.mdx | 2 +- .../typescript/owned-object-pool/overview.mdx | 4 +- .../typescript/transaction-building/gas.mdx | 3 + sdk/docs/pages/typescript/utils.mdx | 8 +- sdk/docs/typedoc.json | 3 +- sdk/enoki/CHANGELOG.md | 34 + sdk/enoki/package.json | 2 +- sdk/enoki/src/EnokiFlow.ts | 12 +- sdk/enoki/src/encryption.ts | 14 +- sdk/graphql-transport/CHANGELOG.md | 48 + sdk/graphql-transport/codegen.ts | 2 +- sdk/graphql-transport/package.json | 2 +- .../src/generated/queries.ts | 1145 ++++-- sdk/graphql-transport/src/mappers/bcs.ts | 4 +- .../src/mappers/transaction-block.ts | 59 +- sdk/graphql-transport/src/methods.ts | 24 +- .../src/queries/getCommitteeInfo.graphql | 2 +- .../src/queries/getCurrentEpoch.graphql | 2 +- .../src/queries/getDynamicFieldObject.graphql | 42 +- .../src/queries/getProtocolConfig.graphql | 2 +- .../src/queries/objects.graphql | 2 +- .../src/queries/queryEvents.graphql | 11 +- sdk/kiosk/CHANGELOG.md | 30 + sdk/kiosk/package.json | 2 +- sdk/kiosk/src/query/transfer-policy.ts | 4 +- sdk/kiosk/src/utils.ts | 4 +- sdk/move-bytecode-template/README.md | 6 +- .../tests/universal.test.ts | 6 +- sdk/suins-toolkit/CHANGELOG.md | 30 + sdk/suins-toolkit/package.json | 2 +- sdk/typescript/CHANGELOG.md | 42 + sdk/typescript/README.md | 42 +- sdk/typescript/package.json | 2 +- .../scripts/update-graphql-schemas.ts | 8 +- sdk/typescript/src/bcs/bcs.ts | 30 +- sdk/typescript/src/bcs/effects.ts | 12 + sdk/typescript/src/client/client.ts | 14 +- sdk/typescript/src/client/types/generated.ts | 3 +- sdk/typescript/src/cryptography/keypair.ts | 6 +- sdk/typescript/src/cryptography/mnemonics.ts | 4 +- sdk/typescript/src/cryptography/publickey.ts | 6 +- sdk/typescript/src/cryptography/signature.ts | 6 +- .../graphql/generated/2024-01/tada-env.d.ts | 202 - .../graphql/generated/2024.1/schema.graphql | 22 +- .../graphql/generated/2024.1/tada-env.d.ts | 2 +- .../graphql/generated/2024.4/schema.graphql | 38 +- .../graphql/generated/2024.4/tada-env.d.ts | 5 +- .../src/keypairs/ed25519/ed25519-hd-key.ts | 4 +- .../src/keypairs/ed25519/keypair.ts | 21 +- .../src/keypairs/ed25519/publickey.ts | 4 +- .../src/keypairs/secp256k1/keypair.ts | 16 +- .../src/keypairs/secp256k1/publickey.ts | 4 +- .../src/keypairs/secp256r1/keypair.ts | 16 +- .../src/keypairs/secp256r1/publickey.ts | 4 +- sdk/typescript/src/multisig/publickey.ts | 6 +- sdk/typescript/src/multisig/signer.ts | 6 +- sdk/typescript/src/transactions/Commands.ts | 6 +- sdk/typescript/src/transactions/Inputs.ts | 4 +- .../src/transactions/Transaction.ts | 6 +- .../src/transactions/TransactionData.ts | 4 +- .../__tests__/Transaction.test.ts | 4 +- .../src/transactions/__tests__/bcs.test.ts | 4 +- sdk/typescript/src/transactions/data/v1.ts | 14 +- .../src/transactions/executor/parallel.ts | 4 +- .../src/transactions/executor/serial.ts | 4 +- .../src/transactions/json-rpc-resolver.ts | 2 +- sdk/typescript/src/utils/dynamic-fields.ts | 30 + sdk/typescript/src/utils/index.ts | 15 +- sdk/typescript/src/utils/sui-types.ts | 4 +- sdk/typescript/src/verify/verify.ts | 4 +- sdk/typescript/src/version.ts | 4 +- sdk/typescript/src/zklogin/publickey.ts | 12 +- sdk/typescript/src/zklogin/signature.ts | 9 +- .../test/e2e/coin-with-balance.test.ts | 26 +- sdk/typescript/test/e2e/keypairs.test.ts | 14 +- sdk/typescript/test/e2e/multisig.test.ts | 4 +- .../test/e2e/parallel-executor.test.ts | 44 +- .../test/e2e/receive-object.test.ts | 2 +- .../test/e2e/zklogin-signature.test.ts | 16 +- sdk/typescript/test/unit/arguments.test.ts | 6 +- .../unit/cryptography/ed25519-keypair.test.ts | 6 +- .../test/unit/cryptography/keypair.test.ts | 20 +- .../cryptography/multisig.publickey.test.ts | 8 +- .../test/unit/cryptography/multisig.test.ts | 14 +- .../test/unit/cryptography/publickey.test.ts | 6 +- .../cryptography/secp256k1-keypair.test.ts | 10 +- .../cryptography/secp256k1-publickey.test.ts | 10 +- .../cryptography/secp256r1-keypair.test.ts | 10 +- .../cryptography/secp256r1-publickey.test.ts | 10 +- .../test/unit/cryptography/signature.test.ts | 10 +- .../test/unit/object-inputs.test.ts | 6 +- .../test/unit/utils/dynamic-fields.test.ts | 19 + sdk/typescript/test/unit/v1-json.test.ts | 6 +- sdk/wallet-standard/CHANGELOG.md | 30 + sdk/wallet-standard/package.json | 2 +- sdk/wallet-standard/src/wallet.ts | 6 +- sdk/zklogin/CHANGELOG.md | 31 + sdk/zklogin/package.json | 2 +- sdk/zklogin/src/nonce.ts | 4 +- sdk/zksend/CHANGELOG.md | 41 + sdk/zksend/package.json | 2 +- sdk/zksend/src/index.test.ts | 4 +- sdk/zksend/src/links/builder.ts | 4 +- sdk/zksend/src/links/claim.ts | 12 +- sdk/zksend/src/links/list-created-links.ts | 6 +- sdk/zksend/src/wallet/index.ts | 4 +- sui-execution/cut/src/plan.rs | 3 +- sui-execution/latest/sui-adapter/Cargo.toml | 1 - .../programmable_transactions/execution.rs | 33 +- .../latest/sui-adapter/src/temporary_store.rs | 5 +- .../sui-move-natives/src/dynamic_field.rs | 6 +- .../src/object_runtime/mod.rs | 23 +- .../sui-move-natives/src/test_scenario.rs | 192 +- .../latest/sui-move-natives/src/validator.rs | 2 +- .../latest/sui-verifier/src/verifier.rs | 3 +- sui-execution/v0/sui-adapter/Cargo.toml | 1 - .../programmable_transactions/execution.rs | 5 - .../v0/sui-adapter/src/temporary_store.rs | 5 +- .../v0/sui-move-natives/src/dynamic_field.rs | 6 +- .../src/object_runtime/mod.rs | 71 +- .../v0/sui-move-natives/src/test_scenario.rs | 183 +- sui-execution/v0/sui-verifier/src/verifier.rs | 3 +- sui-execution/v1/sui-adapter/Cargo.toml | 1 - .../programmable_transactions/execution.rs | 10 +- .../v1/sui-adapter/src/temporary_store.rs | 5 +- .../v1/sui-move-natives/src/dynamic_field.rs | 6 +- .../src/object_runtime/mod.rs | 71 +- .../v1/sui-move-natives/src/test_scenario.rs | 183 +- sui-execution/v1/sui-verifier/src/verifier.rs | 3 +- sui-execution/v2/sui-adapter/Cargo.toml | 1 - .../programmable_transactions/execution.rs | 10 +- .../v2/sui-adapter/src/temporary_store.rs | 5 +- .../v2/sui-move-natives/src/dynamic_field.rs | 6 +- .../src/object_runtime/mod.rs | 71 +- .../v2/sui-move-natives/src/test_scenario.rs | 188 +- sui-execution/v2/sui-verifier/src/verifier.rs | 3 +- 1492 files changed, 72851 insertions(+), 33730 deletions(-) create mode 100644 .github/workflows/split-cluster-bisect.yml create mode 100644 apps/wallet/src/ui/app/hooks/useGetAllBalances.ts create mode 100644 apps/wallet/src/ui/app/hooks/useSupportedCoins.ts create mode 100644 apps/wallet/src/ui/app/hooks/useValidSwapTokensList.ts create mode 100644 apps/wallet/src/ui/app/pages/home/usdc-promo/UsdcPromo.tsx create mode 100644 apps/wallet/src/ui/app/pages/home/usdc-promo/UsdcPromoBanner.tsx create mode 100644 apps/wallet/src/ui/app/pages/home/usdc-promo/useUsdcPromo.ts create mode 100644 apps/wallet/src/ui/app/pages/swap/CoinsSelectionPage.tsx delete mode 100644 apps/wallet/src/ui/app/pages/swap/FromAssets.tsx create mode 100644 apps/wallet/src/ui/app/pages/swap/GasFeesSummary.tsx delete mode 100644 apps/wallet/src/ui/app/pages/swap/ToAssetSection.tsx delete mode 100644 apps/wallet/src/ui/app/pages/swap/ToAssets.tsx create mode 100644 apps/wallet/src/ui/app/pages/swap/useSwapTransaction.ts delete mode 100644 bridge/evm/deploy_configs/example.json create mode 100644 bridge/evm/deploy_configs/mainnet.json create mode 100644 crates/mysten-common/src/logging.rs create mode 100644 crates/sui-bridge-indexer/src/migrations/2024-09-05-180103_governance_actions/down.sql create mode 100644 crates/sui-bridge-indexer/src/migrations/2024-09-05-180103_governance_actions/up.sql create mode 100644 crates/sui-bridge-watchdog/Cargo.toml create mode 100644 crates/sui-bridge-watchdog/src/eth_bridge_status.rs create mode 100644 crates/sui-bridge-watchdog/src/eth_vault_balance.rs create mode 100644 crates/sui-bridge-watchdog/src/lib.rs create mode 100644 crates/sui-bridge-watchdog/src/metrics.rs create mode 100644 crates/sui-bridge-watchdog/src/sui_bridge_status.rs rename crates/{sui-storage/src/indexes.rs => sui-core/src/jsonrpc_index.rs} (82%) create mode 100644 crates/sui-core/src/par_index_live_object_set.rs create mode 100644 crates/sui-data-ingestion-core/src/reducer.rs create mode 100644 crates/sui-deepbook-indexer/src/error.rs create mode 100644 crates/sui-deepbook-indexer/src/server.rs delete mode 100644 crates/sui-e2e-tests/tests/bridge_tests.rs create mode 100644 crates/sui-framework-snapshot/bytecode_snapshot/62/0x0000000000000000000000000000000000000000000000000000000000000001 create mode 100644 crates/sui-framework-snapshot/bytecode_snapshot/62/0x0000000000000000000000000000000000000000000000000000000000000002 create mode 100644 crates/sui-framework-snapshot/bytecode_snapshot/62/0x0000000000000000000000000000000000000000000000000000000000000003 create mode 100644 crates/sui-framework-snapshot/bytecode_snapshot/62/0x000000000000000000000000000000000000000000000000000000000000000b create mode 100644 crates/sui-framework-snapshot/bytecode_snapshot/62/0x000000000000000000000000000000000000000000000000000000000000dee9 create mode 100644 crates/sui-framework-snapshot/bytecode_snapshot/65/0x0000000000000000000000000000000000000000000000000000000000000001 create mode 100644 crates/sui-framework-snapshot/bytecode_snapshot/65/0x0000000000000000000000000000000000000000000000000000000000000002 create mode 100644 crates/sui-framework-snapshot/bytecode_snapshot/65/0x0000000000000000000000000000000000000000000000000000000000000003 create mode 100644 crates/sui-framework-snapshot/bytecode_snapshot/65/0x000000000000000000000000000000000000000000000000000000000000000b create mode 100644 crates/sui-framework-snapshot/bytecode_snapshot/65/0x000000000000000000000000000000000000000000000000000000000000dee9 create mode 100644 crates/sui-framework/packages/move-stdlib/sources/uq32_32.move create mode 100644 crates/sui-framework/packages/move-stdlib/tests/uq32_32_tests.move create mode 100644 crates/sui-framework/packages/sui-framework/tests/display_tests.move create mode 100644 crates/sui-framework/packages/sui-framework/tests/hex_tests.move create mode 100644 crates/sui-framework/packages/sui-system/tests/staking_pool.move create mode 100644 crates/sui-graphql-e2e-tests/tests/stable/epoch/epoch_start_to_end.exp create mode 100644 crates/sui-graphql-e2e-tests/tests/stable/epoch/epoch_start_to_end.move create mode 100644 crates/sui-graphql-e2e-tests/tests/stable/epoch/pagination.exp create mode 100644 crates/sui-graphql-e2e-tests/tests/stable/epoch/pagination.move create mode 100644 crates/sui-graphql-e2e-tests/tests/stable/events/sending_module.exp create mode 100644 crates/sui-graphql-e2e-tests/tests/stable/events/sending_module.move create mode 100644 crates/sui-graphql-e2e-tests/tests/stable/objects/wrap_unwrap.exp create mode 100644 crates/sui-graphql-e2e-tests/tests/stable/objects/wrap_unwrap.move create mode 100644 crates/sui-graphql-e2e-tests/tests/stable/packages/bcs.exp create mode 100644 crates/sui-graphql-e2e-tests/tests/stable/packages/bcs.move create mode 100644 crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/affected_address.exp create mode 100644 crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/affected_address.move create mode 100644 crates/sui-graphql-e2e-tests/tests/staging/transactions/filters/affected_object.exp create mode 100644 crates/sui-graphql-e2e-tests/tests/staging/transactions/filters/affected_object.move rename crates/sui-graphql-rpc/examples/transaction_block_connection/{recv_addr_filter.graphql => affected_addr_filter.graphql} (89%) rename crates/sui-graphql-rpc/examples/transaction_block_connection/{input_object_sign_addr_filter.graphql => input_object_sent_addr_filter.graphql} (86%) rename crates/sui-graphql-rpc/examples/transaction_block_connection/{sign_addr_filter.graphql => sent_addr_filter.graphql} (77%) create mode 100644 crates/sui-indexer/migrations/pg/2024-09-10-195655_drop-df-columns/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-10-195655_drop-df-columns/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-12-213234_watermarks/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-12-213234_watermarks/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-18-003318_epochs_add_json_system_state/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-18-003318_epochs_add_json_system_state/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-19-011238_raw_checkpoints/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-19-011238_raw_checkpoints/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-24-213054_epochs_system_state_nullable/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-24-213054_epochs_system_state_nullable/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135801_event_emit_module_pruning_index/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135801_event_emit_module_pruning_index/metadata.toml create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135801_event_emit_module_pruning_index/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135802_event_emit_package_pruning_index/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135802_event_emit_package_pruning_index/metadata.toml create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135802_event_emit_package_pruning_index/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135803_event_senders_pruning_index/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135803_event_senders_pruning_index/metadata.toml create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135803_event_senders_pruning_index/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135804_event_struct_instantiation_pruning_index/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135804_event_struct_instantiation_pruning_index/metadata.toml create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135804_event_struct_instantiation_pruning_index/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135805_event_struct_module_pruning_index/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135805_event_struct_module_pruning_index/metadata.toml create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135805_event_struct_module_pruning_index/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135806_event_struct_name_pruning_index/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135806_event_struct_name_pruning_index/metadata.toml create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135806_event_struct_name_pruning_index/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135807_event_struct_package_pruning_index/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135807_event_struct_package_pruning_index/metadata.toml create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135807_event_struct_package_pruning_index/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135808_tx_calls_fun_pruning_index/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135808_tx_calls_fun_pruning_index/metadata.toml create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135808_tx_calls_fun_pruning_index/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135809_tx_calls_mod_pruning_index/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135809_tx_calls_mod_pruning_index/metadata.toml create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135809_tx_calls_mod_pruning_index/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135810_tx_calls_pkg_pruning_index/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135810_tx_calls_pkg_pruning_index/metadata.toml create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135810_tx_calls_pkg_pruning_index/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135811_tx_changed_objects_pruning_index/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135811_tx_changed_objects_pruning_index/metadata.toml create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135811_tx_changed_objects_pruning_index/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135812_tx_digests_pruning_index/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135812_tx_digests_pruning_index/metadata.toml create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135812_tx_digests_pruning_index/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135813_tx_input_objects_pruning_index/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135813_tx_input_objects_pruning_index/metadata.toml create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135813_tx_input_objects_pruning_index/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135814_tx_kinds_pruning_index/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135814_tx_kinds_pruning_index/metadata.toml create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135814_tx_kinds_pruning_index/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135815_tx_recipients_pruning_index/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135815_tx_recipients_pruning_index/metadata.toml create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135815_tx_recipients_pruning_index/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135816_tx_senders_pruning_index/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135816_tx_senders_pruning_index/metadata.toml create mode 100644 crates/sui-indexer/migrations/pg/2024-09-25-135816_tx_senders_pruning_index/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-30-153705_add_event_sender/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-09-30-153705_add_event_sender/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-10-08-025030_partial_index_instead/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-10-08-025030_partial_index_instead/up.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-10-09-180628_add_network_total_transactions_to_epochs/down.sql create mode 100644 crates/sui-indexer/migrations/pg/2024-10-09-180628_add_network_total_transactions_to_epochs/up.sql create mode 100644 crates/sui-indexer/src/backfill/backfill_instances/ingestion_backfills/ingestion_backfill_task.rs create mode 100644 crates/sui-indexer/src/backfill/backfill_instances/ingestion_backfills/mod.rs create mode 100644 crates/sui-indexer/src/backfill/backfill_instances/ingestion_backfills/raw_checkpoints.rs create mode 100644 crates/sui-indexer/src/backfill/backfill_instances/ingestion_backfills/tx_affected_objects.rs create mode 100644 crates/sui-indexer/src/backfill/backfill_instances/mod.rs create mode 100644 crates/sui-indexer/src/backfill/backfill_instances/sql_backfill.rs create mode 100644 crates/sui-indexer/src/backfill/backfill_instances/sql_backfills/event_sender.sh create mode 100644 crates/sui-indexer/src/backfill/backfill_instances/sql_backfills/full_objects_history.sh create mode 100644 crates/sui-indexer/src/backfill/backfill_instances/sql_backfills/tx_affected_addresses.sh create mode 100644 crates/sui-indexer/src/backfill/backfill_instances/system_state_summary_json.rs create mode 100644 crates/sui-indexer/src/backfill/backfill_runner.rs create mode 100644 crates/sui-indexer/src/backfill/backfill_task.rs create mode 100644 crates/sui-indexer/src/backfill/mod.rs create mode 100644 crates/sui-indexer/src/handlers/objects_snapshot_handler.rs delete mode 100644 crates/sui-indexer/src/handlers/objects_snapshot_processor.rs create mode 100644 crates/sui-indexer/src/models/raw_checkpoints.rs create mode 100644 crates/sui-indexer/src/models/watermarks.rs create mode 100644 crates/sui-indexer/tests/read_api_tests.rs create mode 100644 crates/sui-json-rpc-api/src/deepbook.rs delete mode 100644 crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__cross_module_enum.snap rename crates/sui-package-resolver/src/snapshots/{sui_package_resolver__tests__cross_module.snap => sui_package_resolver__tests__cross_module_layout.snap} (100%) delete mode 100644 crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__cross_package_enum.snap rename crates/sui-package-resolver/src/snapshots/{sui_package_resolver__tests__cross_package.snap => sui_package_resolver__tests__cross_package_layout.snap} (100%) rename crates/sui-package-resolver/src/snapshots/{sui_package_resolver__tests__multiple_linkage_contexts.snap => sui_package_resolver__tests__multiple_linkage_contexts_layout.snap} (100%) rename crates/sui-package-resolver/src/snapshots/{sui_package_resolver__tests__relinking.snap => sui_package_resolver__tests__relinking_layout.snap} (100%) rename crates/sui-package-resolver/src/snapshots/{sui_package_resolver__tests__simple_type.snap => sui_package_resolver__tests__simple_type_layout.snap} (89%) delete mode 100644 crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__upgraded_package_enum.snap rename crates/sui-package-resolver/src/snapshots/{sui_package_resolver__tests__upgraded_package.snap => sui_package_resolver__tests__upgraded_package_layout.snap} (100%) rename crates/sui-package-resolver/src/snapshots/{sui_package_resolver__tests__upgraded_package_non_defining_id.snap => sui_package_resolver__tests__upgraded_package_non_defining_id_layout.snap} (100%) rename crates/sui-package-resolver/src/snapshots/{sui_package_resolver__tests__value_nesting_boundary.snap => sui_package_resolver__tests__value_nesting_boundary_layout.snap} (100%) create mode 100644 crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_61.snap create mode 100644 crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_62.snap create mode 100644 crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_63.snap create mode 100644 crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_64.snap create mode 100644 crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_65.snap create mode 100644 crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_61.snap create mode 100644 crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_62.snap create mode 100644 crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_63.snap create mode 100644 crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_64.snap create mode 100644 crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_65.snap create mode 100644 crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_61.snap create mode 100644 crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_62.snap create mode 100644 crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_63.snap create mode 100644 crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_64.snap create mode 100644 crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_65.snap create mode 100644 crates/sui-rest-api/src/transactions/resolve/literal.rs create mode 100644 crates/sui-rest-api/src/transactions/resolve/mod.rs create mode 100644 crates/sui-rosetta/src/unit_tests/lib_tests.rs create mode 100644 crates/sui-rosetta/tests/custom_coins/test_coin_no_symbol/Move.toml create mode 100644 crates/sui-rosetta/tests/custom_coins/test_coin_no_symbol/sources/test_coin.move create mode 100644 crates/sui-types/src/dynamic_field/visitor.rs rename crates/sui-types/src/{sui_sdk2_conversions.rs => sui_sdk_types_conversions.rs} (99%) create mode 100644 crates/sui/src/unit_tests/fixtures/upgrade_errors/all_v1/Move.toml create mode 100644 crates/sui/src/unit_tests/fixtures/upgrade_errors/all_v1/sources/UpgradeErrors.move create mode 100644 crates/sui/src/unit_tests/fixtures/upgrade_errors/all_v2/Move.toml create mode 100644 crates/sui/src/unit_tests/fixtures/upgrade_errors/all_v2/sources/UpgradeErrors.move create mode 100644 crates/sui/src/unit_tests/fixtures/upgrade_errors/entry_linking_v1/Move.toml create mode 100644 crates/sui/src/unit_tests/fixtures/upgrade_errors/entry_linking_v1/sources/UpgradeErrors.move create mode 100644 crates/sui/src/unit_tests/fixtures/upgrade_errors/entry_linking_v2/Move.toml create mode 100644 crates/sui/src/unit_tests/fixtures/upgrade_errors/entry_linking_v2/sources/UpgradeErrors.move create mode 100644 crates/sui/src/unit_tests/fixtures/upgrade_errors/friend_linking_v1/Move.toml create mode 100644 crates/sui/src/unit_tests/fixtures/upgrade_errors/friend_linking_v1/sources/UpgradeErrors.move create mode 100644 crates/sui/src/unit_tests/fixtures/upgrade_errors/friend_linking_v2/Move.toml create mode 100644 crates/sui/src/unit_tests/fixtures/upgrade_errors/friend_linking_v2/sources/UpgradeErrors.move create mode 100644 crates/sui/src/unit_tests/fixtures/upgrade_errors/struct_missing_v1/Move.toml create mode 100644 crates/sui/src/unit_tests/fixtures/upgrade_errors/struct_missing_v1/sources/UpgradeErrors.move create mode 100644 crates/sui/src/unit_tests/fixtures/upgrade_errors/struct_missing_v2/Move.toml create mode 100644 crates/sui/src/unit_tests/fixtures/upgrade_errors/struct_missing_v2/sources/UpgradeErrors.move create mode 100644 crates/sui/src/unit_tests/snapshots/sui__upgrade_compatibility__upgrade_compatibility_tests__all_fail.snap create mode 100644 crates/sui/src/unit_tests/snapshots/sui__upgrade_compatibility__upgrade_compatibility_tests__struct_missing.snap create mode 100644 crates/sui/src/unit_tests/upgrade_compatibility_tests.rs create mode 100644 crates/sui/src/upgrade_compatibility.rs create mode 100644 crates/suins-indexer/README.md create mode 100644 crates/suiop-cli/src/cli/incidents/notion.rs create mode 100644 crates/suiop-cli/src/cli/incidents/user.rs create mode 100644 crates/suiop-cli/src/cli/notion/ids.rs create mode 100644 crates/suiop-cli/src/cli/notion/mod.rs create mode 100644 crates/suiop-cli/src/cli/notion/models/block.rs create mode 100644 crates/suiop-cli/src/cli/notion/models/block/tests.rs create mode 100644 crates/suiop-cli/src/cli/notion/models/block/tests/callout.json create mode 100644 crates/suiop-cli/src/cli/notion/models/block/tests/emoji_object.json create mode 100644 crates/suiop-cli/src/cli/notion/models/block/tests/external_file_object.json create mode 100644 crates/suiop-cli/src/cli/notion/models/block/tests/file_object.json create mode 100644 crates/suiop-cli/src/cli/notion/models/block/tests/heading_1.json create mode 100644 crates/suiop-cli/src/cli/notion/models/error.rs create mode 100644 crates/suiop-cli/src/cli/notion/models/mod.rs create mode 100644 crates/suiop-cli/src/cli/notion/models/paging.rs create mode 100644 crates/suiop-cli/src/cli/notion/models/properties.rs create mode 100644 crates/suiop-cli/src/cli/notion/models/properties/formulas.rs create mode 100644 crates/suiop-cli/src/cli/notion/models/properties/tests.rs create mode 100644 crates/suiop-cli/src/cli/notion/models/properties/tests/date_property.json create mode 100644 crates/suiop-cli/src/cli/notion/models/properties/tests/formula_date_value.json create mode 100644 crates/suiop-cli/src/cli/notion/models/properties/tests/formula_number_value.json create mode 100644 crates/suiop-cli/src/cli/notion/models/properties/tests/null_select_property.json create mode 100644 crates/suiop-cli/src/cli/notion/models/properties/tests/rollup_property.json create mode 100644 crates/suiop-cli/src/cli/notion/models/properties/tests/select_property.json create mode 100644 crates/suiop-cli/src/cli/notion/models/properties/tests/text_with_link.json create mode 100644 crates/suiop-cli/src/cli/notion/models/search.rs create mode 100644 crates/suiop-cli/src/cli/notion/models/tests.rs create mode 100644 crates/suiop-cli/src/cli/notion/models/tests/error.json create mode 100644 crates/suiop-cli/src/cli/notion/models/tests/issue_15.json create mode 100644 crates/suiop-cli/src/cli/notion/models/tests/page.json create mode 100644 crates/suiop-cli/src/cli/notion/models/tests/query_result.json create mode 100644 crates/suiop-cli/src/cli/notion/models/tests/rich_text_mention_date.json create mode 100644 crates/suiop-cli/src/cli/notion/models/tests/rich_text_mention_date_with_end.json create mode 100644 crates/suiop-cli/src/cli/notion/models/tests/rich_text_mention_date_with_end_and_time.json create mode 100644 crates/suiop-cli/src/cli/notion/models/tests/rich_text_mention_date_with_time.json create mode 100644 crates/suiop-cli/src/cli/notion/models/tests/rich_text_mention_user_person.json create mode 100644 crates/suiop-cli/src/cli/notion/models/tests/rich_text_text.json create mode 100644 crates/suiop-cli/src/cli/notion/models/tests/search_results.json create mode 100644 crates/suiop-cli/src/cli/notion/models/tests/unknown_error.json create mode 100644 crates/suiop-cli/src/cli/notion/models/text.rs create mode 100644 crates/suiop-cli/src/cli/notion/models/users.rs create mode 100644 crates/suiop-cli/src/cli/notion/properties.rs create mode 100644 docker/sui-graphql-rpc-staging/Dockerfile create mode 100644 docker/sui-proxy/Dockerfile delete mode 100644 docs/content/concepts/events.mdx create mode 100644 docs/content/guides/developer/app-examples/images/styles.png create mode 100644 docs/content/guides/developer/app-examples/images/trustless-accept-escrow.png create mode 100644 docs/content/guides/developer/app-examples/images/trustless-cancel-escrow.png create mode 100644 docs/content/guides/developer/app-examples/images/trustless-escrow-locked.png create mode 100644 docs/content/guides/developer/app-examples/images/trustless-lock-bear.png create mode 100644 docs/content/guides/developer/app-examples/images/trustless-my-locked.png create mode 100644 docs/content/guides/developer/app-examples/images/trustless-new-bear.png create mode 100644 docs/content/guides/developer/app-examples/images/trustless-objects.png create mode 100644 docs/content/guides/developer/app-examples/images/trustless-start-escrow.png create mode 100644 docs/content/guides/developer/app-examples/images/trustless-unlock-bear.png delete mode 100644 docs/content/guides/developer/app-examples/trusted-swap.mdx delete mode 100644 docs/content/guides/developer/app-examples/trustless-swap/backend.mdx delete mode 100644 docs/content/guides/developer/app-examples/trustless-swap/frontend.mdx delete mode 100644 docs/content/guides/developer/app-examples/trustless-swap/indexer-api.mdx create mode 100644 docs/content/guides/developer/images/stablecoinsuccess.png create mode 100644 docs/content/guides/developer/images/stablecoinui.png create mode 100644 docs/content/guides/developer/stablecoins.mdx create mode 100644 docs/content/guides/operator/bridge-node-configuration.mdx create mode 100644 docs/content/guides/operator/monitoring.mdx create mode 100644 docs/content/guides/operator/updates.mdx create mode 100644 docs/site/src/components/EffortBox/index.tsx create mode 100644 docs/site/src/plugins/effort/index.js create mode 100644 docs/site/src/theme/Admonition/Types.js create mode 100644 docs/site/src/theme/CodeBlock/Content/String.js create mode 100644 docs/site/src/theme/CodeBlock/Content/styles.module.css create mode 100644 docs/site/src/theme/MDXComponents/Details.js create mode 100644 examples/trading/frontend/src/networkConfig.ts create mode 100644 examples/usdc-transfer-app/App.js create mode 100644 examples/usdc-transfer-app/favicon.ico create mode 100644 examples/usdc-transfer-app/global.css create mode 100644 examples/usdc-transfer-app/index.html create mode 100644 examples/usdc-transfer-app/index.js create mode 100644 examples/usdc-transfer-app/lib/App-stub.js create mode 100644 examples/usdc-transfer-app/package.json create mode 100644 external-crates/move/crates/move-binary-format/src/compatibility_mode.rs create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/Move.toml create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/NO_TEMPDIR create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/args.exp create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/args.txt create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__calls__test_call_order.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__calls__test_call_return_order.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__calls__test_complex_nested_calls.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__calls__test_return_order.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__aborter.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__bad_cast.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__div_0.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__fail_during_abort.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__overshift_l.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__overshift_r.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__underflow.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__natives__get_orig_type_name_test.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__natives__get_type_name_test.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__packs__test_gen_pack_order.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__packs__test_gen_unpack_order.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__packs__test_pack_order.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__packs__test_unpack_order.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__nested_struct_reference_mutation.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__pass_mut_assign_in_other_fn.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__test_struct_borrow.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__test_vector_mut_borrow.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__test_vector_mut_borrow_pop.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__calls__test_call_order.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__calls__test_call_return_order.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__calls__test_complex_nested_calls.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__calls__test_return_order.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__aborter.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__bad_cast.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__div_0.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__fail_during_abort.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__overshift_l.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__overshift_r.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__underflow.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__natives__get_orig_type_name_test.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__natives__get_type_name_test.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__packs__test_gen_pack_order.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__packs__test_gen_unpack_order.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__packs__test_pack_order.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__packs__test_unpack_order.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__nested_struct_reference_mutation.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__pass_mut_assign_in_other_fn.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__test_struct_borrow.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__test_vector_mut_borrow.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__test_vector_mut_borrow_pop.json create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/calls.move create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/errors.move create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/natives.move create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/references.move create mode 100644 external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/structs.move create mode 100644 external-crates/move/crates/move-cli/tests/tracing_testsuite.rs create mode 100644 external-crates/move/crates/move-compiler/src/expansion/name_validation.rs create mode 100644 external-crates/move/crates/move-compiler/src/linters/redundant_ref_deref.rs create mode 100644 external-crates/move/crates/move-compiler/src/linters/self_assignment.rs create mode 100644 external-crates/move/crates/move-compiler/tests/linter/correct_redundant_ref_deref.move create mode 100644 external-crates/move/crates/move-compiler/tests/linter/false_negative_self_assigment.move create mode 100644 external-crates/move/crates/move-compiler/tests/linter/incorrect_redundant_ref_deref.exp create mode 100644 external-crates/move/crates/move-compiler/tests/linter/incorrect_redundant_ref_deref.move create mode 100644 external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_complex.exp create mode 100644 external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_complex.move create mode 100644 external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_conditional.exp create mode 100644 external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_conditional.move create mode 100644 external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_conditional_valid.move create mode 100644 external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_fields.exp create mode 100644 external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_fields.move create mode 100644 external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_fields_valid.move create mode 100644 external-crates/move/crates/move-compiler/tests/linter/redundant_ref_deref.exp create mode 100644 external-crates/move/crates/move-compiler/tests/linter/redundant_ref_deref.move create mode 100644 external-crates/move/crates/move-compiler/tests/linter/ref_deref_negative.move create mode 100644 external-crates/move/crates/move-compiler/tests/linter/ref_deref_path.exp create mode 100644 external-crates/move/crates/move-compiler/tests/linter/ref_deref_path.move create mode 100644 external-crates/move/crates/move-compiler/tests/linter/ref_deref_triple.exp create mode 100644 external-crates/move/crates/move-compiler/tests/linter/ref_deref_triple.move create mode 100644 external-crates/move/crates/move-compiler/tests/linter/suppress_self_assignment.move create mode 100644 external-crates/move/crates/move-compiler/tests/linter/suppressed_case_redundant_ref_deref.move create mode 100644 external-crates/move/crates/move-compiler/tests/linter/true_negative_self_assignment.move create mode 100644 external-crates/move/crates/move-compiler/tests/linter/true_positive_self_assignment.exp create mode 100644 external-crates/move/crates/move-compiler/tests/linter/true_positive_self_assignment.move create mode 100644 external-crates/move/crates/move-docgen/tests/sources/const_string_test.move create mode 100644 external-crates/move/crates/move-docgen/tests/sources/const_string_test.spec_inline.md create mode 100644 external-crates/move/crates/move-docgen/tests/sources/const_string_test.spec_inline_no_fold.md create mode 100644 external-crates/move/crates/move-docgen/tests/sources/const_string_test.spec_separate.md create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/Move.locked create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/Move.progress create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/Move.resolved create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/Move.toml create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/deps_only/bar/Move.toml create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/deps_only/bar/sources/bar.move create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/deps_only/foo/Move.toml create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/deps_only/foo/sources/foo.move create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/sources/root.move create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_resolver_config/Move.locked create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_resolver_config/Move.resolved create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_resolver_config/Move.toml create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_resolver_config/sources/Root.move create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver/Move.resolved create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver/Move.toml create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver/sources/Root.move create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver_invalid_target/Move.resolved create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver_invalid_target/Move.toml create mode 100644 external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver_invalid_target/sources/Root.move delete mode 100644 external-crates/move/crates/move-package/tests/test_sources/package_hooks_subdir/Move.resolved delete mode 100644 external-crates/move/crates/move-package/tests/test_sources/package_hooks_subdir/Move.toml create mode 100755 external-crates/move/crates/move-package/tests/test_sources/resolvers/successful_package_batch_response.sh create mode 100644 external-crates/move/crates/move-trace-format/Cargo.toml create mode 100644 external-crates/move/crates/move-trace-format/src/format.rs create mode 100644 external-crates/move/crates/move-trace-format/src/interface.rs create mode 100644 external-crates/move/crates/move-trace-format/src/lib.rs create mode 100644 external-crates/move/crates/move-trace-format/src/memory_tracer.rs create mode 100644 external-crates/move/crates/move-vm-runtime/src/tracing2/mod.rs create mode 100644 external-crates/move/crates/move-vm-runtime/src/tracing2/tracer.rs delete mode 100644 narwhal/network/src/connectivity.rs delete mode 100644 sdk/typescript/src/graphql/generated/2024-01/tada-env.d.ts create mode 100644 sdk/typescript/src/utils/dynamic-fields.ts create mode 100644 sdk/typescript/test/unit/utils/dynamic-fields.test.ts diff --git a/.cargo/config.toml b/.cargo/config.toml index a342267d276d7..ef57d8c36bc66 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -30,3 +30,6 @@ move-clippy = [ [build] rustflags = ["-C", "force-frame-pointers=yes", "-C", "force-unwind-tables=yes"] + +[net] +git-fetch-with-cli = true \ No newline at end of file diff --git a/.config/nextest.toml b/.config/nextest.toml index 757ed7769622b..2aca4b60c3e55 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -6,7 +6,7 @@ status-level = "skip" # Do not cancel the test run on the first failure. fail-fast = false # Retry failing tests in order to not block builds on flaky tests -retries = 5 +retries = 3 # Timeout tests after 4 minutes slow-timeout = { period = "60s", terminate-after = 5 } diff --git a/.github/actions/diffs/action.yml b/.github/actions/diffs/action.yml index 29cbdcc4b6ea5..37f2ae16d5bfb 100644 --- a/.github/actions/diffs/action.yml +++ b/.github/actions/diffs/action.yml @@ -22,13 +22,16 @@ outputs: isMoveAutoFormatter: description: True when changes happened in MoveAutoFormatter code value: "${{ steps.diff.outputs.isMoveAutoFormatter }}" + isExamples: + description: True when changes happened in examples/ directory + value: "${{ steps.diff.outputs.isExamples }}" runs: using: composite steps: - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # Pin v4.1.1 - name: Detect Changes - uses: dorny/paths-filter@v2.10.2 + uses: dorny/paths-filter@v3 id: diff with: filters: | @@ -76,3 +79,5 @@ runs: - 'sui-execution/**' isMoveAutoFormatter: - 'external-crates/move/crates/move-analyzer/prettier-plugin/**' + isExamples: + - 'examples/**' diff --git a/.github/actions/ts-e2e/action.yml b/.github/actions/ts-e2e/action.yml index 248180eb31e98..c22d5480b19cd 100644 --- a/.github/actions/ts-e2e/action.yml +++ b/.github/actions/ts-e2e/action.yml @@ -28,12 +28,7 @@ runs: - name: cargo build if: env.s3_file_exist == '' # if empty, we have not built and uploaded this binary to s3 yet - run: | - if [[ "${{ inputs.ref }}" == 'devnet' || "${{ inputs.ref }}" == 'testnet' ]]; then - cargo build --bin sui --features indexer - else - cargo build --bin sui - fi + run: cargo build --bin sui shell: bash - name: Dowload from S3 @@ -66,5 +61,10 @@ runs: shell: bash - name: Run TS SDK e2e tests - run: pnpm dlx concurrently --kill-others --success command-1 "$E2E_RUN_LOCAL_NET_CMD" 'pnpm --filter @mysten/sui --filter @mysten/graphql-transport test:e2e' + run: pnpm dlx concurrently --kill-others --success command-1 "$E2E_RUN_LOCAL_NET_CMD" 'pnpm --filter @mysten/sui test:e2e' + shell: bash + + - name: Run TS SDK GraphQL compatibility e2e tests + if: (!contains(fromJSON('["testnet", "devnet"]'), inputs.ref)) + run: pnpm dlx concurrently --kill-others --success command-1 "$E2E_RUN_LOCAL_NET_CMD" 'pnpm --filter @mysten/graphql-transport test:e2e' shell: bash diff --git a/.github/workflows/release-notes-generator.yml b/.github/workflows/release-notes-generator.yml index 1dcedc7eaa3c2..b1f74473e4fa2 100644 --- a/.github/workflows/release-notes-generator.yml +++ b/.github/workflows/release-notes-generator.yml @@ -63,7 +63,7 @@ jobs: - name: Create Release uses: actions/create-release@v1 env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token + GITHUB_TOKEN: ${{ secrets.SUI_CREATE_RELEASE }} with: tag_name: ${{ inputs.release_tag }} release_name: ${{ inputs.release_tag }} diff --git a/.github/workflows/split-cluster-bisect.yml b/.github/workflows/split-cluster-bisect.yml new file mode 100644 index 0000000000000..c06fbefbef98a --- /dev/null +++ b/.github/workflows/split-cluster-bisect.yml @@ -0,0 +1,53 @@ +name: Split Cluster Check +on: + push: + branches: + - main +jobs: + validate-mainnet: + runs-on: ubuntu-ghcloud + steps: + - name: checkout code repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # Pin v4.1.1 + with: + fetch-depth: 0 + - name: Run split cluster check script + id: mn-split-cluster-check + continue-on-error: true # if failure, continue to bisect + run: | + SUI_PROTOCOL_CONFIG_CHAIN_OVERRIDE=mainnet \ + scripts/compatibility/split-cluster-check.sh origin/mainnet ${{ github.sha }} + - name: Bisect + if: steps.mn-split-cluster-check.outcome == 'failure' && github.event_name == 'push' + run: | + git bisect start ${{ github.event.pull_request.head.sha }} origin/mainnet + SUI_PROTOCOL_CONFIG_CHAIN_OVERRIDE=mainnet \ + git bisect run scripts/split-cluster-check.sh origin/mainnet ${{ github.sha }} + git bisect reset + - name: Mark Failures + if: failure() + run: exit 1 + + validate-testnet: + runs-on: ubuntu-ghcloud + steps: + - name: checkout code repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # Pin v4.1.1 + with: + fetch-depth: 0 + - name: Run split cluster check script + id: tn-split-cluster-check + continue-on-error: true # if failure, continue to bisect + run: | + SUI_PROTOCOL_CONFIG_CHAIN_OVERRIDE=testnet \ + scripts/compatibility/split-cluster-check.sh origin/testnet ${{ github.sha }} + - name: Bisect + if: steps.tn-split-cluster-check.outcome == 'failure' && github.event_name == 'push' + run: | + git bisect start ${{ github.event.pull_request.head.sha }} origin/testnet + SUI_PROTOCOL_CONFIG_CHAIN_OVERRIDE=testnet \ + git bisect run scripts/split-cluster-check.sh origin/testnet ${{ github.sha }} + git bisect reset + - name: Mark Failures + if: failure() + run: exit 1 diff --git a/Cargo.lock b/Cargo.lock index 5a35a0e147012..34011f5351fe9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -182,7 +182,7 @@ dependencies = [ "rand 0.8.5", "rcgen", "ring 0.17.8", - "rustls 0.23.13", + "rustls 0.23.15", "rustls-webpki 0.102.8", "serde", "serde_json", @@ -214,10 +214,10 @@ name = "anemo-build" version = "0.0.0" source = "git+https://github.com/mystenlabs/anemo.git?rev=e609f7697ed6169bf0760882a0b6c032a57e4f3b#e609f7697ed6169bf0760882a0b6c032a57e4f3b" dependencies = [ - "prettyplease 0.2.22", - "proc-macro2 1.0.86", + "prettyplease 0.2.25", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -251,7 +251,7 @@ dependencies = [ "tokio", "tower 0.4.13", "tracing", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -311,9 +311,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.89" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" +checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" dependencies = [ "backtrace", ] @@ -420,7 +420,7 @@ checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" dependencies = [ "num-bigint 0.4.6", "num-traits", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -493,7 +493,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -825,7 +825,7 @@ dependencies = [ "arrow-schema 52.2.0", "chrono", "half", - "indexmap 2.5.0", + "indexmap 2.6.0", "lexical-core", "num", "serde", @@ -959,7 +959,7 @@ dependencies = [ "memchr", "num", "regex", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -999,7 +999,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", "synstructure", @@ -1011,7 +1011,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -1067,11 +1067,11 @@ 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 6.0.0", + "brotli 7.0.0", "flate2", "futures-core", "memchr", @@ -1130,7 +1130,7 @@ dependencies = [ "futures-util", "handlebars", "http 1.1.0", - "indexmap 2.5.0", + "indexmap 2.6.0", "lru 0.7.8", "mime", "multer", @@ -1157,7 +1157,7 @@ checksum = "de3415c9dbaf54397292da0bb81a907e2b989661ce068e4ccfebac33dc9e245e" dependencies = [ "async-graphql", "async-trait", - "axum 0.7.6", + "axum 0.7.7", "bytes", "futures-util", "serde_json", @@ -1177,10 +1177,10 @@ dependencies = [ "async-graphql-parser", "darling 0.20.10", "proc-macro-crate 1.1.3", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "strum 0.25.0", - "syn 2.0.77", + "syn 2.0.85", "thiserror", ] @@ -1203,7 +1203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cf4d4e86208f4f9b81a503943c07e6e7f29ad3505e6c9ce6431fe64dc241681" dependencies = [ "bytes", - "indexmap 2.5.0", + "indexmap 2.6.0", "serde", "serde_json", ] @@ -1253,9 +1253,9 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -1287,9 +1287,9 @@ dependencies = [ [[package]] name = "async-stream" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ "async-stream-impl", "futures-core", @@ -1298,13 +1298,13 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -1324,9 +1324,9 @@ version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -1397,9 +1397,9 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c87f3f15e7794432337fc718554eaa4dc8f04c9677a950ffe366f20a162ae42" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -1410,9 +1410,9 @@ checksum = "7460f7dd8e100147b82a63afca1a20eb6c231ee36b90ba7272e14951cb58af59" [[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 = "aws-config" @@ -1435,7 +1435,7 @@ dependencies = [ "fastrand", "hex", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.31", "ring 0.16.20", "time", "tokio", @@ -1496,7 +1496,7 @@ dependencies = [ "http 0.2.12", "percent-encoding", "tracing", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -1698,7 +1698,7 @@ dependencies = [ "fastrand", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.30", + "hyper 0.14.31", "hyper-rustls 0.24.2", "lazy_static", "pin-project-lite", @@ -1732,7 +1732,7 @@ dependencies = [ "futures-core", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.30", + "hyper 0.14.31", "once_cell", "percent-encoding", "pin-project-lite", @@ -1866,7 +1866,7 @@ dependencies = [ "futures-util", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.30", + "hyper 0.14.31", "itoa", "matchit 0.7.3", "memchr", @@ -1883,19 +1883,20 @@ dependencies = [ [[package]] name = "axum" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f43644eed690f5374f1af436ecd6aea01cd201f6fbdf0178adaf6907afb2cec" +checksum = "504e3947307ac8326a5437504c517c4b56716c9d98fac0028c2acc7ca47d70ae" dependencies = [ "async-trait", - "axum-core 0.4.4", - "base64 0.21.7", + "axum-core 0.4.5", + "axum-macros", + "base64 0.22.1", "bytes", "futures-util", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-util", "itoa", "matchit 0.7.3", @@ -1911,7 +1912,7 @@ dependencies = [ "sha1", "sync_wrapper 1.0.1", "tokio", - "tokio-tungstenite 0.23.1", + "tokio-tungstenite 0.24.0", "tower 0.5.1", "tower-layer", "tower-service", @@ -1937,9 +1938,9 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6b8ba012a258d63c9adfa28b9ddcf66149da6f986c5b5452e629d5ee64bf00" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" dependencies = [ "async-trait", "bytes", @@ -1962,8 +1963,8 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73c3220b188aea709cf1b6c5f9b01c3bd936bb08bd2b5184a12b35ac8131b1f9" dependencies = [ - "axum 0.7.6", - "axum-core 0.4.4", + "axum 0.7.7", + "axum-core 0.4.5", "bytes", "futures-util", "headers", @@ -1979,6 +1980,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +dependencies = [ + "proc-macro2 1.0.89", + "quote 1.0.37", + "syn 2.0.85", +] + [[package]] name = "axum-server" version = "0.6.1" @@ -1990,11 +2002,11 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-util", "pin-project-lite", - "rustls 0.23.13", - "rustls-pemfile 2.1.3", + "rustls 0.23.15", + "rustls-pemfile 2.2.0", "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", @@ -2109,9 +2121,9 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bb8" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b10cf871f3ff2ce56432fddc2615ac7acc3aa22ca321f8fea800846fbb32f188" +checksum = "d89aabfae550a5c44b43ab941844ffcd2e993cb6900b342debf59e9ea74acdb8" dependencies = [ "async-trait", "futures-util", @@ -2119,6 +2131,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "bcder" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627747a6774aab38beb35990d88309481378558875a41da1a4b2e373c906ef0" +dependencies = [ + "bytes", + "smallvec", +] + [[package]] name = "bcrypt-pbkdf" version = "0.10.0" @@ -2194,7 +2216,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3deeecb812ca5300b7d3f66f730cc2ebd3511c3d36c691dd79c165d5b19a26e3" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -2220,7 +2242,7 @@ checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" [[package]] name = "bin-version" -version = "1.34.0" +version = "1.36.1" dependencies = [ "const-str", "git-version", @@ -2247,13 +2269,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", - "prettyplease 0.2.22", - "proc-macro2 1.0.86", + "prettyplease 0.2.25", + "proc-macro2 1.0.89", "quote 1.0.37", "regex", "rustc-hash 1.1.0", "shlex", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -2456,7 +2478,7 @@ dependencies = [ "darling 0.14.4", "maplit", "paste", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -2523,9 +2545,9 @@ dependencies = [ [[package]] name = "bnum" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e31ea183f6ee62ac8b8a8cf7feddd766317adfb13ff469de57ce033efd6a790" +checksum = "50202def95bf36cb7d1d7a7962cea1c36a3f8ad42425e5d2b71d7acb8041b5b8" [[package]] name = "brotli" @@ -2549,6 +2571,17 @@ dependencies = [ "brotli-decompressor 4.0.1", ] +[[package]] +name = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor 4.0.1", +] + [[package]] name = "brotli-decompressor" version = "2.5.1" @@ -2595,7 +2628,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", - "regex-automata 0.4.7", + "regex-automata 0.4.8", "serde", ] @@ -2619,9 +2652,9 @@ checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" [[package]] name = "bytemuck" -version = "1.18.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" [[package]] name = "byteorder" @@ -2631,9 +2664,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" dependencies = [ "serde", ] @@ -2650,9 +2683,9 @@ dependencies = [ [[package]] name = "bytes-varint" -version = "1.0.3" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c1820c7c366b9d26c47143e1604454105a59969aade54e4f695d96acc8332f" +checksum = "2e95ba58c659da9789048773ebecf5bd17fe7d3858b10f693bb579912f0d55ed" dependencies = [ "bytes", ] @@ -2705,7 +2738,7 @@ checksum = "e10ca87c81aaa3a949dbbe2b5e6c2c45dbc94ba4897e45ea31ff9ec5087be3dc" dependencies = [ "cached_proc_macro_types", "darling 0.14.4", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -2862,9 +2895,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.21" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07b1695e2c7e8fc85310cde85aeaab7e3097f593c91d209d3f9df76c928100f0" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "jobserver", "libc", @@ -2994,9 +3027,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.18" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0956a43b323ac1afaffc053ed5c4b7c1f1800bacd1683c353aabbb752515dd3" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -3004,15 +3037,15 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.18" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d72166dd41634086d5803a47eb71ae740e61d84709c36f3c34110173db3961b" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim 0.11.1", - "terminal_size", + "terminal_size 0.4.0", ] [[package]] @@ -3022,9 +3055,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck 0.5.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -3229,7 +3262,7 @@ dependencies = [ "fastcrypto", "futures", "http 1.1.0", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-rustls 0.27.3", "hyper-util", "itertools 0.13.0", @@ -3244,7 +3277,7 @@ dependencies = [ "quinn-proto", "rand 0.8.5", "rstest", - "rustls 0.23.13", + "rustls 0.23.15", "serde", "shared-crypto", "strum_macros 0.24.3", @@ -3259,8 +3292,8 @@ dependencies = [ "tokio-rustls 0.26.0", "tokio-stream", "tokio-util 0.7.12", - "tonic 0.12.2", - "tonic-build 0.12.2", + "tonic 0.12.3", + "tonic-build 0.12.3", "tower 0.4.13", "tower-http", "tracing", @@ -3319,9 +3352,9 @@ dependencies = [ [[package]] name = "const-hex" -version = "1.12.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94fb8a24a26d37e1ffd45343323dc9fe6654ceea44c12f2fcb3d7ac29e610bc6" +checksum = "0121754e84117e65f9d90648ee6aa4882a6e63110307ab73967a4c5e7e69e586" dependencies = [ "cfg-if", "cpufeatures", @@ -3384,7 +3417,7 @@ dependencies = [ "flate2", "futures-util", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.31", "hyperlocal", "log", "mime", @@ -3404,6 +3437,24 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "copy_dir" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "543d1dd138ef086e2ff05e3a48cf9da045da2033d16f8538fd76b86cd49b2ca3" +dependencies = [ + "walkdir", +] + [[package]] name = "core-foundation" version = "0.9.4" @@ -3619,7 +3670,7 @@ dependencies = [ "itertools 0.10.5", "log", "smallvec", - "wasmparser", + "wasmparser 0.121.2", "wasmtime-types", ] @@ -3754,8 +3805,9 @@ checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" dependencies = [ "bitflags 1.3.2", "crossterm_winapi", + "futures-core", "libc", - "mio 0.8.11", + "mio", "parking_lot 0.12.3", "signal-hook", "signal-hook-mio", @@ -3771,7 +3823,7 @@ dependencies = [ "bitflags 1.3.2", "crossterm_winapi", "libc", - "mio 0.8.11", + "mio", "parking_lot 0.12.3", "signal-hook", "signal-hook-mio", @@ -3890,9 +3942,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -3916,7 +3968,7 @@ checksum = "478c02b53607e3f21c374f024c2cfc2154e554905bba478e8e09409f10ce3726" dependencies = [ "cynic-proc-macros", "ref-cast", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "serde_json", "static_assertions", @@ -3934,10 +3986,10 @@ dependencies = [ "darling 0.20.10", "once_cell", "ouroboros 0.18.4", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "strsim 0.10.0", - "syn 2.0.77", + "syn 2.0.85", "thiserror", ] @@ -3947,7 +3999,7 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "718f6cd8c54ae5249fd42b0c86639df0100b8a86eea2e5f1b915cde2e1481453" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "lalrpop-util", "logos", ] @@ -3961,7 +4013,7 @@ dependencies = [ "cynic-codegen", "darling 0.20.10", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -4002,7 +4054,7 @@ checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "strsim 0.10.0", "syn 1.0.109", @@ -4016,7 +4068,7 @@ checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "strsim 0.10.0", "syn 1.0.109", @@ -4030,10 +4082,10 @@ checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "strsim 0.11.1", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -4066,7 +4118,7 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core 0.20.10", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -4150,7 +4202,7 @@ dependencies = [ "tokio-stream", "tokio-util 0.7.12", "url", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -4245,7 +4297,7 @@ dependencies = [ "rand 0.8.5", "sha2 0.10.8", "unicode-segmentation", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -4296,7 +4348,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef552e6f588e446098f6ba40d89ac146c8c7b64aade83c051ee00bb5d2bc18d" dependencies = [ - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -4351,7 +4403,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -4362,7 +4414,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e79116f119dd1dba1abf1f3405f03b9b0e79a27a3883864bfebded8a3dc768cd" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -4373,9 +4425,9 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -4394,7 +4446,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c11bdc11a0c47bc7d37d582b5285da6849c96681023680b906673c5707af7b0f" dependencies = [ "darling 0.14.4", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -4415,11 +4467,31 @@ version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ - "convert_case", - "proc-macro2 1.0.86", + "convert_case 0.4.0", + "proc-macro2 1.0.89", "quote 1.0.37", "rustc_version", - "syn 2.0.77", + "syn 2.0.85", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2 1.0.89", + "quote 1.0.37", + "syn 2.0.85", ] [[package]] @@ -4476,9 +4548,9 @@ checksum = "e7f2c3de51e2ba6bf2a648285696137aaf0f5f487bcbea93972fe8a364e131a4" dependencies = [ "diesel_table_macro_syntax", "dsl_auto_type", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -4498,7 +4570,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" dependencies = [ - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -4645,9 +4717,9 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -4677,7 +4749,7 @@ dependencies = [ "docker-api-stubs", "futures-util", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.31", "log", "paste", "serde", @@ -4699,6 +4771,30 @@ dependencies = [ "serde_with 2.3.3", ] +[[package]] +name = "documented" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feadfed35b96a5634e08fc503677ded669549ae2cf7f0b01d5964f09d95487fd" +dependencies = [ + "documented-macros", + "phf", + "thiserror", +] + +[[package]] +name = "documented-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "973659d4a62084e32a7f9332509455436d33684b316bb3bc2bb6dcea51a68c63" +dependencies = [ + "convert_case 0.6.0", + "optfield", + "proc-macro2 1.0.89", + "quote 1.0.37", + "syn 2.0.85", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -4726,9 +4822,9 @@ dependencies = [ "darling 0.20.10", "either", "heck 0.5.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -4905,9 +5001,9 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" [[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", ] @@ -4943,9 +5039,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ "heck 0.5.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -4962,9 +5058,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" dependencies = [ "once_cell", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -5164,14 +5260,14 @@ dependencies = [ "ethers-core", "ethers-etherscan", "eyre", - "prettyplease 0.2.22", - "proc-macro2 1.0.86", + "prettyplease 0.2.25", + "proc-macro2 1.0.89", "quote 1.0.37", "regex", "reqwest 0.11.27", "serde", "serde_json", - "syn 2.0.77", + "syn 2.0.85", "toml 0.8.19", "walkdir", ] @@ -5186,10 +5282,10 @@ dependencies = [ "const-hex", "ethers-contract-abigen", "ethers-core", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "serde_json", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -5215,7 +5311,7 @@ dependencies = [ "serde", "serde_json", "strum 0.26.3", - "syn 2.0.77", + "syn 2.0.85", "tempfile", "thiserror", "tiny-keccak", @@ -5451,7 +5547,7 @@ dependencies = [ [[package]] name = "fastcrypto" version = "0.1.8" -source = "git+https://github.com/MystenLabs/fastcrypto?rev=3366c26a746b72707572c4f3f05915e20d5c16c2#3366c26a746b72707572c4f3f05915e20d5c16c2" +source = "git+https://github.com/MystenLabs/fastcrypto?rev=c050ffc78b93739328af5d59b05f90e0e26b1b7e#c050ffc78b93739328af5d59b05f90e0e26b1b7e" dependencies = [ "aes", "aes-gcm", @@ -5469,7 +5565,7 @@ dependencies = [ "cbc", "ctr", "curve25519-dalek-ng", - "derive_more", + "derive_more 0.99.18", "digest 0.10.7", "ecdsa 0.16.9", "ed25519-consensus", @@ -5491,7 +5587,7 @@ dependencies = [ "secp256k1", "serde", "serde_json", - "serde_with 3.9.0", + "serde_with 3.11.0", "sha2 0.10.8", "sha3 0.10.8", "signature 2.2.0", @@ -5505,7 +5601,7 @@ dependencies = [ [[package]] name = "fastcrypto-derive" version = "0.1.3" -source = "git+https://github.com/MystenLabs/fastcrypto?rev=3366c26a746b72707572c4f3f05915e20d5c16c2#3366c26a746b72707572c4f3f05915e20d5c16c2" +source = "git+https://github.com/MystenLabs/fastcrypto?rev=c050ffc78b93739328af5d59b05f90e0e26b1b7e#c050ffc78b93739328af5d59b05f90e0e26b1b7e" dependencies = [ "quote 1.0.37", "syn 1.0.109", @@ -5514,7 +5610,7 @@ dependencies = [ [[package]] name = "fastcrypto-tbls" version = "0.1.0" -source = "git+https://github.com/MystenLabs/fastcrypto?rev=3366c26a746b72707572c4f3f05915e20d5c16c2#3366c26a746b72707572c4f3f05915e20d5c16c2" +source = "git+https://github.com/MystenLabs/fastcrypto?rev=c050ffc78b93739328af5d59b05f90e0e26b1b7e#c050ffc78b93739328af5d59b05f90e0e26b1b7e" dependencies = [ "bcs", "digest 0.10.7", @@ -5533,7 +5629,7 @@ dependencies = [ [[package]] name = "fastcrypto-vdf" version = "0.1.0" -source = "git+https://github.com/MystenLabs/fastcrypto?rev=3366c26a746b72707572c4f3f05915e20d5c16c2#3366c26a746b72707572c4f3f05915e20d5c16c2" +source = "git+https://github.com/MystenLabs/fastcrypto?rev=c050ffc78b93739328af5d59b05f90e0e26b1b7e#c050ffc78b93739328af5d59b05f90e0e26b1b7e" dependencies = [ "bcs", "fastcrypto", @@ -5550,7 +5646,7 @@ dependencies = [ [[package]] name = "fastcrypto-zkp" version = "0.1.3" -source = "git+https://github.com/MystenLabs/fastcrypto?rev=3366c26a746b72707572c4f3f05915e20d5c16c2#3366c26a746b72707572c4f3f05915e20d5c16c2" +source = "git+https://github.com/MystenLabs/fastcrypto?rev=c050ffc78b93739328af5d59b05f90e0e26b1b7e#c050ffc78b93739328af5d59b05f90e0e26b1b7e" dependencies = [ "ark-bn254", "ark-ec", @@ -5561,7 +5657,7 @@ dependencies = [ "ark-snark", "bcs", "byte-slice-cast", - "derive_more", + "derive_more 0.99.18", "fastcrypto", "ff 0.13.0", "im", @@ -5571,7 +5667,7 @@ dependencies = [ "num-bigint 0.4.6", "once_cell", "regex", - "reqwest 0.12.7", + "reqwest 0.12.8", "schemars", "serde", "serde_json", @@ -5649,7 +5745,7 @@ dependencies = [ "num-bigint 0.3.3", "num-integer", "num-traits", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -5667,7 +5763,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cca4fdab1b9b7e274e7de51202e37f9cfa542b28c77f8d09b817d77a726b4807" dependencies = [ "darling 0.13.4", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -5754,9 +5850,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.33" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide 0.8.0", @@ -5787,6 +5883,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foldhash" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -5852,9 +5954,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", @@ -5867,9 +5969,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", @@ -5877,15 +5979,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", @@ -5894,9 +5996,9 @@ 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-lite" @@ -5923,26 +6025,26 @@ dependencies = [ [[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 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[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-timer" @@ -5956,9 +6058,9 @@ dependencies = [ [[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", @@ -6003,10 +6105,10 @@ dependencies = [ "async-stream", "async-trait", "dyn-clone", - "hyper 0.14.30", + "hyper 0.14.31", "hyper-rustls 0.25.0", "log", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "serde_json", "thiserror", @@ -6070,7 +6172,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" dependencies = [ "fallible-iterator 0.3.0", - "indexmap 2.5.0", + "indexmap 2.6.0", "stable_deref_trait", ] @@ -6089,9 +6191,9 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -6109,8 +6211,8 @@ dependencies = [ "aho-corasick", "bstr", "log", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -6183,9 +6285,9 @@ dependencies = [ [[package]] name = "guppy" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bff2f6a9d515cf6453282af93363f93bdf570792a6f4f619756e46696d773fa" +checksum = "bf47f1dcacf93614a4181e308b8341f5bf5d4f7ae2f67cc5a078df8e7023ea63" dependencies = [ "ahash 0.8.11", "camino", @@ -6195,7 +6297,7 @@ dependencies = [ "fixedbitset 0.4.2", "guppy-summaries", "guppy-workspace-hack", - "indexmap 2.5.0", + "indexmap 2.6.0", "itertools 0.13.0", "nested", "once_cell", @@ -6244,7 +6346,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap 2.5.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util 0.7.12", @@ -6263,7 +6365,7 @@ dependencies = [ "futures-core", "futures-sink", "http 1.1.0", - "indexmap 2.5.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util 0.7.12", @@ -6272,9 +6374,9 @@ dependencies = [ [[package]] name = "hakari" -version = "0.17.4" +version = "0.17.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3db4569d65cb4bc06dbcd78e4dd9771e266e75e27de868fa369e995fbb8c267" +checksum = "b18b4de8a80066ba6f5f84d8124c9afae1c603584f6a85298d71b480f70fc740" dependencies = [ "ahash 0.8.11", "atomicwrites", @@ -6352,6 +6454,17 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "hashers" version = "1.0.1" @@ -6560,9 +6673,9 @@ checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" [[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" @@ -6578,9 +6691,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", @@ -6602,9 +6715,9 @@ dependencies = [ [[package]] name = "hyper" -version = "1.4.1" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", @@ -6628,7 +6741,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" dependencies = [ "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.31", "log", "rustls 0.20.9", "rustls-native-certs 0.6.3", @@ -6645,7 +6758,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.31", "log", "rustls 0.21.12", "rustls-native-certs 0.6.3", @@ -6661,7 +6774,7 @@ checksum = "399c78f9338483cb7e630c8474b07268983c6bd5acee012e4211f9f7bb21b070" dependencies = [ "futures-util", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.31", "log", "rustls 0.22.4", "rustls-native-certs 0.7.3", @@ -6678,9 +6791,9 @@ checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", "http 1.1.0", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-util", - "rustls 0.23.13", + "rustls 0.23.15", "rustls-native-certs 0.8.0", "rustls-pki-types", "tokio", @@ -6695,7 +6808,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.30", + "hyper 0.14.31", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -6707,7 +6820,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" dependencies = [ - "hyper 1.4.1", + "hyper 1.5.0", "hyper-util", "pin-project-lite", "tokio", @@ -6725,7 +6838,7 @@ dependencies = [ "futures-util", "http 1.1.0", "http-body 1.0.1", - "hyper 1.4.1", + "hyper 1.5.0", "pin-project-lite", "socket2 0.5.7", "tokio", @@ -6741,7 +6854,7 @@ checksum = "0fafdf7b2b2de7c9784f76e02c0935e65a8117ec3b768644379983ab333ac98c" dependencies = [ "futures-util", "hex", - "hyper 0.14.30", + "hyper 0.14.31", "pin-project", "tokio", ] @@ -6862,7 +6975,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -6883,7 +6996,7 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", ] @@ -6906,12 +7019,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown 0.14.5", + "hashbrown 0.15.0", "serde", ] @@ -7025,9 +7138,9 @@ checksum = "5a611371471e98973dbcab4e0ec66c31a10bc356eeb4d54a0e05eac8158fe38c" [[package]] name = "ipnet" -version = "2.10.0" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" [[package]] name = "ipnetwork" @@ -7040,9 +7153,9 @@ dependencies = [ [[package]] name = "iri-string" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c25163201be6ded9e686703e85532f8f852ea1f92ba625cb3c51f7fe6d07a4a" +checksum = "dc0f0a572e8ffe56e2ff4f769f32ffe919282c3916799f8b68688b6030063bea" dependencies = [ "memchr", "serde", @@ -7184,9 +7297,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", ] @@ -7265,7 +7378,7 @@ dependencies = [ "futures-timer", "futures-util", "globset", - "hyper 0.14.30", + "hyper 0.14.31", "jsonrpsee-types", "parking_lot 0.12.3", "rand 0.8.5", @@ -7284,7 +7397,7 @@ version = "0.16.2" source = "git+https://github.com/wlmyng/jsonrpsee.git?rev=b1b300784795f6a64d0fcdf8f03081a9bc38bde8#b1b300784795f6a64d0fcdf8f03081a9bc38bde8" dependencies = [ "async-trait", - "hyper 0.14.30", + "hyper 0.14.31", "hyper-rustls 0.23.2", "jsonrpsee-core", "jsonrpsee-types", @@ -7303,7 +7416,7 @@ source = "git+https://github.com/wlmyng/jsonrpsee.git?rev=b1b300784795f6a64d0fcd dependencies = [ "heck 0.4.1", "proc-macro-crate 1.1.3", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -7316,7 +7429,7 @@ dependencies = [ "futures-channel", "futures-util", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.31", "jsonrpsee-core", "jsonrpsee-types", "serde", @@ -7460,7 +7573,7 @@ dependencies = [ "lalrpop-util", "petgraph 0.6.5", "regex", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", "string_cache", "term", "tiny-keccak", @@ -7474,7 +7587,7 @@ version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ - "regex-automata 0.4.7", + "regex-automata 0.4.8", ] [[package]] @@ -7564,9 +7677,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.159" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libloading" @@ -7584,6 +7697,16 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "libquickjs-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f0b24e9bd171b75ae0295bd428fb8fe58410fb23156e5f34a4657a70c3cee96" +dependencies = [ + "cc", + "copy_dir", +] + [[package]] name = "libredox" version = "0.1.3" @@ -7592,7 +7715,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", - "redox_syscall 0.5.5", + "redox_syscall 0.5.7", ] [[package]] @@ -7694,10 +7817,10 @@ dependencies = [ "beef", "fnv", "lazy_static", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "regex-syntax 0.8.4", - "syn 2.0.77", + "regex-syntax 0.8.5", + "syn 2.0.85", ] [[package]] @@ -7729,11 +7852,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.12.4" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37ee39891760e7d94734f6f63fedc29a2e4a152f836120753a72503f09fcf904" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" dependencies = [ - "hashbrown 0.14.5", + "hashbrown 0.15.0", ] [[package]] @@ -7763,18 +7886,18 @@ dependencies = [ [[package]] name = "lz4" -version = "1.27.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a231296ca742e418c43660cb68e082486ff2538e8db432bc818580f3965025ed" +checksum = "4d1febb2b4a79ddd1980eede06a8f7902197960aa0383ffcfdd62fe723036725" dependencies = [ "lz4-sys", ] [[package]] name = "lz4-sys" -version = "1.11.0" +version = "1.11.1+lz4-1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb44a01837a858d47e5a630d2ccf304c8efcc4b83b8f9f75b7a9ee4fcc6e57d" +checksum = "6bd8c0d6c6ed0cd30b3652886bb8711dc4bb01d637a68105a3d5158039b418e6" dependencies = [ "cc", "libc", @@ -7848,7 +7971,7 @@ dependencies = [ "rusqlite", "semver", "serde", - "serde_with 3.9.0", + "serde_with 3.11.0", "thiserror", "tokio", "tracing", @@ -8023,7 +8146,7 @@ dependencies = [ "supports-color", "supports-hyperlinks", "supports-unicode", - "terminal_size", + "terminal_size 0.3.0", "textwrap", "thiserror", "unicode-width", @@ -8035,9 +8158,9 @@ version = "7.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf09caffaac8068c346b6df2a7fc27a177fd20b39421a39ce0a211bde679a6c" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -8057,7 +8180,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffb161cc72176cb37aa47f1fc520d3ef02263d67d661f44f05d05a079e1237fd" dependencies = [ "migrations_internals", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", ] @@ -8113,18 +8236,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "mio" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", -] - [[package]] name = "mockall" version = "0.11.4" @@ -8147,7 +8258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" dependencies = [ "cfg-if", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -8168,7 +8279,7 @@ dependencies = [ "tagptr", "thiserror", "triomphe", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -8213,6 +8324,7 @@ dependencies = [ "lsp-types", "move-command-line-common", "move-compiler", + "move-core-types", "move-ir-types", "move-package", "move-symbol-pool", @@ -8254,6 +8366,7 @@ dependencies = [ "move-ir-types", "move-symbol-pool", "serde", + "serde_json", ] [[package]] @@ -8261,7 +8374,7 @@ name = "move-bytecode-utils" version = "0.1.0" dependencies = [ "anyhow", - "indexmap 2.5.0", + "indexmap 2.6.0", "move-binary-format", "move-core-types", "petgraph 0.5.1", @@ -8455,7 +8568,7 @@ dependencies = [ "ref-cast", "serde", "serde_bytes", - "serde_with 3.9.0", + "serde_with 3.11.0", "thiserror", "uint", ] @@ -8635,7 +8748,7 @@ version = "0.1.0" dependencies = [ "enum-compat-util", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -8761,6 +8874,21 @@ dependencies = [ "serde", ] +[[package]] +name = "move-trace-format" +version = "0.0.1" +dependencies = [ + "anyhow", + "enum-compat-util", + "move-binary-format", + "move-core-types", + "move-proc-macros", + "ref-cast", + "serde", + "serde_json", + "variant_count", +] + [[package]] name = "move-transactional-test-runner" version = "0.1.0" @@ -8812,6 +8940,7 @@ dependencies = [ "move-stdlib", "move-stdlib-natives", "move-symbol-pool", + "move-trace-format", "move-vm-profiler", "move-vm-runtime", "move-vm-test-utils", @@ -8850,6 +8979,7 @@ dependencies = [ "move-binary-format", "move-bytecode-verifier", "move-core-types", + "move-trace-format", "move-vm-config", "move-vm-profiler", "move-vm-types", @@ -8941,7 +9071,7 @@ dependencies = [ [[package]] name = "msim" version = "0.1.0" -source = "git+https://github.com/MystenLabs/mysten-sim.git?rev=b320996d8dfb99b273fe31c0222c659332283c99#b320996d8dfb99b273fe31c0222c659332283c99" +source = "git+https://github.com/MystenLabs/mysten-sim.git?rev=9c6636c399d5c60a1759f1670b1c07b3d408799a#9c6636c399d5c60a1759f1670b1c07b3d408799a" dependencies = [ "ahash 0.7.8", "async-task 4.3.0", @@ -8961,7 +9091,7 @@ dependencies = [ "serde", "socket2 0.4.10", "tap", - "tokio-util 0.7.10", + "tokio-util 0.7.11", "toml 0.5.11", "tracing", "tracing-subscriber", @@ -8970,10 +9100,10 @@ dependencies = [ [[package]] name = "msim-macros" version = "0.1.0" -source = "git+https://github.com/MystenLabs/mysten-sim.git?rev=b320996d8dfb99b273fe31c0222c659332283c99#b320996d8dfb99b273fe31c0222c659332283c99" +source = "git+https://github.com/MystenLabs/mysten-sim.git?rev=9c6636c399d5c60a1759f1670b1c07b3d408799a#9c6636c399d5c60a1759f1670b1c07b3d408799a" dependencies = [ "darling 0.14.4", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -9044,7 +9174,7 @@ checksum = "1d6d4752e6230d8ef7adf7bd5d8c4b1f6561c1014c5ba9a37445ccefe18aa1db" dependencies = [ "proc-macro-crate 1.1.3", "proc-macro-error", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", "synstructure", @@ -9072,7 +9202,7 @@ dependencies = [ "mysten-metrics", "parking_lot 0.12.3", "prometheus", - "reqwest 0.12.7", + "reqwest 0.12.8", "snap", "sui-tls", "sui-types", @@ -9085,7 +9215,7 @@ name = "mysten-metrics" version = "0.7.0" dependencies = [ "async-trait", - "axum 0.7.6", + "axum 0.7.7", "dashmap", "futures", "once_cell", @@ -9097,7 +9227,7 @@ dependencies = [ "tap", "tokio", "tracing", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -9118,7 +9248,7 @@ dependencies = [ "snap", "tokio", "tokio-stream", - "tonic 0.12.2", + "tonic 0.12.3", "tonic-health", "tower 0.4.13", "tower-http", @@ -9130,7 +9260,7 @@ name = "mysten-service" version = "0.0.1" dependencies = [ "anyhow", - "axum 0.7.6", + "axum 0.7.7", "mysten-metrics", "prometheus", "serde", @@ -9152,7 +9282,7 @@ dependencies = [ "fastcrypto-tbls", "hashbrown 0.12.3", "impl-trait-for-tuples", - "indexmap 2.5.0", + "indexmap 2.6.0", "mysten-util-mem-derive", "once_cell", "parking_lot 0.12.3", @@ -9164,7 +9294,7 @@ dependencies = [ name = "mysten-util-mem-derive" version = "0.1.0" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "syn 1.0.109", "synstructure", ] @@ -9235,7 +9365,7 @@ dependencies = [ "bytes", "fastcrypto", "futures", - "indexmap 2.5.0", + "indexmap 2.6.0", "mockall", "mysten-metrics", "narwhal-config", @@ -9253,7 +9383,7 @@ dependencies = [ "tempfile", "thiserror", "tokio", - "tonic 0.12.2", + "tonic 0.12.3", "tracing", "typed-store", ] @@ -9266,7 +9396,7 @@ dependencies = [ "anemo-tower", "anyhow", "async-trait", - "axum 0.7.6", + "axum 0.7.7", "backoff", "bincode", "bytes", @@ -9294,7 +9424,7 @@ dependencies = [ "anemo", "arc-swap", "async-trait", - "axum 0.7.6", + "axum 0.7.7", "bytes", "cfg-if", "clap", @@ -9315,7 +9445,7 @@ dependencies = [ "pretty_assertions", "prometheus", "rand 0.8.5", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde-reflection", "serde_yaml 0.8.26", "sui-keys", @@ -9349,7 +9479,7 @@ dependencies = [ "fastcrypto", "futures", "governor", - "indexmap 2.5.0", + "indexmap 2.6.0", "itertools 0.13.0", "mockall", "mysten-common", @@ -9369,7 +9499,7 @@ dependencies = [ "prometheus", "proptest", "rand 0.8.5", - "reqwest 0.12.7", + "reqwest 0.12.8", "sui-macros", "sui-protocol-config", "tap", @@ -9413,7 +9543,7 @@ dependencies = [ "anemo", "fastcrypto", "fdlimit", - "indexmap 2.5.0", + "indexmap 2.6.0", "itertools 0.13.0", "mysten-metrics", "mysten-network", @@ -9433,7 +9563,7 @@ dependencies = [ "telemetry-subscribers", "tempfile", "tokio", - "tonic 0.12.2", + "tonic 0.12.3", "tracing", "typed-store", ] @@ -9453,7 +9583,7 @@ dependencies = [ "enum_dispatch", "fastcrypto", "futures", - "indexmap 2.5.0", + "indexmap 2.6.0", "mockall", "mysten-common", "mysten-metrics", @@ -9471,12 +9601,12 @@ dependencies = [ "roaring", "serde", "serde_test", - "serde_with 3.9.0", + "serde_with 3.11.0", "sui-protocol-config", "thiserror", "tokio", - "tonic 0.12.2", - "tonic-build 0.12.2", + "tonic 0.12.3", + "tonic-build 0.12.3", "tracing", "typed-store", ] @@ -9509,14 +9639,14 @@ dependencies = [ "narwhal-types", "prometheus", "rand 0.8.5", - "reqwest 0.12.7", + "reqwest 0.12.8", "sui-protocol-config", "tap", "telemetry-subscribers", "tempfile", "thiserror", "tokio", - "tonic 0.12.2", + "tonic 0.12.3", "tower 0.4.13", "tracing", "typed-store", @@ -9683,7 +9813,7 @@ dependencies = [ "kqueue", "libc", "log", - "mio 0.8.11", + "mio", "walkdir", "windows-sys 0.48.0", ] @@ -9713,7 +9843,7 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16d0d3f2a488592e5368ebbe996e7f1d44aa13156efad201f5b4d84e150eaa93" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -9725,7 +9855,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc7c92f190c97f79b4a332f5e81dcf68c8420af2045c936c9be0bc9de6f63b5" dependencies = [ "proc-macro-crate 3.2.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -9824,7 +9954,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -9868,7 +9998,7 @@ checksum = "e238432a7881ec7164503ccc516c014bf009be7984cde1ba56837862543bdec3" dependencies = [ "bitvec 1.0.1", "either", - "lru 0.12.4", + "lru 0.12.5", "num-bigint 0.4.6", "num-integer", "num-modular", @@ -9932,9 +10062,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96667db765a921f7b295ffee8b60472b686a51d4f21c2ee4ffdb94c7013b65a6" dependencies = [ "proc-macro-crate 1.1.3", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -9944,9 +10074,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro-crate 3.2.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -9972,7 +10102,7 @@ checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" dependencies = [ "crc32fast", "hashbrown 0.14.5", - "indexmap 2.5.0", + "indexmap 2.6.0", "memchr", ] @@ -10008,16 +10138,16 @@ dependencies = [ "chrono", "futures", "humantime", - "hyper 1.4.1", + "hyper 1.5.0", "itertools 0.13.0", "md-5 0.10.6", "parking_lot 0.12.3", "percent-encoding", "quick-xml", "rand 0.8.5", - "reqwest 0.12.7", + "reqwest 0.12.8", "ring 0.17.8", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.2.0", "serde", "serde_json", "snafu", @@ -10038,9 +10168,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 = "oorandom" @@ -10085,7 +10215,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" dependencies = [ "bytes", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -10095,7 +10225,7 @@ name = "openapiv3" version = "2.0.0" source = "git+https://github.com/bmwill/openapiv3.git?rev=ca4b4845b7c159a39f5c68ad8f7f76cb6f4d6963#ca4b4845b7c159a39f5c68ad8f7f76cb6f4d6963" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "schemars", "serde", "serde_json", @@ -10109,68 +10239,64 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "opentelemetry" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9591d937bc0e6d2feb6f71a559540ab300ea49955229c347a517a28d27784c54" +checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a" dependencies = [ - "opentelemetry_api", - "opentelemetry_sdk", + "futures-core", + "futures-sink", + "indexmap 2.6.0", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror", + "urlencoding", ] [[package]] name = "opentelemetry" -version = "0.21.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a" +checksum = "803801d3d3b71cd026851a53f974ea03df3d179cb758b260136a6c9e22e196af" dependencies = [ "futures-core", "futures-sink", - "indexmap 2.5.0", "js-sys", "once_cell", "pin-project-lite", "thiserror", - "urlencoding", ] [[package]] name = "opentelemetry-otlp" -version = "0.13.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e5e5a5c4135864099f3faafbe939eb4d7f9b80ebf68a8448da961b32a7c1275" +checksum = "596b1719b3cab83addb20bcbffdf21575279d9436d9ccccfe651a3bf0ab5ab06" dependencies = [ "async-trait", "futures-core", - "http 0.2.12", + "http 1.1.0", + "opentelemetry 0.25.0", "opentelemetry-proto", - "opentelemetry-semantic-conventions", - "opentelemetry_api", "opentelemetry_sdk", - "prost 0.11.9", + "prost 0.13.3", "thiserror", "tokio", - "tonic 0.9.2", + "tonic 0.12.3", ] [[package]] name = "opentelemetry-proto" -version = "0.3.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e3f814aa9f8c905d0ee4bde026afd3b2577a97c10e1699912e3e44f0c4cbeb" +checksum = "2c43620e8f93359eb7e627a3b16ee92d8585774986f24f2ab010817426c5ce61" dependencies = [ - "opentelemetry_api", + "hex", + "opentelemetry 0.25.0", "opentelemetry_sdk", - "prost 0.11.9", - "tonic 0.9.2", -] - -[[package]] -name = "opentelemetry-semantic-conventions" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73c9f9340ad135068800e7f1b24e9e09ed9e7143f5bf8518ded3d3ec69789269" -dependencies = [ - "opentelemetry 0.20.0", + "prost 0.13.3", + "serde", + "tonic 0.12.3", ] [[package]] @@ -10191,27 +10317,36 @@ dependencies = [ [[package]] name = "opentelemetry_sdk" -version = "0.20.0" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8e705a0612d48139799fcbaba0d4a90f06277153e43dd2bdc16c6f0edd8026" +checksum = "e0da0d6b47a3dbc6e9c9e36a0520e25cf943e046843818faaa3f87365a548c82" dependencies = [ "async-trait", - "crossbeam-channel", "futures-channel", "futures-executor", "futures-util", + "glob", "once_cell", - "opentelemetry_api", - "ordered-float 3.9.2", + "opentelemetry 0.25.0", "percent-encoding", "rand 0.8.5", - "regex", "serde_json", "thiserror", "tokio", "tokio-stream", ] +[[package]] +name = "optfield" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa59f025cde9c698fcb4fcb3533db4621795374065bee908215263488f2d2a1d" +dependencies = [ + "proc-macro2 1.0.89", + "quote 1.0.37", + "syn 2.0.85", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -10227,15 +10362,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "ordered-float" -version = "3.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1e1c390732d15f1d48471625cd92d154e66db2c56645e29a9cd26f4699f72dc" -dependencies = [ - "num-traits", -] - [[package]] name = "ouroboros" version = "0.17.2" @@ -10266,9 +10392,9 @@ checksum = "ec4c6225c69b4ca778c0aea097321a64c421cf4577b331c61b229267edabb6f8" dependencies = [ "heck 0.4.1", "proc-macro-error", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -10279,10 +10405,10 @@ checksum = "39b0deead1528fd0e5947a8546a9642a9777c25f6e1e26f34c97b204bbb465bd" dependencies = [ "heck 0.4.1", "itertools 0.12.1", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "proc-macro2-diagnostics", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -10376,7 +10502,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1557010476e0595c9b568d16dcfb81b93cdeb157612726f5170d31aa707bed27" dependencies = [ "proc-macro-crate 1.1.3", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -10388,7 +10514,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" dependencies = [ "proc-macro-crate 3.2.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -10442,7 +10568,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.5.5", + "redox_syscall 0.5.7", "smallvec", "windows-targets 0.52.6", ] @@ -10564,7 +10690,7 @@ dependencies = [ "ciborium", "coset", "data-encoding", - "indexmap 2.5.0", + "indexmap 2.6.0", "rand 0.8.5", "serde", "serde_json", @@ -10615,9 +10741,9 @@ checksum = "1e91099d4268b0e11973f036e885d652fb0b21fedcf69738c627f94db6a44f42" [[package]] name = "pathdiff" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" dependencies = [ "camino", ] @@ -10695,9 +10821,9 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pest" -version = "2.7.13" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", "thiserror", @@ -10706,9 +10832,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.13" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" dependencies = [ "pest", "pest_generator", @@ -10716,22 +10842,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.13" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" dependencies = [ "pest", "pest_meta", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] name = "pest_meta" -version = "2.7.13" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" dependencies = [ "once_cell", "pest", @@ -10755,7 +10881,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset 0.4.2", - "indexmap 2.5.0", + "indexmap 2.6.0", ] [[package]] @@ -10806,9 +10932,9 @@ checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" dependencies = [ "phf_generator", "phf_shared 0.11.2", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -10831,29 +10957,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 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[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" @@ -10989,9 +11115,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d30538d42559de6b034bc76fd6dd4c38961b1ee5c6c56e3808c50128fdbc22ce" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" [[package]] name = "postgres-protocol" @@ -11120,18 +11246,18 @@ version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "syn 1.0.109", ] [[package]] name = "prettyplease" -version = "0.2.22" +version = "0.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" dependencies = [ - "proc-macro2 1.0.86", - "syn 2.0.77", + "proc-macro2 1.0.89", + "syn 2.0.85", ] [[package]] @@ -11209,7 +11335,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", "version_check", @@ -11221,7 +11347,7 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "version_check", ] @@ -11237,9 +11363,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", ] @@ -11250,9 +11376,9 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", "version_check", "yansi 1.0.1", ] @@ -11289,7 +11415,7 @@ checksum = "0fcebfa99f03ae51220778316b37d24981e36322c82c24848f48c5bd0f64cbdb" dependencies = [ "enum-as-inner", "mime", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "time", "url", @@ -11320,7 +11446,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", "rusty-fork", "tempfile", "unarray", @@ -11402,11 +11528,11 @@ dependencies = [ "multimap 0.10.0", "once_cell", "petgraph 0.6.5", - "prettyplease 0.2.22", + "prettyplease 0.2.25", "prost 0.13.3", "prost-types 0.13.3", "regex", - "syn 2.0.77", + "syn 2.0.85", "tempfile", ] @@ -11418,7 +11544,7 @@ checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools 0.10.5", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -11431,9 +11557,9 @@ checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", "itertools 0.12.1", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -11444,9 +11570,9 @@ checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5" dependencies = [ "anyhow", "itertools 0.13.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -11521,6 +11647,16 @@ version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +name = "quick-js" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19cb4cefcb00f4ab9b332664d06005a74f582ac16aa959c6ad5912957bd83e5f" +dependencies = [ + "libquickjs-sys", + "once_cell", +] + [[package]] name = "quick-xml" version = "0.36.2" @@ -11543,7 +11679,7 @@ dependencies = [ "quinn-proto", "quinn-udp", "rustc-hash 2.0.0", - "rustls 0.23.13", + "rustls 0.23.15", "socket2 0.5.7", "thiserror", "tokio", @@ -11560,7 +11696,7 @@ dependencies = [ "rand 0.8.5", "ring 0.17.8", "rustc-hash 2.0.0", - "rustls 0.23.13", + "rustls 0.23.15", "slab", "thiserror", "tinyvec", @@ -11595,7 +11731,7 @@ version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", ] [[package]] @@ -11711,9 +11847,9 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "11.1.0" +version = "11.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb9ee317cfe3fbd54b36a511efc1edd42e216903c9cd575e686dd68a2ba90d8d" +checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0" dependencies = [ "bitflags 2.6.0", ] @@ -11757,26 +11893,26 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a25d631e41bfb5fdcde1d4e2215f62f7f0afa3ff11e26563765bd6ea1d229aeb" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] name = "real_tokio" -version = "1.36.0" -source = "git+https://github.com/mystenmark/tokio-madsim-fork.git?rev=e47aafebf98e9c1734a8848a1876d5946c44bdd1#e47aafebf98e9c1734a8848a1876d5946c44bdd1" +version = "1.38.1" +source = "git+https://github.com/mystenmark/tokio-madsim-fork.git?rev=d46208cb11118c0e6ab5dfea1a2265add36fbc15#d46208cb11118c0e6ab5dfea1a2265add36fbc15" dependencies = [ "backtrace", "bytes", "libc", - "mio 0.8.11", + "mio", "num_cpus", "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", "socket2 0.5.7", - "tokio-macros 2.2.0", + "tokio-macros 2.3.0 (git+https://github.com/mystenmark/tokio-madsim-fork.git?rev=d46208cb11118c0e6ab5dfea1a2265add36fbc15)", "windows-sys 0.48.0", ] @@ -11791,9 +11927,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.5" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62871f2d65009c0256aed1b9cfeeb8ac272833c404e13d53d400cd0dad7a2ac0" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ "bitflags 2.6.0", ] @@ -11824,9 +11960,9 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -11844,14 +11980,14 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.7", - "regex-syntax 0.8.4", + "regex-automata 0.4.8", + "regex-syntax 0.8.5", ] [[package]] @@ -11865,13 +12001,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.4", + "regex-syntax 0.8.5", ] [[package]] @@ -11888,9 +12024,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[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 = "reqwest" @@ -11906,7 +12042,7 @@ dependencies = [ "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.30", + "hyper 0.14.31", "hyper-rustls 0.24.2", "ipnet", "js-sys", @@ -11935,9 +12071,9 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "async-compression", "base64 0.22.1", @@ -11949,7 +12085,7 @@ dependencies = [ "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-rustls 0.27.3", "hyper-util", "ipnet", @@ -11960,9 +12096,9 @@ dependencies = [ "percent-encoding", "pin-project-lite", "quinn", - "rustls 0.23.13", - "rustls-native-certs 0.7.3", - "rustls-pemfile 2.1.3", + "rustls 0.23.15", + "rustls-native-certs 0.8.0", + "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", @@ -11990,7 +12126,7 @@ dependencies = [ "anyhow", "async-trait", "http 1.1.0", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "thiserror", "tower-service", @@ -12008,9 +12144,9 @@ dependencies = [ "futures", "getrandom 0.2.15", "http 1.1.0", - "hyper 1.4.1", + "hyper 1.5.0", "parking_lot 0.11.2", - "reqwest 0.12.7", + "reqwest 0.12.8", "reqwest-middleware", "retry-policies", "tokio", @@ -12106,7 +12242,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -12225,7 +12361,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7229b505ae0706e64f37ffc54a9c163e11022a6636d58fe1f3f52018257ff9f7" dependencies = [ "cfg-if", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "rustc_version", "syn 1.0.109", @@ -12244,7 +12380,7 @@ dependencies = [ "crc32fast", "futures", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.31", "hyper-rustls 0.23.2", "lazy_static", "log", @@ -12267,7 +12403,7 @@ dependencies = [ "chrono", "dirs-next", "futures", - "hyper 0.14.30", + "hyper 0.14.31", "serde", "serde_json", "shlex", @@ -12303,7 +12439,7 @@ dependencies = [ "hex", "hmac 0.11.0", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.31", "log", "md-5 0.9.1", "percent-encoding", @@ -12517,9 +12653,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.13" +version = "0.23.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993" dependencies = [ "log", "once_cell", @@ -12549,7 +12685,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.2.0", "rustls-pki-types", "schannel", "security-framework", @@ -12562,7 +12698,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.2.0", "rustls-pki-types", "schannel", "security-framework", @@ -12579,19 +12715,18 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" [[package]] name = "rustls-webpki" @@ -12616,9 +12751,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 = "rusty-fork" @@ -12662,7 +12797,7 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "107c3d5d7f370ac09efa62a78375f94d94b8a33c61d8c278b96683fb4dbf2d8d" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -12693,33 +12828,33 @@ dependencies = [ [[package]] name = "scale-info" -version = "2.11.3" +version = "2.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" +checksum = "1aa7ffc1c0ef49b0452c6e2986abf2b07743320641ffd5fc63d552458e3b779b" dependencies = [ "cfg-if", - "derive_more", + "derive_more 1.0.0", "parity-scale-codec 3.6.12", "scale-info-derive", ] [[package]] name = "scale-info-derive" -version = "2.11.3" +version = "2.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" +checksum = "46385cc24172cf615450267463f937c10072516359b3ff1cb24228a4a08bf951" dependencies = [ "proc-macro-crate 3.2.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 1.0.109", + "syn 2.0.85", ] [[package]] name = "schannel" -version = "0.1.24" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ "windows-sys 0.59.0", ] @@ -12743,20 +12878,19 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "serde_derive_internals", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] name = "scoped-futures" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1473e24c637950c9bd38763220bea91ec3e095a89f672bbd7a10d03e77ba467" +checksum = "1b24aae2d0636530f359e9d5ef0c04669d11c5e756699b27a6a6d845d8329091" dependencies = [ - "cfg-if", - "pin-utils", + "pin-project-lite", ] [[package]] @@ -12893,9 +13027,9 @@ checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "3ea7893ff5e2466df8d720bb615088341b295f849602c6956047f8f80f0e9bc1" dependencies = [ "serde_derive", ] @@ -12942,13 +13076,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.213" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "7e85ad2009c50b58e87caa8cd6dac16bdf511bbfb7af6c33df902396aa480fa5" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -12957,18 +13091,18 @@ version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "itoa", "memchr", "ryu", @@ -12991,16 +13125,16 @@ version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] name = "serde_spanned" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] @@ -13044,19 +13178,19 @@ dependencies = [ [[package]] name = "serde_with" -version = "3.9.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.5.0", + "indexmap 2.6.0", "serde", "serde_derive", "serde_json", - "serde_with_macros 3.9.0", + "serde_with_macros 3.11.0", "time", ] @@ -13067,21 +13201,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" dependencies = [ "darling 0.20.10", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] name = "serde_with_macros" -version = "3.9.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" dependencies = [ "darling 0.20.10", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -13102,7 +13236,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "itoa", "ryu", "serde", @@ -13267,7 +13401,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" dependencies = [ "libc", - "mio 0.8.11", + "mio", "signal-hook", ] @@ -13434,7 +13568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "990079665f075b699031e9c08fd3ab99be5029b96f3b78dc0709e8f77e4efebf" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -13460,7 +13594,7 @@ dependencies = [ "log", "object_store 0.10.2", "regex", - "reqwest 0.12.7", + "reqwest 0.12.8", "reqwest-middleware", "reqwest-retry", "serde", @@ -13469,7 +13603,7 @@ dependencies = [ "thiserror", "tokio", "url", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -13611,7 +13745,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55fe75cb4a364c7f7ae06c7dbbc8d84bddd85d6cdf9975963c3935bc1991761e" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -13732,7 +13866,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "rustversion", "syn 1.0.109", @@ -13745,10 +13879,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" dependencies = [ "heck 0.4.1", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "rustversion", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -13758,10 +13892,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck 0.5.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "rustversion", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -13797,14 +13931,14 @@ checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" [[package]] name = "sui" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anemo", "anyhow", "assert_cmd", "async-recursion", "async-trait", - "axum 0.7.6", + "axum 0.7.7", "bcs", "bin-version", "bip32 0.4.0", @@ -13838,7 +13972,7 @@ dependencies = [ "prometheus", "rand 0.8.5", "regex", - "reqwest 0.12.7", + "reqwest 0.12.8", "rusoto_core", "rusoto_kms", "rustyline", @@ -13867,7 +14001,7 @@ dependencies = [ "sui-package-management", "sui-protocol-config", "sui-replay", - "sui-sdk 1.34.0", + "sui-sdk", "sui-simulator", "sui-source-validation", "sui-swarm", @@ -13888,7 +14022,7 @@ dependencies = [ "tracing", "unescape", "url", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -14011,13 +14145,13 @@ dependencies = [ [[package]] name = "sui-analytics-indexer" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "arrow 52.2.0", "arrow-array 52.2.0", "async-trait", - "axum 0.7.6", + "axum 0.7.7", "bcs", "byteorder", "bytes", @@ -14063,16 +14197,16 @@ dependencies = [ [[package]] name = "sui-analytics-indexer-derive" -version = "1.34.0" +version = "1.36.1" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] [[package]] name = "sui-archival" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "byteorder", @@ -14132,7 +14266,7 @@ dependencies = [ "narwhal-config", "prettytable-rs", "prometheus-parse", - "reqwest 0.12.7", + "reqwest 0.12.8", "russh", "russh-keys", "serde", @@ -14179,7 +14313,7 @@ dependencies = [ "sui-macros", "sui-network", "sui-protocol-config", - "sui-sdk 1.34.0", + "sui-sdk", "sui-simulator", "sui-storage", "sui-surfer", @@ -14197,12 +14331,12 @@ dependencies = [ [[package]] name = "sui-bridge" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "arc-swap", "async-trait", - "axum 0.7.6", + "axum 0.7.7", "backoff", "bcs", "bin-version", @@ -14222,17 +14356,17 @@ dependencies = [ "once_cell", "prometheus", "rand 0.8.5", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "serde_json", - "serde_with 3.9.0", + "serde_with 3.11.0", "shared-crypto", "sui-authority-aggregation", "sui-config", "sui-json-rpc-api", "sui-json-rpc-types", "sui-keys", - "sui-sdk 1.34.0", + "sui-sdk", "sui-test-transaction-builder", "sui-types", "tap", @@ -14247,7 +14381,7 @@ dependencies = [ [[package]] name = "sui-bridge-cli" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "clap", @@ -14255,16 +14389,16 @@ dependencies = [ "fastcrypto", "futures", "move-core-types", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "serde_json", - "serde_with 3.9.0", + "serde_with 3.11.0", "shared-crypto", "sui-bridge", "sui-config", "sui-json-rpc-types", "sui-keys", - "sui-sdk 1.34.0", + "sui-sdk", "sui-types", "telemetry-subscribers", "tokio", @@ -14290,13 +14424,16 @@ dependencies = [ "prometheus", "rayon", "serde", + "serde_json", "serde_yaml 0.8.26", + "strum_macros 0.24.3", "sui-bridge", + "sui-bridge-watchdog", "sui-config", "sui-data-ingestion-core", "sui-indexer-builder", "sui-json-rpc-types", - "sui-sdk 1.34.0", + "sui-sdk", "sui-test-transaction-builder", "sui-types", "tap", @@ -14307,9 +14444,24 @@ dependencies = [ "tracing", ] +[[package]] +name = "sui-bridge-watchdog" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "ethers", + "futures", + "mysten-metrics", + "prometheus", + "sui-bridge", + "tokio", + "tracing", +] + [[package]] name = "sui-cluster-test" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "async-trait", @@ -14321,7 +14473,7 @@ dependencies = [ "move-core-types", "prometheus", "regex", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde_json", "shared-crypto", "sui-config", @@ -14332,7 +14484,7 @@ dependencies = [ "sui-json", "sui-json-rpc-types", "sui-keys", - "sui-sdk 1.34.0", + "sui-sdk", "sui-swarm", "sui-swarm-config", "sui-test-transaction-builder", @@ -14343,7 +14495,7 @@ dependencies = [ "tokio", "tokio-util 0.7.12", "tracing", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -14365,12 +14517,13 @@ dependencies = [ "once_cell", "prometheus", "rand 0.8.5", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", - "serde_with 3.9.0", + "serde_with 3.11.0", "serde_yaml 0.8.26", "sui-keys", "sui-protocol-config", + "sui-rest-api", "sui-types", "tempfile", "tracing", @@ -14384,7 +14537,7 @@ dependencies = [ "anyhow", "arc-swap", "async-trait", - "axum 0.7.6", + "axum 0.7.7", "bcs", "bytes", "chrono", @@ -14405,7 +14558,7 @@ dependencies = [ "fs_extra", "futures", "im", - "indexmap 2.5.0", + "indexmap 2.6.0", "itertools 0.13.0", "jsonrpsee", "lru 0.10.1", @@ -14433,14 +14586,14 @@ dependencies = [ "prometheus", "rand 0.8.5", "rayon", - "reqwest 0.12.7", + "reqwest 0.12.8", "roaring", "rstest", "scopeguard", "serde", "serde-reflection", "serde_json", - "serde_with 3.9.0", + "serde_with 3.11.0", "serde_yaml 0.8.26", "shared-crypto", "signature 1.6.4", @@ -14501,7 +14654,7 @@ dependencies = [ [[package]] name = "sui-data-ingestion" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "async-trait", @@ -14557,6 +14710,7 @@ dependencies = [ "telemetry-subscribers", "tempfile", "tokio", + "tokio-stream", "tracing", "url", ] @@ -14567,6 +14721,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "axum 0.7.7", "backoff", "bcs", "bigdecimal", @@ -14580,25 +14735,22 @@ dependencies = [ "prometheus", "serde", "serde_yaml 0.8.26", - "sui-bridge", "sui-config", "sui-data-ingestion-core", "sui-indexer-builder", "sui-json-rpc-types", - "sui-sdk 1.34.0", - "sui-test-transaction-builder", + "sui-sdk", "sui-types", "tap", "telemetry-subscribers", "tempfile", - "test-cluster", "tokio", "tracing", ] [[package]] name = "sui-e2e-tests" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "assert_cmd", @@ -14611,7 +14763,7 @@ dependencies = [ "fastcrypto-zkp", "fs_extra", "futures", - "indexmap 2.5.0", + "indexmap 2.6.0", "insta", "jsonrpsee", "move-binary-format", @@ -14642,7 +14794,8 @@ dependencies = [ "sui-node", "sui-protocol-config", "sui-rest-api", - "sui-sdk 1.34.0", + "sui-sdk", + "sui-sdk-types", "sui-simulator", "sui-storage", "sui-swarm", @@ -14715,17 +14868,18 @@ dependencies = [ [[package]] name = "sui-faucet" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "async-recursion", "async-trait", - "axum 0.7.6", + "axum 0.7.7", "clap", "eyre", "futures", "http 1.1.0", "mysten-metrics", + "mysten-network", "parking_lot 0.12.3", "prometheus", "scopeguard", @@ -14734,7 +14888,7 @@ dependencies = [ "sui-config", "sui-json-rpc-types", "sui-keys", - "sui-sdk 1.34.0", + "sui-sdk", "sui-types", "tap", "telemetry-subscribers", @@ -14742,12 +14896,13 @@ dependencies = [ "test-cluster", "thiserror", "tokio", + "tonic 0.12.3", "tower 0.4.13", "tower-http", "tracing", "ttl_cache", "typed-store", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -14772,7 +14927,7 @@ dependencies = [ [[package]] name = "sui-framework-snapshot" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "bcs", @@ -14820,7 +14975,7 @@ dependencies = [ "prometheus", "rand 0.8.5", "serde", - "serde_with 3.9.0", + "serde_with 3.11.0", "serde_yaml 0.8.26", "shared-crypto", "sui-config", @@ -14836,7 +14991,7 @@ dependencies = [ [[package]] name = "sui-graphql-config" -version = "1.34.0" +version = "1.36.1" dependencies = [ "quote 1.0.37", "syn 1.0.109", @@ -14856,14 +15011,14 @@ dependencies = [ [[package]] name = "sui-graphql-rpc" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "async-graphql", "async-graphql-axum", "async-graphql-value", "async-trait", - "axum 0.7.6", + "axum 0.7.7", "axum-extra", "bcs", "bin-version", @@ -14880,7 +15035,7 @@ dependencies = [ "futures", "hex", "http 1.1.0", - "hyper 1.4.1", + "hyper 1.5.0", "im", "insta", "itertools 0.13.0", @@ -14896,10 +15051,10 @@ dependencies = [ "prometheus", "rand 0.8.5", "regex", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "serde_json", - "serde_with 3.9.0", + "serde_with 3.11.0", "serde_yaml 0.8.26", "shared-crypto", "similar", @@ -14915,7 +15070,7 @@ dependencies = [ "sui-package-resolver", "sui-protocol-config", "sui-rest-api", - "sui-sdk 1.34.0", + "sui-sdk", "sui-swarm-config", "sui-test-transaction-builder", "sui-types", @@ -14930,7 +15085,7 @@ dependencies = [ "tower 0.4.13", "tower-http", "tracing", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -14938,9 +15093,9 @@ name = "sui-graphql-rpc-client" version = "0.1.0" dependencies = [ "async-graphql", - "axum 0.7.6", - "hyper 1.4.1", - "reqwest 0.12.7", + "axum 0.7.7", + "hyper 1.5.0", + "reqwest 0.12.8", "serde_json", "sui-graphql-rpc-headers", "thiserror", @@ -14950,16 +15105,16 @@ dependencies = [ name = "sui-graphql-rpc-headers" version = "0.1.0" dependencies = [ - "axum 0.7.6", + "axum 0.7.7", ] [[package]] name = "sui-indexer" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "async-trait", - "axum 0.7.6", + "axum 0.7.7", "backoff", "bb8", "bcs", @@ -14969,6 +15124,7 @@ dependencies = [ "clap", "criterion", "csv", + "dashmap", "diesel", "diesel-async", "diesel_migrations", @@ -14985,12 +15141,15 @@ dependencies = [ "ntest", "object_store 0.10.2", "prometheus", + "rand 0.8.5", "rayon", "regex", "serde", "serde_json", - "serde_with 3.9.0", + "serde_with 3.11.0", "simulacrum", + "strum 0.24.1", + "strum_macros 0.24.3", "sui-archival", "sui-config", "sui-core", @@ -15005,7 +15164,7 @@ dependencies = [ "sui-package-resolver", "sui-protocol-config", "sui-rest-api", - "sui-sdk 1.34.0", + "sui-sdk", "sui-snapshot", "sui-storage", "sui-test-transaction-builder", @@ -15017,7 +15176,9 @@ dependencies = [ "test-cluster", "thiserror", "tokio", + "tokio-stream", "tokio-util 0.7.12", + "toml 0.7.8", "tracing", "url", ] @@ -15033,7 +15194,7 @@ dependencies = [ "prometheus", "sui-data-ingestion-core", "sui-indexer-builder", - "sui-sdk 1.34.0", + "sui-sdk", "sui-types", "tap", "telemetry-subscribers", @@ -15067,7 +15228,8 @@ dependencies = [ "anyhow", "arc-swap", "async-trait", - "axum 0.7.6", + "axum 0.7.7", + "backoff", "bcs", "cached", "chrono", @@ -15076,8 +15238,8 @@ dependencies = [ "fastcrypto", "futures", "http-body 0.4.6", - "hyper 1.4.1", - "indexmap 2.5.0", + "hyper 1.5.0", + "indexmap 2.6.0", "itertools 0.13.0", "jsonrpsee", "mockall", @@ -15142,13 +15304,13 @@ dependencies = [ "anyhow", "async-trait", "bcs", - "hyper 1.4.1", + "hyper 1.5.0", "jsonrpsee", "move-core-types", "move-package", "prometheus", "rand 0.8.5", - "reqwest 0.12.7", + "reqwest 0.12.8", "sui-config", "sui-core", "sui-json", @@ -15161,7 +15323,7 @@ dependencies = [ "sui-open-rpc", "sui-open-rpc-macros", "sui-protocol-config", - "sui-sdk 1.34.0", + "sui-sdk", "sui-simulator", "sui-swarm-config", "sui-test-transaction-builder", @@ -15190,7 +15352,7 @@ dependencies = [ "schemars", "serde", "serde_json", - "serde_with 3.9.0", + "serde_with 3.11.0", "sui-enum-compat-util", "sui-json", "sui-macros", @@ -15222,7 +15384,7 @@ dependencies = [ [[package]] name = "sui-light-client" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "async-trait", @@ -15234,7 +15396,7 @@ dependencies = [ "move-binary-format", "move-core-types", "object_store 0.10.2", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "serde_json", "serde_yaml 0.8.26", @@ -15242,7 +15404,7 @@ dependencies = [ "sui-json-rpc-types", "sui-package-resolver", "sui-rest-api", - "sui-sdk 1.34.0", + "sui-sdk", "sui-types", "tokio", "url", @@ -15260,7 +15422,7 @@ dependencies = [ [[package]] name = "sui-metric-checker" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "backoff", @@ -15270,7 +15432,7 @@ dependencies = [ "humantime", "once_cell", "prometheus-http-query", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "serde_yaml 0.9.34+deprecated", "strum_macros 0.24.3", @@ -15281,7 +15443,7 @@ dependencies = [ [[package]] name = "sui-move" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "assert_cmd", @@ -15323,7 +15485,7 @@ dependencies = [ [[package]] name = "sui-move-build" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "datatest-stable", @@ -15347,7 +15509,7 @@ dependencies = [ [[package]] name = "sui-move-lsp" -version = "1.34.0" +version = "1.36.1" dependencies = [ "bin-version", "clap", @@ -15364,7 +15526,7 @@ dependencies = [ "fastcrypto", "fastcrypto-vdf", "fastcrypto-zkp", - "indexmap 2.5.0", + "indexmap 2.6.0", "move-binary-format", "move-core-types", "move-stdlib-natives", @@ -15425,7 +15587,7 @@ dependencies = [ "better_any", "fastcrypto", "fastcrypto-zkp", - "indexmap 2.5.0", + "indexmap 2.6.0", "move-binary-format", "move-core-types", "move-stdlib-natives-v2", @@ -15454,11 +15616,13 @@ dependencies = [ "fastcrypto-tbls", "futures", "governor", + "mysten-common", "mysten-metrics", "mysten-network", "prometheus", "rand 0.8.5", "serde", + "shared-crypto", "sui-archival", "sui-config", "sui-macros", @@ -15469,25 +15633,26 @@ dependencies = [ "telemetry-subscribers", "tempfile", "tokio", - "tonic 0.12.2", - "tonic-build 0.12.2", + "tonic 0.12.3", + "tonic-build 0.12.3", "tower 0.4.13", "tracing", ] [[package]] name = "sui-node" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anemo", "anemo-tower", "anyhow", "arc-swap", - "axum 0.7.6", + "axum 0.7.7", "base64 0.21.7", "bcs", "bin-version", "clap", + "consensus-core", "fastcrypto", "fastcrypto-zkp", "futures", @@ -15500,7 +15665,7 @@ dependencies = [ "narwhal-network", "parking_lot 0.12.3", "prometheus", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "sui-archival", "sui-config", @@ -15528,7 +15693,7 @@ dependencies = [ [[package]] name = "sui-open-rpc" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "bcs", @@ -15556,7 +15721,7 @@ version = "0.1.0" dependencies = [ "derive-syn-parse", "itertools 0.13.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", "unescape", @@ -15564,7 +15729,7 @@ dependencies = [ [[package]] name = "sui-oracle" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "bcs", @@ -15576,7 +15741,7 @@ dependencies = [ "once_cell", "prometheus", "rand 0.8.5", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "serde_json", "shared-crypto", @@ -15584,7 +15749,7 @@ dependencies = [ "sui-json-rpc-types", "sui-keys", "sui-move-build", - "sui-sdk 1.34.0", + "sui-sdk", "sui-types", "tap", "telemetry-subscribers", @@ -15594,7 +15759,7 @@ dependencies = [ [[package]] name = "sui-package-dump" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "bcs", @@ -15602,7 +15767,7 @@ dependencies = [ "cynic-codegen", "fastcrypto", "move-core-types", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "serde_json", "sui-types", @@ -15611,14 +15776,14 @@ dependencies = [ [[package]] name = "sui-package-management" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "move-core-types", "move-package", "move-symbol-pool", "sui-json-rpc-types", - "sui-sdk 1.34.0", + "sui-sdk", "sui-types", "thiserror", "tracing", @@ -15631,7 +15796,7 @@ dependencies = [ "async-trait", "bcs", "eyre", - "hyper 1.4.1", + "hyper 1.5.0", "insta", "lru 0.10.1", "move-binary-format", @@ -15653,10 +15818,10 @@ name = "sui-proc-macros" version = "0.7.0" dependencies = [ "msim-macros", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "sui-enum-compat-util", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -15669,7 +15834,7 @@ dependencies = [ "schemars", "serde", "serde-env", - "serde_with 3.9.0", + "serde_with 3.11.0", "sui-protocol-config-macros", "tracing", ] @@ -15678,7 +15843,7 @@ dependencies = [ name = "sui-protocol-config-macros" version = "0.1.0" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -15688,7 +15853,7 @@ name = "sui-proxy" version = "0.0.2" dependencies = [ "anyhow", - "axum 0.7.6", + "axum 0.7.7", "axum-extra", "axum-server", "bin-version", @@ -15698,7 +15863,7 @@ dependencies = [ "fastcrypto", "futures", "hex", - "hyper 1.4.1", + "hyper 1.5.0", "ipnetwork", "itertools 0.13.0", "mime", @@ -15710,12 +15875,12 @@ dependencies = [ "prost-build 0.13.3", "protobuf", "rand 0.8.5", - "reqwest 0.12.7", - "rustls 0.23.13", - "rustls-pemfile 2.1.3", + "reqwest 0.12.8", + "rustls 0.23.15", + "rustls-pemfile 2.2.0", "serde", "serde_json", - "serde_with 3.9.0", + "serde_with 3.11.0", "serde_yaml 0.8.26", "snap", "sui-tls", @@ -15751,7 +15916,7 @@ dependencies = [ "regex", "serde", "serde_json", - "serde_with 3.9.0", + "serde_with 3.11.0", "serde_yaml 0.8.26", "shared-crypto", "shellexpand 3.1.0", @@ -15764,7 +15929,7 @@ dependencies = [ "sui-json-rpc-api", "sui-json-rpc-types", "sui-protocol-config", - "sui-sdk 1.34.0", + "sui-sdk", "sui-storage", "sui-transaction-checks", "sui-types", @@ -15782,24 +15947,27 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "axum 0.7.6", + "axum 0.7.7", "bcs", "diffy 0.3.0", + "documented", "fastcrypto", "itertools 0.13.0", "mime", + "move-binary-format", + "move-core-types", "mysten-network", "openapiv3", "prometheus", "rand 0.8.5", - "reqwest 0.12.7", + "reqwest 0.12.8", "schemars", "serde", "serde_json", - "serde_with 3.9.0", + "serde_with 3.11.0", "serde_yaml 0.8.26", "sui-protocol-config", - "sui-sdk 0.0.0", + "sui-sdk-types", "sui-types", "tap", "thiserror", @@ -15809,24 +15977,26 @@ dependencies = [ [[package]] name = "sui-rosetta" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "async-trait", - "axum 0.7.6", + "axum 0.7.7", "axum-extra", "bcs", "clap", "eyre", "fastcrypto", "futures", - "hyper 1.4.1", + "hyper 1.5.0", + "lru 0.10.1", "move-cli", "move-core-types", "mysten-metrics", "once_cell", + "quick-js", "rand 0.8.5", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "serde_json", "shared-crypto", @@ -15838,7 +16008,7 @@ dependencies = [ "sui-keys", "sui-move-build", "sui-node", - "sui-sdk 1.34.0", + "sui-sdk", "sui-swarm-config", "sui-types", "telemetry-subscribers", @@ -15852,7 +16022,7 @@ dependencies = [ [[package]] name = "sui-rpc-loadgen" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "async-trait", @@ -15870,38 +16040,18 @@ dependencies = [ "sui-json-rpc", "sui-json-rpc-types", "sui-keys", - "sui-sdk 1.34.0", + "sui-sdk", "sui-types", "telemetry-subscribers", "test-cluster", "tokio", - "tonic 0.12.2", + "tonic 0.12.3", "tracing", ] [[package]] name = "sui-sdk" -version = "0.0.0" -source = "git+https://github.com/mystenlabs/sui-rust-sdk.git?rev=bd233b6879b917fb95e17f21927c198e7a60c924#bd233b6879b917fb95e17f21927c198e7a60c924" -dependencies = [ - "base64ct", - "bcs", - "blake2", - "bnum", - "bs58 0.5.1", - "hex", - "roaring", - "schemars", - "serde", - "serde_derive", - "serde_json", - "serde_with 3.9.0", - "winnow 0.6.19", -] - -[[package]] -name = "sui-sdk" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "async-recursion", @@ -15917,10 +16067,10 @@ dependencies = [ "jsonrpsee", "move-core-types", "rand 0.8.5", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "serde_json", - "serde_with 3.9.0", + "serde_with 3.11.0", "shared-crypto", "sui-config", "sui-json", @@ -15935,9 +16085,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "sui-sdk-types" +version = "0.0.1" +source = "git+https://github.com/MystenLabs/sui-rust-sdk.git?rev=31bd9da32a8057edc45da8f5e6a8f25b83919b93#31bd9da32a8057edc45da8f5e6a8f25b83919b93" +dependencies = [ + "base64ct", + "bcs", + "blake2", + "bnum", + "bs58 0.5.1", + "hex", + "roaring", + "schemars", + "serde", + "serde_derive", + "serde_json", + "serde_with 3.11.0", + "winnow 0.6.20", +] + [[package]] name = "sui-security-watchdog" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "arrow-array 52.2.0", @@ -15948,7 +16118,7 @@ dependencies = [ "lexical-util", "mysten-metrics", "prometheus", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "serde_json", "snowflake-api", @@ -15956,7 +16126,7 @@ dependencies = [ "tokio", "tokio-cron-scheduler", "tracing", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -15984,7 +16154,7 @@ dependencies = [ [[package]] name = "sui-single-node-benchmark" -version = "1.34.0" +version = "1.36.1" dependencies = [ "async-trait", "bcs", @@ -16047,7 +16217,7 @@ dependencies = [ [[package]] name = "sui-source-validation" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "colored", @@ -16065,7 +16235,7 @@ dependencies = [ "sui-json-rpc-types", "sui-move-build", "sui-package-management", - "sui-sdk 1.34.0", + "sui-sdk", "sui-test-transaction-builder", "sui-types", "tar", @@ -16082,12 +16252,12 @@ name = "sui-source-validation-service" version = "0.1.0" dependencies = [ "anyhow", - "axum 0.7.6", + "axum 0.7.7", "bin-version", "clap", "expect-test", "fs_extra", - "hyper 1.4.1", + "hyper 1.5.0", "jsonrpsee", "move-compiler", "move-core-types", @@ -16095,13 +16265,13 @@ dependencies = [ "move-symbol-pool", "mysten-metrics", "prometheus", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "sui", "sui-json-rpc-types", "sui-move", "sui-move-build", - "sui-sdk 1.34.0", + "sui-sdk", "sui-source-validation", "telemetry-subscribers", "tempfile", @@ -16120,7 +16290,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", - "axum 0.7.6", + "axum 0.7.7", "backoff", "base64-url", "bcs", @@ -16132,7 +16302,7 @@ dependencies = [ "eyre", "fastcrypto", "futures", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-rustls 0.27.3", "indicatif", "integer-encoding", @@ -16151,7 +16321,7 @@ dependencies = [ "percent-encoding", "pretty_assertions", "prometheus", - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "serde_json", "sui-config", @@ -16173,13 +16343,13 @@ dependencies = [ [[package]] name = "sui-surfer" -version = "1.34.0" +version = "1.36.1" dependencies = [ "async-trait", "bcs", "clap", "futures", - "indexmap 2.5.0", + "indexmap 2.6.0", "move-binary-format", "move-core-types", "move-package", @@ -16238,7 +16408,7 @@ dependencies = [ "prometheus", "rand 0.8.5", "serde", - "serde_with 3.9.0", + "serde_with 3.11.0", "serde_yaml 0.8.26", "shared-crypto", "sui-config", @@ -16246,6 +16416,7 @@ dependencies = [ "sui-genesis-builder", "sui-macros", "sui-protocol-config", + "sui-rest-api", "sui-simulator", "sui-types", "tempfile", @@ -16256,7 +16427,7 @@ dependencies = [ name = "sui-telemetry" version = "0.1.0" dependencies = [ - "reqwest 0.12.7", + "reqwest 0.12.8", "serde", "sui-core", "tracing", @@ -16271,28 +16442,29 @@ dependencies = [ "shared-crypto", "sui-genesis-builder", "sui-move-build", - "sui-sdk 1.34.0", + "sui-sdk", "sui-types", ] [[package]] name = "sui-test-validator" -version = "1.34.0" +version = "1.36.1" [[package]] name = "sui-tls" version = "0.0.0" dependencies = [ "anyhow", - "axum 0.7.6", + "arc-swap", + "axum 0.7.7", "axum-server", "ed25519 1.5.3", "fastcrypto", "pkcs8 0.9.0", "rand 0.8.5", "rcgen", - "reqwest 0.12.7", - "rustls 0.23.13", + "reqwest 0.12.8", + "rustls 0.23.15", "rustls-webpki 0.102.8", "tokio", "tokio-rustls 0.26.0", @@ -16302,7 +16474,7 @@ dependencies = [ [[package]] name = "sui-tool" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anemo", "anemo-cli", @@ -16334,7 +16506,7 @@ dependencies = [ "sui-package-dump", "sui-protocol-config", "sui-replay", - "sui-sdk 1.34.0", + "sui-sdk", "sui-snapshot", "sui-storage", "sui-types", @@ -16438,7 +16610,7 @@ dependencies = [ "coset", "criterion", "derivative", - "derive_more", + "derive_more 0.99.18", "enum_dispatch", "expect-test", "eyre", @@ -16446,7 +16618,7 @@ dependencies = [ "fastcrypto-tbls", "fastcrypto-zkp", "im", - "indexmap 2.5.0", + "indexmap 2.6.0", "itertools 0.13.0", "jsonrpsee", "lru 0.10.1", @@ -16480,7 +16652,7 @@ dependencies = [ "serde", "serde-name", "serde_json", - "serde_with 3.9.0", + "serde_with 3.11.0", "serde_yaml 0.8.26", "shared-crypto", "signature 1.6.4", @@ -16490,11 +16662,11 @@ dependencies = [ "sui-enum-compat-util", "sui-macros", "sui-protocol-config", - "sui-sdk 0.0.0", + "sui-sdk-types", "tap", "thiserror", "tokio", - "tonic 0.12.2", + "tonic 0.12.3", "tracing", "typed-store-error", "url", @@ -16584,7 +16756,7 @@ dependencies = [ [[package]] name = "suins-indexer" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "async-trait", @@ -16596,6 +16768,7 @@ dependencies = [ "diesel-async", "dotenvy", "futures", + "futures-util", "move-core-types", "mysten-metrics", "mysten-service", @@ -16603,6 +16776,7 @@ dependencies = [ "object_store 0.10.2", "prometheus", "rand 0.8.5", + "rustls 0.23.15", "serde", "serde_json", "serde_yaml 0.8.26", @@ -16613,24 +16787,29 @@ dependencies = [ "telemetry-subscribers", "tempfile", "tokio", + "tokio-postgres", + "tokio-postgres-rustls", "tracing", "url", + "webpki-roots 0.26.6", ] [[package]] name = "suiop-cli" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", - "axum 0.7.6", + "axum 0.7.7", "base64 0.21.7", "chrono", "clap", "colored", + "crossterm 0.25.0", "dirs 4.0.0", "docker-api", "field_names", "futures", + "futures-timer", "include_dir", "inquire", "itertools 0.13.0", @@ -16639,7 +16818,7 @@ dependencies = [ "prettytable-rs", "rand 0.8.5", "regex", - "reqwest 0.12.7", + "reqwest 0.12.8", "semver", "serde", "serde_json", @@ -16650,6 +16829,7 @@ dependencies = [ "strum 0.24.1", "tabled", "tempfile", + "thiserror", "tokio", "toml_edit 0.19.15", "tracing", @@ -16699,21 +16879,21 @@ dependencies = [ [[package]] name = "symbolic-common" -version = "12.11.1" +version = "12.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fdf97c441f18a4f92425b896a4ec7a27e03631a0b1047ec4e34e9916a9a167e" +checksum = "366f1b4c6baf6cfefc234bbd4899535fca0b06c74443039a73f6dfb2fad88d77" dependencies = [ "debugid", "memmap2", "stable_deref_trait", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] name = "symbolic-demangle" -version = "12.11.1" +version = "12.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc8ece6b129e97e53d1fbb3f61d33a6a9e5369b11d01228c068094d6d134eaea" +checksum = "aba05ba5b9962ea5617baf556293720a8b2d0a282aa14ee4bf10e22efc7da8c8" dependencies = [ "cpp_demangle 0.4.4", "rustc-demangle", @@ -16737,18 +16917,18 @@ version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "unicode-ident", ] [[package]] name = "syn" -version = "2.0.77" +version = "2.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "unicode-ident", ] @@ -16774,7 +16954,7 @@ version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", "unicode-xid 0.2.6", @@ -16851,7 +17031,7 @@ checksum = "99f688a08b54f4f02f0a3c382aefdb7884d3d69609f785bd253dc033243e3fe4" dependencies = [ "heck 0.4.1", "proc-macro-error", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -16921,14 +17101,15 @@ dependencies = [ "crossterm 0.25.0", "futures", "once_cell", - "opentelemetry 0.20.0", + "opentelemetry 0.25.0", "opentelemetry-otlp", "opentelemetry-proto", "opentelemetry_api", + "opentelemetry_sdk", "prometheus", - "prost 0.11.9", + "prost 0.13.3", "tokio", - "tonic 0.9.2", + "tonic 0.12.3", "tracing", "tracing-appender", "tracing-opentelemetry", @@ -16937,9 +17118,9 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.12.0" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", @@ -17027,6 +17208,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "terminal_size" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f599bd7ca042cfdf8f4512b277c02ba102247820f9d9d4a9f521f496751a6ef" +dependencies = [ + "rustix", + "windows-sys 0.59.0", +] + [[package]] name = "termtree" version = "0.4.1" @@ -17046,7 +17237,6 @@ dependencies = [ "move-binary-format", "prometheus", "rand 0.8.5", - "sui-bridge", "sui-config", "sui-core", "sui-framework", @@ -17057,7 +17247,7 @@ dependencies = [ "sui-macros", "sui-node", "sui-protocol-config", - "sui-sdk 1.34.0", + "sui-sdk", "sui-simulator", "sui-swarm", "sui-swarm-config", @@ -17086,7 +17276,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48db3bbc562408b2111f3a0c96ec416ffa3ab66f8a6ab42579b608b9f74744e1" dependencies = [ "cargo_metadata 0.15.4", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "serde", "strum_macros 0.24.3", @@ -17102,10 +17292,10 @@ dependencies = [ "if_chain", "itertools 0.10.5", "lazy_static", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "subprocess", - "syn 2.0.77", + "syn 2.0.85", "test-fuzz-internal", "toolchain_find", ] @@ -17137,22 +17327,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.64" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -17182,7 +17372,7 @@ checksum = "7e54bc85fc7faa8bc175c4bab5b92ba8d9a3ce893d0e9f42cc455c8ab16a9e09" dependencies = [ "byteorder", "integer-encoding", - "ordered-float 2.10.1", + "ordered-float", ] [[package]] @@ -17273,21 +17463,22 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.40.0" +version = "1.38.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" dependencies = [ "backtrace", "bytes", "libc", - "mio 1.0.2", + "mio", + "num_cpus", "parking_lot 0.12.3", "pin-project-lite", "signal-hook-registry", "socket2 0.5.7", - "tokio-macros 2.4.0", + "tokio-macros 2.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "tracing", - "windows-sys 0.52.0", + "windows-sys 0.48.0", ] [[package]] @@ -17302,7 +17493,7 @@ dependencies = [ "num-traits", "tokio", "tracing", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -17317,23 +17508,23 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" -source = "git+https://github.com/mystenmark/tokio-madsim-fork.git?rev=e47aafebf98e9c1734a8848a1876d5946c44bdd1#e47aafebf98e9c1734a8848a1876d5946c44bdd1" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] name = "tokio-macros" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +version = "2.3.0" +source = "git+https://github.com/mystenmark/tokio-madsim-fork.git?rev=d46208cb11118c0e6ab5dfea1a2265add36fbc15#d46208cb11118c0e6ab5dfea1a2265add36fbc15" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -17362,6 +17553,20 @@ dependencies = [ "whoami", ] +[[package]] +name = "tokio-postgres-rustls" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04fb792ccd6bbcd4bba408eb8a292f70fc4a3589e5d793626f45190e6454b6ab" +dependencies = [ + "ring 0.17.8", + "rustls 0.23.15", + "tokio", + "tokio-postgres", + "tokio-rustls 0.26.0", + "x509-certificate", +] + [[package]] name = "tokio-retry" version = "0.3.0" @@ -17411,7 +17616,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls 0.23.13", + "rustls 0.23.15", "rustls-pki-types", "tokio", ] @@ -17445,20 +17650,20 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" dependencies = [ "futures-util", "log", "tokio", - "tungstenite 0.23.0", + "tungstenite 0.24.0", ] [[package]] name = "tokio-util" -version = "0.7.10" -source = "git+https://github.com/mystenmark/tokio-madsim-fork.git?rev=e47aafebf98e9c1734a8848a1876d5946c44bdd1#e47aafebf98e9c1734a8848a1876d5946c44bdd1" +version = "0.7.11" +source = "git+https://github.com/mystenmark/tokio-madsim-fork.git?rev=d46208cb11118c0e6ab5dfea1a2265add36fbc15#d46208cb11118c0e6ab5dfea1a2265add36fbc15" dependencies = [ "bytes", "futures-core", @@ -17469,7 +17674,6 @@ dependencies = [ "pin-project-lite", "real_tokio", "slab", - "tracing", ] [[package]] @@ -17504,7 +17708,7 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime 0.6.8", @@ -17568,7 +17772,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime 0.6.8", @@ -17581,11 +17785,11 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap 2.5.0", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime 0.6.8", - "winnow 0.6.19", + "winnow 0.6.20", ] [[package]] @@ -17604,7 +17808,7 @@ dependencies = [ "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.30", + "hyper 0.14.31", "hyper-timeout 0.4.1", "percent-encoding", "pin-project", @@ -17634,7 +17838,7 @@ dependencies = [ "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.30", + "hyper 0.14.31", "hyper-timeout 0.4.1", "percent-encoding", "pin-project", @@ -17649,20 +17853,20 @@ dependencies = [ [[package]] name = "tonic" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6f6ba989e4b2c58ae83d862d3a3e27690b6e3ae630d0deb59f3697f32aa88ad" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ "async-stream", "async-trait", - "axum 0.7.6", + "axum 0.7.7", "base64 0.22.1", "bytes", "h2 0.4.6", "http 1.1.0", "http-body 1.0.1", "http-body-util", - "hyper 1.4.1", + "hyper 1.5.0", "hyper-timeout 0.5.1", "hyper-util", "percent-encoding", @@ -17684,7 +17888,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6fdaae4c2c638bb70fe42803a26fbd6fc6ac8c72f5c59f67ecc2a2dcabf4b07" dependencies = [ "prettyplease 0.1.25", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "prost-build 0.11.9", "quote 1.0.37", "syn 1.0.109", @@ -17692,28 +17896,29 @@ dependencies = [ [[package]] name = "tonic-build" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe4ee8877250136bd7e3d2331632810a4df4ea5e004656990d8d66d2f5ee8a67" +checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" dependencies = [ - "prettyplease 0.2.22", - "proc-macro2 1.0.86", + "prettyplease 0.2.25", + "proc-macro2 1.0.89", "prost-build 0.13.3", + "prost-types 0.13.3", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] name = "tonic-health" -version = "0.12.2" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0a34e6f706bae26b2b490e1da5c3f6a6ff87cae442bcbc7c881bab9631b5a7" +checksum = "1eaf34ddb812120f5c601162d5429933c9b527d901ab0e7f930d3147e33a09b2" dependencies = [ "async-stream", "prost 0.13.3", "tokio", "tokio-stream", - "tonic 0.12.2", + "tonic 0.12.3", ] [[package]] @@ -17794,7 +17999,7 @@ dependencies = [ "tower-layer", "tower-service", "tracing", - "uuid 1.10.0", + "uuid 1.11.0", ] [[package]] @@ -17839,9 +18044,9 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -17876,17 +18081,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "tracing-log" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f751112709b4e791d8ce53e32c4ed2d353565a795ce84da2285393f41557bdf2" -dependencies = [ - "log", - "once_cell", - "tracing-core", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -17900,18 +18094,20 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.21.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75327c6b667828ddc28f5e3f169036cb793c3f588d83bf0f262a7f062ffed3c8" +checksum = "5eabc56d23707ad55ba2a0750fc24767125d5a0f51993ba41ad2c441cc7b8dea" dependencies = [ + "js-sys", "once_cell", - "opentelemetry 0.20.0", + "opentelemetry 0.25.0", "opentelemetry_sdk", "smallvec", "tracing", "tracing-core", - "tracing-log 0.1.4", + "tracing-log", "tracing-subscriber", + "web-time", ] [[package]] @@ -17942,7 +18138,7 @@ dependencies = [ "time", "tracing", "tracing-core", - "tracing-log 0.2.0", + "tracing-log", "tracing-serde", ] @@ -17952,7 +18148,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b79e2e9c9ab44c6d7c20d5976961b47e8f49ac199154daa514b77cd1ab536625" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -18036,9 +18232,9 @@ dependencies = [ [[package]] name = "tungstenite" -version = "0.23.0" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" dependencies = [ "byteorder", "bytes", @@ -18078,7 +18274,7 @@ dependencies = [ "msim", "once_cell", "ouroboros 0.17.2", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "prometheus", "quote 1.0.37", "rand 0.8.5", @@ -18103,7 +18299,7 @@ name = "typed-store-derive" version = "0.3.0" dependencies = [ "itertools 0.13.0", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "syn 1.0.109", ] @@ -18125,11 +18321,11 @@ dependencies = [ "libc", "memchr", "nom", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "regex", "regex-syntax 0.7.5", - "syn 2.0.77", + "syn 2.0.85", "zstd-sys", ] @@ -18158,14 +18354,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a615d6c2764852a2e88a4f16e9ce1ea49bb776b5872956309e170d63a042a34f" dependencies = [ "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "uint" @@ -18193,18 +18389,15 @@ checksum = "ccb97dac3243214f8d8507998906ca3e2e0b900bf9bf4870477f125b82e68f6e" [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] +checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df" [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" @@ -18229,9 +18422,9 @@ dependencies = [ [[package]] name = "unicode-properties" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" +checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" [[package]] name = "unicode-segmentation" @@ -18301,7 +18494,7 @@ dependencies = [ "flate2", "log", "once_cell", - "rustls 0.23.13", + "rustls 0.23.15", "rustls-pki-types", "url", "webpki-roots 0.26.6", @@ -18349,9 +18542,9 @@ dependencies = [ [[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" dependencies = [ "getrandom 0.2.15", "rand 0.8.5", @@ -18365,9 +18558,9 @@ checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" [[package]] name = "value-bag" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" +checksum = "3ef4c4aa54d5d05a279399bfa921ec387b7aba77caf7a682ae8d86785b8fdad2" [[package]] name = "variant_count" @@ -18430,7 +18623,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e369bee1b05d510a7b4ed645f5faa90619e05437111783ea5848f28d97d3c2e" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", ] @@ -18508,9 +18701,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[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", @@ -18519,24 +18712,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 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", "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", @@ -18546,9 +18739,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 1.0.37", "wasm-bindgen-macro-support", @@ -18556,22 +18749,22 @@ 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 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", "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 = "wasm-encoder" @@ -18584,18 +18777,19 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.217.0" +version = "0.219.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b88b0814c9a2b323a9b46c687e726996c255ac8b64aa237dd11c81ed4854760" +checksum = "29cbbd772edcb8e7d524a82ee8cef8dd046fc14033796a754c3ad246d019fa54" dependencies = [ "leb128", + "wasmparser 0.219.1", ] [[package]] name = "wasm-streams" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" dependencies = [ "futures-util", "js-sys", @@ -18626,10 +18820,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dbe55c8f9d0dbd25d9447a5a889ff90c0cc3feaa7395310d3d826b2c703eaab" dependencies = [ "bitflags 2.6.0", - "indexmap 2.5.0", + "indexmap 2.6.0", "semver", ] +[[package]] +name = "wasmparser" +version = "0.219.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c771866898879073c53b565a6c7b49953795159836714ac56a5befb581227c5" +dependencies = [ + "bitflags 2.6.0", + "indexmap 2.6.0", +] + [[package]] name = "wasmprinter" version = "0.2.80" @@ -18637,7 +18841,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60e73986a6b7fdfedb7c5bf9e7eb71135486507c8fbc4c0c42cffcb6532988b7" dependencies = [ "anyhow", - "wasmparser", + "wasmparser 0.121.2", ] [[package]] @@ -18655,7 +18859,7 @@ dependencies = [ "encoding_rs", "fxprof-processed-profile", "gimli", - "indexmap 2.5.0", + "indexmap 2.6.0", "ittapi", "libc", "log", @@ -18669,7 +18873,7 @@ dependencies = [ "serde_json", "target-lexicon", "wasm-encoder 0.41.2", - "wasmparser", + "wasmparser 0.121.2", "wasmtime-cache", "wasmtime-component-macro", "wasmtime-component-util", @@ -18720,9 +18924,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc6aca484581f9651886dca45f9dea893e105713b58623d14b06c56d8fe3f3f1" dependencies = [ "anyhow", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", "wasmtime-component-util", "wasmtime-wit-bindgen", "wit-parser", @@ -18753,7 +18957,7 @@ dependencies = [ "object", "target-lexicon", "thiserror", - "wasmparser", + "wasmparser 0.121.2", "wasmtime-cranelift-shared", "wasmtime-environ", "wasmtime-versioned-export-macros", @@ -18786,7 +18990,7 @@ dependencies = [ "cpp_demangle 0.3.5", "cranelift-entity", "gimli", - "indexmap 2.5.0", + "indexmap 2.6.0", "log", "object", "rustc-demangle", @@ -18795,7 +18999,7 @@ dependencies = [ "target-lexicon", "thiserror", "wasm-encoder 0.41.2", - "wasmparser", + "wasmparser 0.121.2", "wasmprinter", "wasmtime-component-util", "wasmtime-types", @@ -18849,7 +19053,7 @@ dependencies = [ "cc", "cfg-if", "encoding_rs", - "indexmap 2.5.0", + "indexmap 2.6.0", "libc", "log", "mach", @@ -18879,7 +19083,7 @@ dependencies = [ "serde", "serde_derive", "thiserror", - "wasmparser", + "wasmparser 0.121.2", ] [[package]] @@ -18888,9 +19092,9 @@ version = "18.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5399c175ddba4a471b9da45105dea3493059d52b2d54860eadb0df04c813948d" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -18937,7 +19141,7 @@ dependencies = [ "gimli", "object", "target-lexicon", - "wasmparser", + "wasmparser 0.121.2", "wasmtime-cranelift-shared", "wasmtime-environ", "winch-codegen", @@ -18951,7 +19155,7 @@ checksum = "6945fc6cfee04ba81016e9723bea77a2b913108e03904a4d901daedf208365f5" dependencies = [ "anyhow", "heck 0.4.1", - "indexmap 2.5.0", + "indexmap 2.6.0", "wit-parser", ] @@ -18972,31 +19176,41 @@ dependencies = [ [[package]] name = "wast" -version = "217.0.0" +version = "219.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79004ecebded92d3c710d4841383368c7f04b63d0992ddd6b0c7d5029b7629b7" +checksum = "4f79a9d9df79986a68689a6b40bcc8d5d40d807487b235bebc2ac69a242b54a1" dependencies = [ "bumpalo", "leb128", "memchr", "unicode-width", - "wasm-encoder 0.217.0", + "wasm-encoder 0.219.1", ] [[package]] name = "wat" -version = "1.217.0" +version = "1.219.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c126271c3d92ca0f7c63e4e462e40c69cca52fd4245fcda730d1cf558fb55088" +checksum = "8bc3cf014fb336883a411cd662f987abf6a1d2a27f2f0008616a0070bbf6bd0d" dependencies = [ - "wast 217.0.0", + "wast 219.0.1", ] [[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", @@ -19054,7 +19268,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "372d5b87f58ec45c384ba03563b03544dc5fadc3983e434b286913f5b4a9bb6d" dependencies = [ - "redox_syscall 0.5.5", + "redox_syscall 0.5.7", "wasite", "web-sys", ] @@ -19088,10 +19302,10 @@ checksum = "def372d639555c826c4f287a7bdde673da127ecb95a3cd5453d53d8f3c0c07e4" dependencies = [ "anyhow", "heck 0.4.1", - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", "shellexpand 2.1.2", - "syn 2.0.77", + "syn 2.0.85", "witx", ] @@ -19101,9 +19315,9 @@ version = "18.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93e43fc332703d1ec3aa86a5ce8bb49e6b95b6c617b90e726d3e70a0f70f48a5" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", "wiggle-generate", ] @@ -19150,7 +19364,7 @@ dependencies = [ "regalloc2", "smallvec", "target-lexicon", - "wasmparser", + "wasmparser 0.121.2", "wasmtime-environ", ] @@ -19352,9 +19566,9 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c52ac009d615e79296318c1bcce2d422aaca15ad08515e344feeda07df67a587" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] @@ -19387,7 +19601,7 @@ checksum = "316b36a9f0005f5aa4b03c39bc3728d045df136f8c13a73b7db4510dec725e08" dependencies = [ "anyhow", "id-arena", - "indexmap 2.5.0", + "indexmap 2.6.0", "log", "semver", "serde", @@ -19444,7 +19658,7 @@ dependencies = [ [[package]] name = "x" -version = "1.34.0" +version = "1.36.1" dependencies = [ "anyhow", "camino", @@ -19453,6 +19667,25 @@ dependencies = [ "nexlint-lints", ] +[[package]] +name = "x509-certificate" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66534846dec7a11d7c50a74b7cdb208b9a581cad890b7866430d438455847c85" +dependencies = [ + "bcder", + "bytes", + "chrono", + "der 0.7.9", + "hex", + "pem 3.0.4", + "ring 0.17.8", + "signature 2.2.0", + "spki 0.7.3", + "thiserror", + "zeroize", +] + [[package]] name = "x509-parser" version = "0.14.0" @@ -19537,7 +19770,7 @@ dependencies = [ "base64 0.21.7", "futures", "http 0.2.12", - "hyper 0.14.30", + "hyper 0.14.31", "hyper-rustls 0.24.2", "itertools 0.12.1", "log", @@ -19569,9 +19802,9 @@ version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] @@ -19589,9 +19822,9 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ - "proc-macro2 1.0.86", + "proc-macro2 1.0.89", "quote 1.0.37", - "syn 2.0.77", + "syn 2.0.85", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 2531bc6ceb96c..67cc9896cda22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -93,6 +93,7 @@ members = [ "crates/sui-bridge", "crates/sui-bridge-cli", "crates/sui-bridge-indexer", + "crates/sui-bridge-watchdog", "crates/sui-cluster-test", "crates/sui-config", "crates/sui-core", @@ -202,7 +203,7 @@ members = [ [workspace.package] # This version string will be inherited by sui-core, sui-faucet, sui-node, sui-tools, sui-sdk, sui-move-build, and sui crates. -version = "1.34.0" +version = "1.36.1" [profile.release] # debug = 1 means line charts only, which is minimum needed for good stack traces @@ -264,6 +265,7 @@ aws-sdk-s3 = "0.29.0" aws-smithy-http = "0.56" aws-smithy-runtime-api = "0.56" axum = { version = "0.7", default-features = false, features = [ + "macros", "tokio", "http1", "http2", @@ -386,8 +388,8 @@ moka = { version = "0.12", default-features = false, features = [ "atomic64", ] } more-asserts = "0.3.1" -msim = { git = "https://github.com/MystenLabs/mysten-sim.git", rev = "b320996d8dfb99b273fe31c0222c659332283c99", package = "msim" } -msim-macros = { git = "https://github.com/MystenLabs/mysten-sim.git", rev = "b320996d8dfb99b273fe31c0222c659332283c99", package = "msim-macros" } +msim = { git = "https://github.com/MystenLabs/mysten-sim.git", rev = "9c6636c399d5c60a1759f1670b1c07b3d408799a", package = "msim" } +msim-macros = { git = "https://github.com/MystenLabs/mysten-sim.git", rev = "9c6636c399d5c60a1759f1670b1c07b3d408799a", package = "msim-macros" } multiaddr = "0.17.0" nexlint = { git = "https://github.com/nextest-rs/nexlint.git", rev = "7ce56bd591242a57660ed05f14ca2483c37d895b" } nexlint-lints = { git = "https://github.com/nextest-rs/nexlint.git", rev = "7ce56bd591242a57660ed05f14ca2483c37d895b" } @@ -577,13 +579,12 @@ mamoru-sui-types = { git = "https://github.com/Mamoru-Foundation/mamoru-core", r #mamoru-sniffer = { path = "../mamoru-core/mamoru-sniffer" } #mamoru-sui-types = { path = "../mamoru-core/blockchain-types/mamoru-sui-types" } -fastcrypto = { git = "https://github.com/MystenLabs/fastcrypto", rev = "3366c26a746b72707572c4f3f05915e20d5c16c2" } -fastcrypto-tbls = { git = "https://github.com/MystenLabs/fastcrypto", rev = "3366c26a746b72707572c4f3f05915e20d5c16c2" } -fastcrypto-zkp = { git = "https://github.com/MystenLabs/fastcrypto", rev = "3366c26a746b72707572c4f3f05915e20d5c16c2", package = "fastcrypto-zkp" } -fastcrypto-vdf = { git = "https://github.com/MystenLabs/fastcrypto", rev = "3366c26a746b72707572c4f3f05915e20d5c16c2", features = [ +fastcrypto = { git = "https://github.com/MystenLabs/fastcrypto", rev = "c050ffc78b93739328af5d59b05f90e0e26b1b7e" } +fastcrypto-tbls = { git = "https://github.com/MystenLabs/fastcrypto", rev = "c050ffc78b93739328af5d59b05f90e0e26b1b7e" } +fastcrypto-zkp = { git = "https://github.com/MystenLabs/fastcrypto", rev = "c050ffc78b93739328af5d59b05f90e0e26b1b7e", package = "fastcrypto-zkp" } +fastcrypto-vdf = { git = "https://github.com/MystenLabs/fastcrypto", rev = "c050ffc78b93739328af5d59b05f90e0e26b1b7e", features = [ "experimental", ] } - passkey-types = { version = "0.2.0" } passkey-client = { version = "0.2.0" } passkey-authenticator = { version = "0.2.0" } @@ -597,11 +598,8 @@ anemo-cli = { git = "https://github.com/mystenlabs/anemo.git", rev = "e609f7697e anemo-tower = { git = "https://github.com/mystenlabs/anemo.git", rev = "e609f7697ed6169bf0760882a0b6c032a57e4f3b" } # core-types with json format for REST api -sui-sdk2 = { package = "sui-sdk", git = "https://github.com/mystenlabs/sui-rust-sdk.git", rev = "bd233b6879b917fb95e17f21927c198e7a60c924", features = [ - "hash", - "serde", - "schemars", -] } +# sui-sdk-types = { version = "0.0.1", features = ["hash", "serde", "schemars"] } +sui-sdk-types = { git = "https://github.com/MystenLabs/sui-rust-sdk.git", rev = "31bd9da32a8057edc45da8f5e6a8f25b83919b93", features = ["hash", "serde", "schemars"] } ### Workspace Members ### anemo-benchmark = { path = "crates/anemo-benchmark" } @@ -623,6 +621,7 @@ sui-archival = { path = "crates/sui-archival" } sui-authority-aggregation = { path = "crates/sui-authority-aggregation" } sui-benchmark = { path = "crates/sui-benchmark" } sui-bridge = { path = "crates/sui-bridge" } +sui-bridge-watchdog = { path = "crates/sui-bridge-watchdog" } sui-cluster-test = { path = "crates/sui-cluster-test" } sui-config = { path = "crates/sui-config" } sui-core = { path = "crates/sui-core" } diff --git a/README.md b/README.md index 1323054a4f9f8..b203dfa32e418 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

-Logo +Logo

# Welcome to Sui diff --git a/apps/core/src/hooks/useFormatCoin.ts b/apps/core/src/hooks/useFormatCoin.ts index bd7b1317c765f..a4ed34fcc987f 100644 --- a/apps/core/src/hooks/useFormatCoin.ts +++ b/apps/core/src/hooks/useFormatCoin.ts @@ -1,6 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +import { useFeatureValue } from '@growthbook/growthbook-react'; import { useSuiClient } from '@mysten/dapp-kit'; import { CoinMetadata } from '@mysten/sui/client'; import { SUI_TYPE_ARG } from '@mysten/sui/utils'; @@ -44,8 +45,21 @@ const ELLIPSIS = '\u{2026}'; const SYMBOL_TRUNCATE_LENGTH = 5; const NAME_TRUNCATE_LENGTH = 10; +type CoinMetadataOverrides = { + [coinType: string]: { + name?: string; + iconUrl?: string; + symbol?: string; + }; +}; + export function useCoinMetadata(coinType?: string | null) { const client = useSuiClient(); + const tokenMetadataOverrides = useFeatureValue( + 'token-metadata-overrides', + {}, + ); + return useQuery({ queryKey: ['coin-metadata', coinType], queryFn: async () => { @@ -72,16 +86,29 @@ export function useCoinMetadata(coinType?: string | null) { select(data) { if (!data) return null; + const symbol = + coinType && tokenMetadataOverrides[coinType]?.symbol + ? tokenMetadataOverrides[coinType].symbol + : data.symbol; + const name = + coinType && tokenMetadataOverrides[coinType]?.name + ? tokenMetadataOverrides[coinType].name + : data.name; + return { ...data, + iconUrl: + coinType && tokenMetadataOverrides[coinType]?.iconUrl + ? tokenMetadataOverrides[coinType].iconUrl + : data.iconUrl, symbol: - data.symbol.length > SYMBOL_TRUNCATE_LENGTH - ? data.symbol.slice(0, SYMBOL_TRUNCATE_LENGTH) + ELLIPSIS - : data.symbol, + symbol.length > SYMBOL_TRUNCATE_LENGTH + ? symbol.slice(0, SYMBOL_TRUNCATE_LENGTH) + ELLIPSIS + : symbol, name: - data.name.length > NAME_TRUNCATE_LENGTH - ? data.name.slice(0, NAME_TRUNCATE_LENGTH) + ELLIPSIS - : data.name, + name.length > NAME_TRUNCATE_LENGTH + ? name.slice(0, NAME_TRUNCATE_LENGTH) + ELLIPSIS + : name, }; }, retry: false, diff --git a/apps/core/src/utils/transaction/getBalanceChangeSummary.ts b/apps/core/src/utils/transaction/getBalanceChangeSummary.ts index 9f89165a2c367..51fe8b51067b6 100644 --- a/apps/core/src/utils/transaction/getBalanceChangeSummary.ts +++ b/apps/core/src/utils/transaction/getBalanceChangeSummary.ts @@ -18,7 +18,7 @@ export type BalanceChange = { export type BalanceChangeByOwner = Record; export type BalanceChangeSummary = BalanceChangeByOwner | null; -function getOwnerAddress(owner: ObjectOwner): string { +export function getOwnerAddress(owner: ObjectOwner): string { if (typeof owner === 'object') { if ('AddressOwner' in owner) { return owner.AddressOwner; diff --git a/apps/wallet/src/background/accounts/index.ts b/apps/wallet/src/background/accounts/index.ts index a8feb5089c961..a57b4f379cfbe 100644 --- a/apps/wallet/src/background/accounts/index.ts +++ b/apps/wallet/src/background/accounts/index.ts @@ -7,7 +7,7 @@ import { type MethodPayload, } from '_src/shared/messaging/messages/payloads/MethodPayload'; import { type WalletStatusChange } from '_src/shared/messaging/messages/payloads/wallet-status-change'; -import { fromB64 } from '@mysten/sui/utils'; +import { fromBase64 } from '@mysten/sui/utils'; import Dexie from 'dexie'; import { getAccountSourceByID } from '../account-sources'; @@ -236,7 +236,7 @@ export async function accountsHandleUIMessage(msg: Message, uiConnection: UiConn { type: 'method-payload', method: 'signDataResponse', - args: { signature: await account.signData(fromB64(data)) }, + args: { signature: await account.signData(fromBase64(data)) }, }, msg.id, ), diff --git a/apps/wallet/src/dapp-interface/WalletStandardInterface.ts b/apps/wallet/src/dapp-interface/WalletStandardInterface.ts index 739507c8b94c7..389dcc6dccb75 100644 --- a/apps/wallet/src/dapp-interface/WalletStandardInterface.ts +++ b/apps/wallet/src/dapp-interface/WalletStandardInterface.ts @@ -30,7 +30,7 @@ import { type SignMessageRequest } from '_src/shared/messaging/messages/payloads import { isWalletStatusChangePayload } from '_src/shared/messaging/messages/payloads/wallet-status-change'; import { bcs } from '@mysten/sui/bcs'; import { isTransaction } from '@mysten/sui/transactions'; -import { fromB64, toB64 } from '@mysten/sui/utils'; +import { fromBase64, toBase64 } from '@mysten/sui/utils'; import { ReadonlyWalletAccount, SUI_CHAINS, @@ -171,7 +171,7 @@ export class SuiWallet implements Wallet { new ReadonlyWalletAccount({ address, label: nickname || undefined, - publicKey: publicKey ? fromB64(publicKey) : new Uint8Array(), + publicKey: publicKey ? fromBase64(publicKey) : new Uint8Array(), chains: this.#activeChain ? [this.#activeChain] : [], features: ['sui:signAndExecuteTransaction'], }), @@ -334,7 +334,7 @@ export class SuiWallet implements Wallet { txSignatures: [signature], intentMessage: { value: bcsTransaction }, }, - ] = bcs.SenderSignedData.parse(fromB64(rawTransaction!)); + ] = bcs.SenderSignedData.parse(fromBase64(rawTransaction!)); const bytes = bcs.TransactionData.serialize(bcsTransaction).toBase64(); @@ -342,7 +342,7 @@ export class SuiWallet implements Wallet { digest, signature, bytes, - effects: toB64(new Uint8Array(rawEffects!)), + effects: toBase64(new Uint8Array(rawEffects!)), }; }, ); @@ -353,7 +353,7 @@ export class SuiWallet implements Wallet { this.#send({ type: 'sign-message-request', args: { - message: toB64(message), + message: toBase64(message), accountAddress: account.address, }, }), @@ -371,7 +371,7 @@ export class SuiWallet implements Wallet { this.#send({ type: 'sign-message-request', args: { - message: toB64(message), + message: toBase64(message), accountAddress: account.address, }, }), diff --git a/apps/wallet/src/shared/analytics/ampli/index.ts b/apps/wallet/src/shared/analytics/ampli/index.ts index f3f66275a6a66..2c52d691daa87 100644 --- a/apps/wallet/src/shared/analytics/ampli/index.ts +++ b/apps/wallet/src/shared/analytics/ampli/index.ts @@ -222,7 +222,7 @@ export interface ClickedSwapCoinProperties { */ sourceFlow: string; /** - * The total balance in SUI of the selected coin that the user has. + * The total balance of the selected coin that the user has. * * | Rule | Value | * |---|---| @@ -246,6 +246,15 @@ export interface ClickedUnstakeSuiProperties { validatorAddress: string; } +export interface ClickedUsdcPromoBannerProperties { + /** + * | Rule | Value | + * |---|---| + * | Item Type | string | + */ + wUsdcInAccount: string[]; +} + export interface ConnectedHardwareWalletProperties { /** * The type of hardware wallet that was connected to. @@ -327,6 +336,26 @@ export interface PinnedCoinProperties { coinType: string; } +export interface ReceivedOnrampProvidersDataProperties { + countryCode?: string; + /** + * | Rule | Value | + * |---|---| + * | Item Type | string | + */ + providerNames: string[]; + region?: string; +} + +export interface ReceivedOnrampProvidersDataFailedProperties { + countryCode: string; + /** + * A message associated with an error event. + */ + errorMessage: string; + region?: string; +} + export interface RespondedToConnectionRequestProperties { /** * The name of the application that initiated the connection request. @@ -373,7 +402,7 @@ export interface SelectedCoinProperties { */ sourceFlow: string; /** - * The total balance in SUI of the selected coin that the user has. + * The total balance of the selected coin that the user has. * * | Rule | Value | * |---|---| @@ -401,6 +430,13 @@ export interface SelectedValidatorProperties { validatorName: string; } +export interface SentCoinFailedProperties { + /** + * A message associated with an error event. + */ + errorMessage: string; +} + export interface SentCoinsProperties { coinType: string; } @@ -423,6 +459,19 @@ export interface SentCollectibleFailedProperties { objectId: string; } +export interface SignedTransactionBlockFailedProperties { + /** + * | Rule | Value | + * |---|---| + * | Enum Values | sign, signAndExecute | + */ + action: 'sign' | 'signAndExecute'; + /** + * A message associated with an error event. + */ + errorMessage: string; +} + export interface StakedSuiProperties { /** * The amount of SUI staked. @@ -467,7 +516,7 @@ export interface SwappedCoinProperties { fromCoinType: string; toCoinType: string; /** - * The total balance in SUI of the selected coin that the user has. + * The total balance of the selected coin that the user has. * * | Rule | Value | * |---|---| @@ -490,7 +539,7 @@ export interface SwappedCoinFailedProperties { fromCoinType: string; toCoinType: string; /** - * The total balance in SUI of the selected coin that the user has. + * The total balance of the selected coin that the user has. * * | Rule | Value | * |---|---| @@ -525,10 +574,13 @@ export interface UnstakedSuiProperties { } export interface VisitedFiatOnRampProperties { + countryCode?: string; + isBestRate?: boolean; /** * The name of the fiat on-ramp provider. */ providerName: string; + region?: string; } export class Identify implements BaseEvent { @@ -647,6 +699,14 @@ export class ClickedUnstakeSui implements BaseEvent { } } +export class ClickedUsdcPromoBanner implements BaseEvent { + event_type = 'clicked usdc promo banner'; + + constructor(public event_properties: ClickedUsdcPromoBannerProperties) { + this.event_properties = event_properties; + } +} + export class ConnectedHardwareWallet implements BaseEvent { event_type = 'connected hardware wallet'; @@ -715,6 +775,22 @@ export class PinnedCoin implements BaseEvent { } } +export class ReceivedOnrampProvidersData implements BaseEvent { + event_type = 'received onramp providers data'; + + constructor(public event_properties: ReceivedOnrampProvidersDataProperties) { + this.event_properties = event_properties; + } +} + +export class ReceivedOnrampProvidersDataFailed implements BaseEvent { + event_type = 'Received onramp providers data (failed)'; + + constructor(public event_properties: ReceivedOnrampProvidersDataFailedProperties) { + this.event_properties = event_properties; + } +} + export class RespondedToConnectionRequest implements BaseEvent { event_type = 'responded to connection request'; @@ -747,6 +823,14 @@ export class SelectedValidator implements BaseEvent { } } +export class SentCoinFailed implements BaseEvent { + event_type = 'sent coin failed'; + + constructor(public event_properties: SentCoinFailedProperties) { + this.event_properties = event_properties; + } +} + export class SentCoins implements BaseEvent { event_type = 'sent coins'; @@ -771,6 +855,14 @@ export class SentCollectibleFailed implements BaseEvent { } } +export class SignedTransactionBlockFailed implements BaseEvent { + event_type = 'signed transaction block failed'; + + constructor(public event_properties: SignedTransactionBlockFailedProperties) { + this.event_properties = event_properties; + } +} + export class StakedSui implements BaseEvent { event_type = 'staked SUI'; @@ -1227,6 +1319,23 @@ export class Ampli { return this.track(new ClickedUnstakeSui(properties), options); } + /** + * clicked usdc promo banner + * + * [View in Tracking Plan](https://data.amplitude.com/mystenlabs/Sui%20Wallet/events/main/latest/clicked%20usdc%20promo%20banner) + * + * Event has no description in tracking plan. + * + * @param properties The event's properties (e.g. wUsdcInAccount) + * @param options Amplitude event options. + */ + clickedUsdcPromoBanner( + properties: ClickedUsdcPromoBannerProperties, + options?: EventOptions, + ) { + return this.track(new ClickedUsdcPromoBanner(properties), options); + } + /** * connected hardware wallet * @@ -1394,6 +1503,40 @@ export class Ampli { return this.track(new PinnedCoin(properties), options); } + /** + * received onramp providers data + * + * [View in Tracking Plan](https://data.amplitude.com/mystenlabs/Sui%20Wallet/events/main/latest/received%20onramp%20providers%20data) + * + * Event to track when data from onramp providers is received + * + * @param properties The event's properties (e.g. countryCode) + * @param options Amplitude event options. + */ + receivedOnrampProvidersData( + properties: ReceivedOnrampProvidersDataProperties, + options?: EventOptions, + ) { + return this.track(new ReceivedOnrampProvidersData(properties), options); + } + + /** + * Received onramp providers data (failed) + * + * [View in Tracking Plan](https://data.amplitude.com/mystenlabs/Sui%20Wallet/events/main/latest/Received%20onramp%20providers%20data%20(failed)) + * + * Event to track when data from onramp providers is received + * + * @param properties The event's properties (e.g. countryCode) + * @param options Amplitude event options. + */ + receivedOnrampProvidersDataFailed( + properties: ReceivedOnrampProvidersDataFailedProperties, + options?: EventOptions, + ) { + return this.track(new ReceivedOnrampProvidersDataFailed(properties), options); + } + /** * responded to connection request * @@ -1470,6 +1613,23 @@ export class Ampli { return this.track(new SelectedValidator(properties), options); } + /** + * sent coin failed + * + * [View in Tracking Plan](https://data.amplitude.com/mystenlabs/Sui%20Wallet/events/main/latest/sent%20coin%20failed) + * + * Event to track errors encountered while sending coins + * + * @param properties The event's properties (e.g. errorMessage) + * @param options Amplitude event options. + */ + sentCoinFailed( + properties: SentCoinFailedProperties, + options?: EventOptions, + ) { + return this.track(new SentCoinFailed(properties), options); + } + /** * sent coins * @@ -1523,6 +1683,23 @@ export class Ampli { return this.track(new SentCollectibleFailed(properties), options); } + /** + * signed transaction block failed + * + * [View in Tracking Plan](https://data.amplitude.com/mystenlabs/Sui%20Wallet/events/main/latest/signed%20transaction%20block%20failed) + * + * Event to track errors encountered while signing a transaction block + * + * @param properties The event's properties (e.g. action) + * @param options Amplitude event options. + */ + signedTransactionBlockFailed( + properties: SignedTransactionBlockFailedProperties, + options?: EventOptions, + ) { + return this.track(new SignedTransactionBlockFailed(properties), options); + } + /** * staked SUI * @@ -1695,7 +1872,7 @@ export class Ampli { * * Owner: Jon Shek * - * @param properties The event's properties (e.g. providerName) + * @param properties The event's properties (e.g. countryCode) * @param options Amplitude event options. */ visitedFiatOnRamp( diff --git a/apps/wallet/src/shared/utils/from-exported-keypair.ts b/apps/wallet/src/shared/utils/from-exported-keypair.ts index 868a4cd62dfa5..e09927b161d27 100644 --- a/apps/wallet/src/shared/utils/from-exported-keypair.ts +++ b/apps/wallet/src/shared/utils/from-exported-keypair.ts @@ -10,7 +10,7 @@ import { import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; import { Secp256k1Keypair } from '@mysten/sui/keypairs/secp256k1'; import { Secp256r1Keypair } from '@mysten/sui/keypairs/secp256r1'; -import { fromB64 } from '@mysten/sui/utils'; +import { fromBase64 } from '@mysten/sui/utils'; /** * Wallet stored data might contain imported accounts with their keys stored in the previous format. @@ -31,7 +31,7 @@ export function fromExportedKeypair( if (!legacySupport) { throw new Error('Invalid type of secret key. A string value was expected.'); } - secretKey = fromB64(secret.privateKey); + secretKey = fromBase64(secret.privateKey); schema = secret.schema; } else { const decoded = decodeSuiPrivateKey(secret); diff --git a/apps/wallet/src/shared/utils/index.ts b/apps/wallet/src/shared/utils/index.ts index 04bdea8fbc6d7..c982ed4e9ae0a 100644 --- a/apps/wallet/src/shared/utils/index.ts +++ b/apps/wallet/src/shared/utils/index.ts @@ -4,7 +4,7 @@ import { useAppSelector } from '_hooks'; import { setAttributes } from '_src/shared/experimentation/features'; import { useGrowthBook } from '@growthbook/growthbook-react'; -import { fromB64, toB64 } from '@mysten/sui/utils'; +import { fromBase64, toBase64 } from '@mysten/sui/utils'; import * as Sentry from '@sentry/browser'; import { useEffect } from 'react'; import Browser from 'webextension-polyfill'; @@ -77,8 +77,8 @@ export function toSearchQueryString(searchParams: URLSearchParams) { } export function toUtf8OrB64(message: string | Uint8Array) { - const messageBytes = typeof message === 'string' ? fromB64(message) : message; - let messageToReturn: string = typeof message === 'string' ? message : toB64(message); + const messageBytes = typeof message === 'string' ? fromBase64(message) : message; + let messageToReturn: string = typeof message === 'string' ? message : toBase64(message); let type: 'utf8' | 'base64' = 'base64'; try { messageToReturn = new TextDecoder('utf8', { fatal: true }).decode(messageBytes); diff --git a/apps/wallet/src/ui/app/QredoSigner.ts b/apps/wallet/src/ui/app/QredoSigner.ts index 3858baefed41f..e50900a21040a 100644 --- a/apps/wallet/src/ui/app/QredoSigner.ts +++ b/apps/wallet/src/ui/app/QredoSigner.ts @@ -10,7 +10,7 @@ import { } from '_src/shared/qredo-api'; import { type SuiClient } from '@mysten/sui/client'; import { messageWithIntent } from '@mysten/sui/cryptography'; -import { toB64 } from '@mysten/sui/utils'; +import { toBase64 } from '@mysten/sui/utils'; import mitt from 'mitt'; import { WalletSigner } from './WalletSigner'; @@ -86,7 +86,7 @@ export class QredoSigner extends WalletSigner { clientIdentifier, ); return { - messageBytes: toB64(input.message), + messageBytes: toBase64(input.message), signature, }; }; @@ -98,7 +98,7 @@ export class QredoSigner extends WalletSigner { clientIdentifier, ); return { - transactionBlockBytes: toB64(transactionBlockBytes), + transactionBlockBytes: toBase64(transactionBlockBytes), signature, }; }; @@ -157,7 +157,7 @@ export class QredoSigner extends WalletSigner { throw new Error(`Unsupported network ${networkNames[this.#apiEnv]}`); } const qredoTransaction = await this.#qredoAPI.createTransaction({ - messageWithIntent: toB64(intent), + messageWithIntent: toBase64(intent), network: this.#network, broadcast, from: await this.getAddress(), diff --git a/apps/wallet/src/ui/app/WalletSigner.ts b/apps/wallet/src/ui/app/WalletSigner.ts index d5df590a6b0ab..43bde1409db61 100644 --- a/apps/wallet/src/ui/app/WalletSigner.ts +++ b/apps/wallet/src/ui/app/WalletSigner.ts @@ -11,7 +11,7 @@ import { } from '@mysten/sui/client'; import { messageWithIntent } from '@mysten/sui/cryptography'; import { isTransaction, type Transaction } from '@mysten/sui/transactions'; -import { fromB64, toB64 } from '@mysten/sui/utils'; +import { fromBase64, toBase64 } from '@mysten/sui/utils'; export type SignedTransaction = { transactionBlockBytes: string; @@ -43,7 +43,7 @@ export abstract class WalletSigner { ); return { - messageBytes: toB64(input.message), + messageBytes: toBase64(input.message), signature, }; } @@ -59,7 +59,7 @@ export abstract class WalletSigner { } if (typeof transactionBlock === 'string') { - return fromB64(transactionBlock); + return fromBase64(transactionBlock); } if (transactionBlock instanceof Uint8Array) { @@ -78,7 +78,7 @@ export abstract class WalletSigner { const signature = await this.signData(messageWithIntent('TransactionData', bytes)); return { - transactionBlockBytes: toB64(bytes), + transactionBlockBytes: toBase64(bytes), signature, }; } diff --git a/apps/wallet/src/ui/app/background-client/index.ts b/apps/wallet/src/ui/app/background-client/index.ts index 669dbcbcc1ae6..44ba8c295c0c2 100644 --- a/apps/wallet/src/ui/app/background-client/index.ts +++ b/apps/wallet/src/ui/app/background-client/index.ts @@ -31,7 +31,7 @@ import { import { type SignedMessage, type SignedTransaction } from '_src/ui/app/WalletSigner'; import type { AppDispatch } from '_store'; import { type SuiTransactionBlockResponse } from '@mysten/sui/client'; -import { toB64 } from '@mysten/sui/utils'; +import { toBase64 } from '@mysten/sui/utils'; import { type QueryKey } from '@tanstack/react-query'; import { lastValueFrom, map, take } from 'rxjs'; @@ -156,7 +156,7 @@ export class BackgroundClient { createMessage>({ type: 'method-payload', method: 'signData', - args: { data: toB64(data), id: addressOrID }, + args: { data: toBase64(data), id: addressOrID }, }), ).pipe( take(1), diff --git a/apps/wallet/src/ui/app/components/address-input/index.tsx b/apps/wallet/src/ui/app/components/address-input/index.tsx index 115ee0195919a..02a408d9b9a85 100644 --- a/apps/wallet/src/ui/app/components/address-input/index.tsx +++ b/apps/wallet/src/ui/app/components/address-input/index.tsx @@ -132,7 +132,7 @@ export function AddressInput({ - {meta.touched && !isValidating ? ( + {field.value && !isValidating ? (
{warningData === RecipientWarningType.OBJECT ? ( diff --git a/apps/wallet/src/ui/app/components/buynlarge/HomePanel.tsx b/apps/wallet/src/ui/app/components/buynlarge/HomePanel.tsx index fce86deec0dc4..01af0ce74a1cd 100644 --- a/apps/wallet/src/ui/app/components/buynlarge/HomePanel.tsx +++ b/apps/wallet/src/ui/app/components/buynlarge/HomePanel.tsx @@ -25,11 +25,11 @@ export function BuyNLargeHomePanel() { return ( <> - {bnl.map((item) => { + {bnl.map((item, index) => { if (!item || !item.enabled || !item.asset || seen.includes(item?.objectType)) return null; return ( -
+
{ diff --git a/apps/wallet/src/ui/app/components/coin-icon/index.tsx b/apps/wallet/src/ui/app/components/coin-icon/index.tsx index 7a4fc107c09a3..d164aacb86ea1 100644 --- a/apps/wallet/src/ui/app/components/coin-icon/index.tsx +++ b/apps/wallet/src/ui/app/components/coin-icon/index.tsx @@ -4,7 +4,7 @@ import { ImageIcon } from '_app/shared/image-icon'; import { useCoinMetadata } from '@mysten/core'; import { Sui, Unstaked } from '@mysten/icons'; -import { SUI_TYPE_ARG } from '@mysten/sui/utils'; +import { normalizeStructTag, SUI_TYPE_ARG } from '@mysten/sui/utils'; import { cva, type VariantProps } from 'class-variance-authority'; import { useCoinMetadataOverrides } from '../../hooks/useCoinMetadataOverride'; @@ -43,7 +43,7 @@ function NonSuiCoin({ coinType }: NonSuiCoinProps) { const coinMetadataOverrides = useCoinMetadataOverrides(); return ( -
+
{coinMeta?.iconUrl ? ( { } export function CoinIcon({ coinType, ...styleProps }: CoinIconProps) { + const isSui = coinType + ? normalizeStructTag(coinType) === normalizeStructTag(SUI_TYPE_ARG) + : false; + return (
- {coinType === SUI_TYPE_ARG ? : } + {isSui ? : }
); } diff --git a/apps/wallet/src/ui/app/components/known-scam-overlay/useDappPreflight.ts b/apps/wallet/src/ui/app/components/known-scam-overlay/useDappPreflight.ts index 24e0ca0b9e6b5..02e556ec5a601 100644 --- a/apps/wallet/src/ui/app/components/known-scam-overlay/useDappPreflight.ts +++ b/apps/wallet/src/ui/app/components/known-scam-overlay/useDappPreflight.ts @@ -3,7 +3,7 @@ import { useAppsBackend } from '@mysten/core'; import { useSuiClient } from '@mysten/dapp-kit'; import { type Transaction } from '@mysten/sui/transactions'; -import { toB64 } from '@mysten/sui/utils'; +import { toBase64 } from '@mysten/sui/utils'; import { useQuery } from '@tanstack/react-query'; import { @@ -45,7 +45,7 @@ export function useDappPreflight({ if (requestType === RequestType.SIGN_TRANSACTION && transaction) { const transactionBytes = await transaction.build({ client }); - body.transactionBytes = toB64(transactionBytes); + body.transactionBytes = toBase64(transactionBytes); } return request( diff --git a/apps/wallet/src/ui/app/helpers/filterAndSortTokenBalances.ts b/apps/wallet/src/ui/app/helpers/filterAndSortTokenBalances.ts index 47ae3b0f3ad9b..6872ad3e8fcfa 100644 --- a/apps/wallet/src/ui/app/helpers/filterAndSortTokenBalances.ts +++ b/apps/wallet/src/ui/app/helpers/filterAndSortTokenBalances.ts @@ -1,7 +1,9 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +import { USDC_TYPE_ARG } from '_pages/swap/utils'; import { type CoinBalance } from '@mysten/sui/client'; +import { SUI_TYPE_ARG } from '@mysten/sui/utils'; // Sort tokens by symbol and total balance // Move this to the API backend @@ -9,11 +11,23 @@ import { type CoinBalance } from '@mysten/sui/client'; export function filterAndSortTokenBalances(tokens: CoinBalance[]) { return tokens .filter((token) => Number(token.totalBalance) > 0) - .sort((a, b) => - (getCoinSymbol(a.coinType) + Number(a.totalBalance)).localeCompare( + .sort((a, b) => { + if (a.coinType === SUI_TYPE_ARG) { + return -1; + } + if (b.coinType === SUI_TYPE_ARG) { + return 1; + } + if (a.coinType === USDC_TYPE_ARG) { + return -1; + } + if (b.coinType === USDC_TYPE_ARG) { + return 1; + } + return (getCoinSymbol(a.coinType) + Number(a.totalBalance)).localeCompare( getCoinSymbol(b.coinType) + Number(b.totalBalance), - ), - ); + ); + }); } export function getCoinSymbol(coinTypeArg: string) { diff --git a/apps/wallet/src/ui/app/hooks/useGetAllBalances.ts b/apps/wallet/src/ui/app/hooks/useGetAllBalances.ts new file mode 100644 index 0000000000000..94d5b10a62ffd --- /dev/null +++ b/apps/wallet/src/ui/app/hooks/useGetAllBalances.ts @@ -0,0 +1,18 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +import { useCoinsReFetchingConfig } from '_app/hooks/useCoinsReFetchingConfig'; +import { useSuiClientQuery } from '@mysten/dapp-kit'; + +export function useGetAllBalances(owner: string) { + const { staleTime, refetchInterval } = useCoinsReFetchingConfig(); + + return useSuiClientQuery( + 'getAllBalances', + { owner: owner! }, + { + enabled: !!owner, + refetchInterval, + staleTime, + }, + ); +} diff --git a/apps/wallet/src/ui/app/hooks/useSupportedCoins.ts b/apps/wallet/src/ui/app/hooks/useSupportedCoins.ts new file mode 100644 index 0000000000000..65d8ee279c0ab --- /dev/null +++ b/apps/wallet/src/ui/app/hooks/useSupportedCoins.ts @@ -0,0 +1,13 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +import { useAppsBackend } from '@mysten/core'; +import { useQuery } from '@tanstack/react-query'; + +export function useSupportedCoins() { + const { request } = useAppsBackend(); + + return useQuery({ + queryKey: ['supported-coins-apps-backend'], + queryFn: async () => request<{ supported: string[] }>('swap/coins'), + }); +} diff --git a/apps/wallet/src/ui/app/hooks/useValidSwapTokensList.ts b/apps/wallet/src/ui/app/hooks/useValidSwapTokensList.ts new file mode 100644 index 0000000000000..fea5757201d5e --- /dev/null +++ b/apps/wallet/src/ui/app/hooks/useValidSwapTokensList.ts @@ -0,0 +1,68 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +import { useActiveAddress } from '_app/hooks/useActiveAddress'; +import { useGetAllBalances } from '_app/hooks/useGetAllBalances'; +import { useRecognizedPackages } from '_app/hooks/useRecognizedPackages'; +import { useSupportedCoins } from '_app/hooks/useSupportedCoins'; +import { type CoinBalance } from '@mysten/sui/client'; +import { + normalizeStructTag, + normalizeSuiObjectId, + parseStructTag, + SUI_TYPE_ARG, +} from '@mysten/sui/utils'; +import { useMemo } from 'react'; + +export function filterTokenBalances(tokens: CoinBalance[]) { + return tokens.filter( + (token) => Number(token.totalBalance) > 0 || token.coinType === SUI_TYPE_ARG, + ); +} + +export function useValidSwapTokensList() { + const address = useActiveAddress(); + const { data, isLoading: isSupportedCoinsLoading } = useSupportedCoins(); + const { data: rawCoinBalances, isLoading: isGetAllBalancesLoading } = useGetAllBalances( + address || '', + ); + const packages = useRecognizedPackages(); + const normalizedPackages = useMemo( + () => packages.map((id) => normalizeSuiObjectId(id)), + [packages], + ); + + const supported = useMemo( + () => + data?.supported.filter((type) => normalizedPackages.includes(parseStructTag(type).address)), + [data, normalizedPackages], + ); + + const coinBalances = useMemo( + () => (rawCoinBalances ? filterTokenBalances(rawCoinBalances) : null), + [rawCoinBalances], + ); + + const validSwaps = useMemo( + () => + supported?.sort((a, b) => { + const suiType = normalizeStructTag(SUI_TYPE_ARG); + const balanceA = BigInt( + coinBalances?.find( + (balance) => normalizeStructTag(balance.coinType) === normalizeStructTag(a), + )?.totalBalance ?? 0, + ); + const balanceB = BigInt( + coinBalances?.find( + (balance) => normalizeStructTag(balance.coinType) === normalizeStructTag(b), + )?.totalBalance ?? 0, + ); + return a === suiType ? -1 : b === suiType ? 1 : Number(balanceB - balanceA); + }) ?? [], + [supported, coinBalances], + ); + + return { + isLoading: isSupportedCoinsLoading || isGetAllBalancesLoading, + data: validSwaps, + }; +} diff --git a/apps/wallet/src/ui/app/index.tsx b/apps/wallet/src/ui/app/index.tsx index 2c289f192bcef..318ca3b529009 100644 --- a/apps/wallet/src/ui/app/index.tsx +++ b/apps/wallet/src/ui/app/index.tsx @@ -2,13 +2,14 @@ // SPDX-License-Identifier: Apache-2.0 import { useAppDispatch, useAppSelector } from '_hooks'; +import { UsdcPromo } from '_pages/home/usdc-promo/UsdcPromo'; import { SwapPage } from '_pages/swap'; -import { FromAssets } from '_pages/swap/FromAssets'; +import { CoinsSelectionPage } from '_pages/swap/CoinsSelectionPage'; import { setNavVisibility } from '_redux/slices/app'; import { isLedgerAccountSerializedUI } from '_src/background/accounts/LedgerAccount'; import { persistableStorage } from '_src/shared/analytics/amplitude'; import { type LedgerAccountsPublicKeys } from '_src/shared/messaging/messages/payloads/MethodPayload'; -import { toB64 } from '@mysten/sui/utils'; +import { toBase64 } from '@mysten/sui/utils'; import { useEffect, useMemo } from 'react'; import { Navigate, Outlet, Route, Routes, useLocation } from 'react-router-dom'; import { throttle } from 'throttle-debounce'; @@ -115,7 +116,7 @@ const App = () => { const { publicKey } = await suiLedgerClient.getPublicKey(derivationPath); publicKeysToStore.push({ accountID: id, - publicKey: toB64(publicKey), + publicKey: toBase64(publicKey), }); } catch (e) { // do nothing @@ -168,6 +169,7 @@ const App = () => { } /> } /> } /> + } /> } /> } /> } /> @@ -176,7 +178,7 @@ const App = () => { } /> } /> } /> - } /> + } /> } /> } /> } /> diff --git a/apps/wallet/src/ui/app/pages/approval-request/transaction-request/TransactionDetails/Command.tsx b/apps/wallet/src/ui/app/pages/approval-request/transaction-request/TransactionDetails/Command.tsx index 4ea1f50afa5b8..b232413b7ebce 100644 --- a/apps/wallet/src/ui/app/pages/approval-request/transaction-request/TransactionDetails/Command.tsx +++ b/apps/wallet/src/ui/app/pages/approval-request/transaction-request/TransactionDetails/Command.tsx @@ -4,7 +4,7 @@ import { Text } from '_src/ui/app/shared/text'; import { ChevronDown12, ChevronRight12 } from '@mysten/icons'; import { type Argument, type Commands, type TransactionData } from '@mysten/sui/transactions'; -import { toB64 } from '@mysten/sui/utils'; +import { toBase64 } from '@mysten/sui/utils'; import { useState } from 'react'; type TransactionType = TransactionData['commands'][0]; @@ -34,7 +34,7 @@ function convertCommandArgumentToString( if (Array.isArray(arg)) { // Publish transaction special casing: if (typeof arg[0] === 'number') { - return toB64(new Uint8Array(arg as number[])); + return toBase64(new Uint8Array(arg as number[])); } return `[${arg.map((argVal) => convertCommandArgumentToString(argVal)).join(', ')}]`; diff --git a/apps/wallet/src/ui/app/pages/home/interstitial/index.tsx b/apps/wallet/src/ui/app/pages/home/interstitial/index.tsx index 409a878474f70..86f3d2272550a 100644 --- a/apps/wallet/src/ui/app/pages/home/interstitial/index.tsx +++ b/apps/wallet/src/ui/app/pages/home/interstitial/index.tsx @@ -69,7 +69,7 @@ function Interstitial({ {bannerUrl && ( - interstitial-banner + interstitial-banner )} Send - {isRenderSwapButton && ( - { - ampli.clickedSwapCoin({ - coinType: coinBalance.coinType, - totalBalance: Number(formatted), - sourceFlow: 'TokenRow', - }); - }} - > - Swap - - )} + { + ampli.clickedSwapCoin({ + coinType: coinBalance.coinType, + totalBalance: Number(formatted), + sourceFlow: 'TokenRow', + }); + }} + > + Swap +
) : ( -
+
{symbol} @@ -191,19 +182,19 @@ export function TokenRow({
{balance > 0n && ( - + {formatted} {symbol} )} - {balanceInUsd && balanceInUsd > 0 && ( + {balanceInUsd && balanceInUsd > 0 ? ( {Number(balanceInUsd).toLocaleString('en', { style: 'currency', currency: 'USD', })} - )} + ) : null}
); @@ -429,6 +420,8 @@ function TokenDetails({ coinType }: TokenDetailsProps) { > + +
{ if (data?.MessageWithIntent) { - return fromB64(data.MessageWithIntent); + return fromBase64(data.MessageWithIntent); } return null; }, [data?.MessageWithIntent]); diff --git a/apps/wallet/src/ui/app/pages/home/usdc-promo/UsdcPromo.tsx b/apps/wallet/src/ui/app/pages/home/usdc-promo/UsdcPromo.tsx new file mode 100644 index 0000000000000..4278c317898e3 --- /dev/null +++ b/apps/wallet/src/ui/app/pages/home/usdc-promo/UsdcPromo.tsx @@ -0,0 +1,55 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +import { Button } from '_app/shared/ButtonUI'; +import { Heading } from '_app/shared/heading'; +import PageTitle from '_app/shared/PageTitle'; +import { Text } from '_app/shared/text'; +import { useUsdcPromo } from '_pages/home/usdc-promo/useUsdcPromo'; +import { USDC_TYPE_ARG } from '_pages/swap/utils'; +import { ampli } from '_shared/analytics/ampli'; +import { useNavigate, useSearchParams } from 'react-router-dom'; + +export function UsdcPromo() { + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const fromCoinType = searchParams.get('type'); + const presetAmount = searchParams.get('presetAmount'); + const { promoBannerSheetTitle, promoBannerSheetContent, ctaLabel } = useUsdcPromo(); + + return ( +
+ + USDC +
+ + {promoBannerSheetTitle} + + + {promoBannerSheetContent} + +
+
+ ); +} diff --git a/apps/wallet/src/ui/app/pages/home/usdc-promo/UsdcPromoBanner.tsx b/apps/wallet/src/ui/app/pages/home/usdc-promo/UsdcPromoBanner.tsx new file mode 100644 index 0000000000000..a7e11395014c5 --- /dev/null +++ b/apps/wallet/src/ui/app/pages/home/usdc-promo/UsdcPromoBanner.tsx @@ -0,0 +1,83 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +import { useGetAllBalances } from '_app/hooks/useGetAllBalances'; +import { Text } from '_app/shared/text'; +import { ButtonOrLink } from '_app/shared/utils/ButtonOrLink'; +import { useActiveAddress } from '_hooks'; +import { useUsdcPromo } from '_pages/home/usdc-promo/useUsdcPromo'; +import { ampli } from '_shared/analytics/ampli'; +import { useCoinMetadata } from '@mysten/core'; +import { type CoinBalance } from '@mysten/sui/client'; +import BigNumber from 'bignumber.js'; +import { useMemo } from 'react'; +import { useNavigate } from 'react-router-dom'; + +function useUsdcInUserBalance() { + const activeAccountAddress = useActiveAddress(); + const { wrappedUsdcList } = useUsdcPromo(); + + const { data: coinBalances } = useGetAllBalances(activeAccountAddress || ''); + + return coinBalances + ? coinBalances.filter( + (coin) => wrappedUsdcList.includes(coin.coinType) && Number(coin.totalBalance) > 0, + ) + : []; +} + +function BannerImage({ balance }: { balance: CoinBalance }) { + const navigate = useNavigate(); + const { promoBannerBackground, promoBannerText } = useUsdcPromo(); + const { data: metadata } = useCoinMetadata(balance.coinType); + const usdcInUsersBalance = useUsdcInUserBalance(); + + const maxBalance = useMemo(() => { + const decimals = metadata?.decimals ?? 0; + return new BigNumber(balance?.totalBalance || 0) + .shiftedBy(-decimals) + .decimalPlaces(decimals) + .toString(); + }, [balance, metadata]); + + return ( + { + ampli.clickedUsdcPromoBanner({ + wUsdcInAccount: usdcInUsersBalance.map((coin) => coin.coinType), + }); + navigate( + `/usdc-promo?${new URLSearchParams({ + type: balance.coinType, + presetAmount: maxBalance, + })}`, + ); + }} + > + USDC Promo +
+ USDC + + {promoBannerText} + +
+
+ ); +} + +export function UsdcPromoBanner() { + const { enabled } = useUsdcPromo(); + const usdcInUsersBalance = useUsdcInUserBalance(); + + const firstUsdcInUsersBalance = usdcInUsersBalance[0]; + + if (!enabled || !firstUsdcInUsersBalance) { + return null; + } + + return ; +} diff --git a/apps/wallet/src/ui/app/pages/home/usdc-promo/useUsdcPromo.ts b/apps/wallet/src/ui/app/pages/home/usdc-promo/useUsdcPromo.ts new file mode 100644 index 0000000000000..66992175f7f9e --- /dev/null +++ b/apps/wallet/src/ui/app/pages/home/usdc-promo/useUsdcPromo.ts @@ -0,0 +1,31 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +import { useFeatureIsOn, useFeatureValue } from '@growthbook/growthbook-react'; + +type WalletUsdcPromo = { + promoBannerImage: string; + promoBannerBackground: string; + promoBannerText: string; + promoBannerSheetTitle: string; + promoBannerSheetContent: string; + ctaLabel: string; + wrappedUsdcList: string[]; +}; + +export function useUsdcPromo() { + const enabled = useFeatureIsOn('wallet-usdc-promo-enabled'); + const dynamicConfigs = useFeatureValue('wallet-usdc-promo', { + promoBannerImage: '', + promoBannerBackground: '', + promoBannerText: '', + promoBannerSheetTitle: '', + promoBannerSheetContent: '', + ctaLabel: '', + wrappedUsdcList: [], + }); + + return { + ...dynamicConfigs, + enabled, + }; +} diff --git a/apps/wallet/src/ui/app/pages/swap/AssetData.tsx b/apps/wallet/src/ui/app/pages/swap/AssetData.tsx index c64eb46f58d80..a4fb234bd289b 100644 --- a/apps/wallet/src/ui/app/pages/swap/AssetData.tsx +++ b/apps/wallet/src/ui/app/pages/swap/AssetData.tsx @@ -1,51 +1,58 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +import { useActiveAccount } from '_app/hooks/useActiveAccount'; import { Heading } from '_app/shared/heading'; import { Text } from '_app/shared/text'; import { ButtonOrLink } from '_app/shared/utils/ButtonOrLink'; import { CoinIcon } from '_components/coin-icon'; import { DescriptionItem } from '_pages/approval-request/transaction-request/DescriptionList'; +import { useGetBalance } from '_pages/swap/utils'; +import { useCoinMetadata } from '@mysten/core'; import { ChevronDown16 } from '@mysten/icons'; export function AssetData({ - tokenBalance, coinType, - symbol, to, onClick, disabled, }: { - tokenBalance: string; coinType: string; - symbol: string; to?: string; onClick?: () => void; disabled?: boolean; }) { + const activeAccount = useActiveAccount(); + const currentAddress = activeAccount?.address; + + const { data: balance } = useGetBalance({ + coinType, + owner: currentAddress, + }); + + const { data: coinMetadata } = useCoinMetadata(coinType); + return ( - - - - {symbol} - - {!disabled && } - -
+ + {!!coinType && } + + {coinMetadata?.symbol || 'Select coin'} + + {!disabled && } + } > - {!!tokenBalance && ( -
+ {!!balance && ( +
Balance
{' '} - {tokenBalance} {symbol} + {balance?.formatted} {coinMetadata?.symbol}
)} diff --git a/apps/wallet/src/ui/app/pages/swap/CoinsSelectionPage.tsx b/apps/wallet/src/ui/app/pages/swap/CoinsSelectionPage.tsx new file mode 100644 index 0000000000000..f49bb2e9edacd --- /dev/null +++ b/apps/wallet/src/ui/app/pages/swap/CoinsSelectionPage.tsx @@ -0,0 +1,78 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { useGetAllBalances } from '_app/hooks/useGetAllBalances'; +import { useValidSwapTokensList } from '_app/hooks/useValidSwapTokensList'; +import Loading from '_components/loading'; +import Overlay from '_components/overlay'; +import { useActiveAddress, useSortedCoinsByCategories } from '_hooks'; +import { TokenRow } from '_pages/home/tokens/TokensDetails'; +import { normalizeStructTag } from '@mysten/sui/utils'; +import { Fragment } from 'react'; +import { useNavigate, useSearchParams } from 'react-router-dom'; + +export function CoinsSelectionPage() { + const navigate = useNavigate(); + const selectedAddress = useActiveAddress(); + const [searchParams] = useSearchParams(); + const fromCoinType = searchParams.get('fromCoinType'); + const toCoinType = searchParams.get('toCoinType'); + const source = searchParams.get('source'); + const currentAmount = searchParams.get('currentAmount'); + + const { data: swapFromTokensList, isLoading } = useValidSwapTokensList(); + const swapToTokensList = swapFromTokensList.filter((token) => { + if (!fromCoinType) { + return true; + } + return normalizeStructTag(token) !== normalizeStructTag(fromCoinType); + }); + + const allowedCoinTypes = source === 'fromCoinType' ? swapFromTokensList : swapToTokensList; + + const { data: coinBalances, isPending } = useGetAllBalances(selectedAddress || ''); + + const { recognized } = useSortedCoinsByCategories(coinBalances ?? []); + + return ( + navigate(-1)}> + +
+ {allowedCoinTypes.map((coinType, index) => { + const coinBalance = recognized?.find((coin) => coin.coinType === coinType) || {}; + const totalBalance = + coinBalances?.find( + (balance) => normalizeStructTag(balance.coinType) === normalizeStructTag(coinType), + )?.totalBalance ?? '0'; + + return ( + + { + const params = fromCoinType + ? { type: fromCoinType, toType: coinType, presetAmount: currentAmount || '0' } + : { + type: coinType, + toType: toCoinType || '', + presetAmount: currentAmount || '0', + }; + navigate(`/swap?${new URLSearchParams(params)}`); + }} + /> + +
+ + ); + })} +
+ + + ); +} diff --git a/apps/wallet/src/ui/app/pages/swap/FromAssets.tsx b/apps/wallet/src/ui/app/pages/swap/FromAssets.tsx deleted file mode 100644 index 4a22562c883ca..0000000000000 --- a/apps/wallet/src/ui/app/pages/swap/FromAssets.tsx +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -import Loading from '_components/loading'; -import Overlay from '_components/overlay'; -import { filterAndSortTokenBalances } from '_helpers'; -import { - useActiveAddress, - useAllowedSwapCoinsList, - useCoinsReFetchingConfig, - useSortedCoinsByCategories, -} from '_hooks'; -import { TokenRow } from '_pages/home/tokens/TokensDetails'; -import { DeepBookContextProvider } from '_shared/deepBook/context'; -import { useSuiClientQuery } from '@mysten/dapp-kit'; -import { Fragment } from 'react'; -import { useNavigate } from 'react-router-dom'; - -function FromAssetsComponent() { - const navigate = useNavigate(); - const selectedAddress = useActiveAddress(); - const { staleTime, refetchInterval } = useCoinsReFetchingConfig(); - - const { data: coins, isPending } = useSuiClientQuery( - 'getAllBalances', - { owner: selectedAddress! }, - { - enabled: !!selectedAddress, - refetchInterval, - staleTime, - select: filterAndSortTokenBalances, - }, - ); - - const { recognized } = useSortedCoinsByCategories(coins ?? []); - const allowedSwapCoinsList = useAllowedSwapCoinsList(); - - const renderedRecognizedCoins = recognized.filter(({ coinType }) => - allowedSwapCoinsList.includes(coinType), - ); - - return ( - navigate(-1)}> - -
- {renderedRecognizedCoins?.map((coinBalance, index) => { - return ( - - { - navigate( - `/swap?${new URLSearchParams({ type: coinBalance.coinType }).toString()}`, - ); - }} - /> - - {index !== recognized.length - 1 &&
} - - ); - })} -
- - - ); -} - -export function FromAssets() { - return ( - - - - ); -} diff --git a/apps/wallet/src/ui/app/pages/swap/GasFeesSummary.tsx b/apps/wallet/src/ui/app/pages/swap/GasFeesSummary.tsx new file mode 100644 index 0000000000000..3fc0ad8869b9c --- /dev/null +++ b/apps/wallet/src/ui/app/pages/swap/GasFeesSummary.tsx @@ -0,0 +1,63 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { Text } from '_app/shared/text'; +import { DescriptionItem } from '_pages/approval-request/transaction-request/DescriptionList'; +import { getGasSummary, useCoinMetadata, useFormatCoin } from '@mysten/core'; +import { type DryRunTransactionBlockResponse } from '@mysten/sui/client'; +import { SUI_TYPE_ARG } from '@mysten/sui/utils'; +import { useMemo } from 'react'; + +interface GasFeesSummaryProps { + transaction?: DryRunTransactionBlockResponse; + feePercentage?: number; + accessFees?: string; + accessFeeType?: string; +} + +export function GasFeesSummary({ + transaction, + feePercentage, + accessFees, + accessFeeType, +}: GasFeesSummaryProps) { + const gasSummary = useMemo(() => { + if (!transaction) return null; + return getGasSummary(transaction); + }, [transaction]); + const totalGas = gasSummary?.totalGas; + const [gasAmount, gasSymbol] = useFormatCoin(totalGas, SUI_TYPE_ARG); + + const { data: accessFeeMetadata } = useCoinMetadata(accessFeeType); + + return ( +
+ + Access Fees ({feePercentage ? `${feePercentage * 100}%` : '--'}) + + } + > + + {accessFees ?? '--'} + {accessFeeMetadata?.symbol ? ` ${accessFeeMetadata.symbol}` : ''} + + + +
+ + + Estimated Gas Fee + + } + > + + {gasAmount ? `${gasAmount} ${gasSymbol}` : '--'} + + +
+ ); +} diff --git a/apps/wallet/src/ui/app/pages/swap/ToAssetSection.tsx b/apps/wallet/src/ui/app/pages/swap/ToAssetSection.tsx deleted file mode 100644 index c35b3d67bbf50..0000000000000 --- a/apps/wallet/src/ui/app/pages/swap/ToAssetSection.tsx +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 -import { useRecognizedCoins } from '_app/hooks/deepbook'; -import { Button } from '_app/shared/ButtonUI'; -import { InputWithActionButton } from '_app/shared/InputWithAction'; -import { Text } from '_app/shared/text'; -import Alert from '_components/alert'; -import { AssetData } from '_pages/swap/AssetData'; -import { - Coins, - SUI_CONVERSION_RATE, - USDC_CONVERSION_RATE, - type FormValues, -} from '_pages/swap/constants'; -import { MaxSlippage, MaxSlippageModal } from '_pages/swap/MaxSlippage'; -import { ToAssets } from '_pages/swap/ToAssets'; -import { getUSDCurrency, useSwapData } from '_pages/swap/utils'; -import { useDeepBookContext } from '_shared/deepBook/context'; -import { type BalanceChange } from '@mysten/sui/client'; -import { SUI_TYPE_ARG } from '@mysten/sui/utils'; -import BigNumber from 'bignumber.js'; -import clsx from 'clsx'; -import { useEffect, useState } from 'react'; -import { useFormContext } from 'react-hook-form'; - -export function ToAssetSection({ - activeCoinType, - balanceChanges, - slippageErrorString, - baseCoinType, - quoteCoinType, - loading, - refetch, - error, -}: { - activeCoinType: string | null; - balanceChanges: BalanceChange[]; - slippageErrorString: string; - baseCoinType: string; - quoteCoinType: string; - loading: boolean; - refetch: () => void; - error: Error | null; -}) { - const coinsMap = useDeepBookContext().configs.coinsMap; - const recognizedCoins = useRecognizedCoins(); - const [isToAssetOpen, setToAssetOpen] = useState(false); - const [isSlippageModalOpen, setSlippageModalOpen] = useState(false); - const isAsk = activeCoinType === SUI_TYPE_ARG; - - const { formattedBaseBalance, formattedQuoteBalance, baseCoinMetadata, quoteCoinMetadata } = - useSwapData({ - baseCoinType, - quoteCoinType, - }); - - const toAssetBalance = isAsk ? formattedQuoteBalance : formattedBaseBalance; - const toAssetMetaData = isAsk ? quoteCoinMetadata : baseCoinMetadata; - - const { - watch, - setValue, - formState: { isValid }, - } = useFormContext(); - const toAssetType = watch('toAssetType'); - - const rawToAssetAmount = balanceChanges.find( - (balanceChange) => balanceChange.coinType === toAssetType, - )?.amount; - - const toAssetAmountAsNum = new BigNumber(rawToAssetAmount || '0') - .shiftedBy(isAsk ? -SUI_CONVERSION_RATE : -USDC_CONVERSION_RATE) - .toNumber(); - - useEffect(() => { - const newToAsset = isAsk ? coinsMap[Coins.USDC] : SUI_TYPE_ARG; - setValue('toAssetType', newToAsset); - }, [coinsMap, isAsk, setValue]); - - const toAssetSymbol = toAssetMetaData.data?.symbol ?? ''; - const amount = watch('amount'); - - if (!toAssetMetaData.data) { - return null; - } - - return ( -
- setToAssetOpen(false)} - onRowClick={(coinType) => { - setToAssetOpen(false); - }} - /> - { - setToAssetOpen(true); - }} - /> - - - {toAssetSymbol} - - ) - } - info={ - isValid && ( - - {getUSDCurrency(isAsk ? toAssetAmountAsNum : Number(amount))} - - ) - } - /> - - {isValid && toAssetAmountAsNum && amount ? ( -
- setSlippageModalOpen(true)} /> - - {slippageErrorString && ( -
- {slippageErrorString} -
- )} - - setSlippageModalOpen(false)} - /> -
- ) : null} - - {error && ( -
- - - Calculation failed - - {error.message || 'An error has occurred, try again.'} - -
- )} -
- ); -} diff --git a/apps/wallet/src/ui/app/pages/swap/ToAssets.tsx b/apps/wallet/src/ui/app/pages/swap/ToAssets.tsx deleted file mode 100644 index d022c8dee75c1..0000000000000 --- a/apps/wallet/src/ui/app/pages/swap/ToAssets.tsx +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -import Overlay from '_components/overlay'; -import { useActiveAddress, useCoinsReFetchingConfig } from '_hooks'; -import { TokenRow } from '_pages/home/tokens/TokensDetails'; -import { useSuiClientQuery } from '@mysten/dapp-kit'; -import { Fragment } from 'react'; -import { useSearchParams } from 'react-router-dom'; - -function ToAsset({ coinType, onClick }: { coinType: string; onClick: (coinType: string) => void }) { - const accountAddress = useActiveAddress(); - const [searchParams] = useSearchParams(); - const activeCoinType = searchParams.get('type'); - - const { staleTime, refetchInterval } = useCoinsReFetchingConfig(); - - const { data: coinBalance } = useSuiClientQuery( - 'getBalance', - { coinType: coinType, owner: accountAddress! }, - { enabled: !!accountAddress, refetchInterval, staleTime }, - ); - - if (!coinBalance || coinBalance.coinType === activeCoinType) { - return null; - } - - return ( - { - onClick(coinType); - }} - /> - ); -} - -export function ToAssets({ - onClose, - isOpen, - onRowClick, - recognizedCoins, -}: { - onClose: () => void; - isOpen: boolean; - onRowClick: (coinType: string) => void; - recognizedCoins: string[]; -}) { - return ( - -
- {recognizedCoins.map((coinType, index) => ( - - - {index !== recognizedCoins.length - 1 &&
} - - ))} -
- - ); -} diff --git a/apps/wallet/src/ui/app/pages/swap/index.tsx b/apps/wallet/src/ui/app/pages/swap/index.tsx index f33c3d26a8e80..93396613d06ab 100644 --- a/apps/wallet/src/ui/app/pages/swap/index.tsx +++ b/apps/wallet/src/ui/app/pages/swap/index.tsx @@ -1,338 +1,209 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 - import { useActiveAccount } from '_app/hooks/useActiveAccount'; -import { useRecognizedPackages } from '_app/hooks/useRecognizedPackages'; import { useSigner } from '_app/hooks/useSigner'; import BottomMenuLayout, { Content, Menu } from '_app/shared/bottom-menu-layout'; import { Button } from '_app/shared/ButtonUI'; import { Form } from '_app/shared/forms/Form'; +import { Heading } from '_app/shared/heading'; import { InputWithActionButton } from '_app/shared/InputWithAction'; import { Text } from '_app/shared/text'; import { ButtonOrLink } from '_app/shared/utils/ButtonOrLink'; -import Loading from '_components/loading'; +import Alert from '_components/alert'; +import LoadingIndicator from '_components/loading/LoadingIndicator'; import Overlay from '_components/overlay'; -import { filterAndSortTokenBalances } from '_helpers'; -import { - useAllowedSwapCoinsList, - useCoinsReFetchingConfig, - useGetEstimate, - useSortedCoinsByCategories, -} from '_hooks'; -import { AverageSection } from '_pages/swap/AverageSection'; -import { - Coins, - initialValues, - SUI_CONVERSION_RATE, - SUI_USDC_AVERAGE_CONVERSION_RATE, - USDC_CONVERSION_RATE, - type FormValues, -} from '_pages/swap/constants'; +import { parseAmount } from '_helpers'; +import { DescriptionItem } from '_pages/approval-request/transaction-request/DescriptionList'; +import { AssetData } from '_pages/swap/AssetData'; +import { GasFeesSummary } from '_pages/swap/GasFeesSummary'; +import { MaxSlippage, MaxSlippageModal } from '_pages/swap/MaxSlippage'; +import { useSwapTransaction } from '_pages/swap/useSwapTransaction'; import { - getAverageFromBalanceChanges, - getBalanceConversion, - getUSDCurrency, - isExceedingSlippageTolerance, - useSwapData, + DEFAULT_MAX_SLIPPAGE_PERCENTAGE, + formatSwapQuote, + maxSlippageFormSchema, + useCoinTypesFromRouteParams, + useGetBalance, } from '_pages/swap/utils'; import { ampli } from '_shared/analytics/ampli'; -import { DeepBookContextProvider, useDeepBookContext } from '_shared/deepBook/context'; -import { useTransactionSummary, useZodForm } from '@mysten/core'; -import { useSuiClientQuery } from '@mysten/dapp-kit'; +import { useFeatureValue } from '@growthbook/growthbook-react'; +import { useBalanceInUSD, useCoinMetadata, useZodForm } from '@mysten/core'; +import { useSuiClient } from '@mysten/dapp-kit'; import { ArrowDown12, ArrowRight16 } from '@mysten/icons'; -import { type DryRunTransactionBlockResponse } from '@mysten/sui/client'; -import { SUI_TYPE_ARG } from '@mysten/sui/utils'; +import { normalizeStructTag, SUI_TYPE_ARG } from '@mysten/sui/utils'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import BigNumber from 'bignumber.js'; import clsx from 'clsx'; -import { useEffect, useMemo, useState } from 'react'; -import { useWatch, type SubmitHandler } from 'react-hook-form'; +import { useMemo, useState } from 'react'; +import type { SubmitHandler } from 'react-hook-form'; import { useNavigate, useSearchParams } from 'react-router-dom'; import { z } from 'zod'; -import { AssetData } from './AssetData'; -import { GasFeeSection } from './GasFeeSection'; -import { ToAssetSection } from './ToAssetSection'; - -const MIN_INPUT = 0.1; - -enum ErrorStrings { - MISSING_DATA = 'Missing data', - SLIPPAGE_EXCEEDS_TOLERANCE = 'Current slippage exceeds tolerance', - NOT_ENOUGH_BALANCE = 'Not enough balance', -} - -function getSwapPageAtcText( - fromSymbol: string, - toAssetType: string, - coinsMap: Record, -) { - const toSymbol = - toAssetType === SUI_TYPE_ARG - ? Coins.SUI - : Object.entries(coinsMap).find(([key, value]) => value === toAssetType)?.[0] || ''; - - return `Swap ${fromSymbol} to ${toSymbol}`; -} - -export function SwapPageContent() { - const deepBookContext = useDeepBookContext(); - const [slippageErrorString, setSlippageErrorString] = useState(''); - const queryClient = useQueryClient(); - const mainnetPools = deepBookContext.configs.pools; +export function SwapPage() { const navigate = useNavigate(); - const [searchParams] = useSearchParams(); + const client = useSuiClient(); + const queryClient = useQueryClient(); const activeAccount = useActiveAccount(); const signer = useSigner(activeAccount); - const activeAccountAddress = activeAccount?.address; - const { staleTime, refetchInterval } = useCoinsReFetchingConfig(); - const coinsMap = deepBookContext.configs.coinsMap; - const deepBookClient = deepBookContext.client; - const accountCapId = deepBookContext.accountCapId; - const allowedSwapCoinsList = useAllowedSwapCoinsList(); - - const activeCoinType = searchParams.get('type'); - const isAsk = activeCoinType === SUI_TYPE_ARG; - - const baseCoinType = SUI_TYPE_ARG; - const quoteCoinType = coinsMap.USDC; - - const poolId = mainnetPools.SUI_USDC[0]; - - const { - baseCoinBalanceData, - quoteCoinBalanceData, - formattedBaseBalance, - formattedQuoteBalance, - baseCoinMetadata, - quoteCoinMetadata, - baseCoinSymbol, - quoteCoinSymbol, - isPending, - } = useSwapData({ - baseCoinType, - quoteCoinType, - }); - - const rawBaseBalance = baseCoinBalanceData?.totalBalance; - const rawQuoteBalance = quoteCoinBalanceData?.totalBalance; - - const { data: coinBalances } = useSuiClientQuery( - 'getAllBalances', - { owner: activeAccountAddress! }, - { - enabled: !!activeAccountAddress, - staleTime, - refetchInterval, - select: filterAndSortTokenBalances, - }, - ); - - const { recognized } = useSortedCoinsByCategories(coinBalances ?? []); - - const formattedBaseTokenBalance = formattedBaseBalance.replace(/,/g, ''); - - const formattedQuoteTokenBalance = formattedQuoteBalance.replace(/,/g, ''); - - const baseCoinDecimals = baseCoinMetadata.data?.decimals ?? 0; - const maxBaseBalance = rawBaseBalance || '0'; - - const quoteCoinDecimals = quoteCoinMetadata.data?.decimals ?? 0; - const maxQuoteBalance = rawQuoteBalance || '0'; + const [isSlippageModalOpen, setSlippageModalOpen] = useState(false); + const [searchParams] = useSearchParams(); + const currentAddress = activeAccount?.address; + const { fromCoinType, toCoinType } = useCoinTypesFromRouteParams(); + const defaultSlippage = useFeatureValue('defi-max-slippage', DEFAULT_MAX_SLIPPAGE_PERCENTAGE); + const maxSlippage = Number(searchParams.get('maxSlippage') || defaultSlippage); + const presetAmount = searchParams.get('presetAmount'); + const isSui = fromCoinType + ? normalizeStructTag(fromCoinType) === normalizeStructTag(SUI_TYPE_ARG) + : false; + const { data: fromCoinData } = useCoinMetadata(fromCoinType); const validationSchema = useMemo(() => { - return z.object({ - amount: z.string().transform((value, context) => { - const bigNumberValue = new BigNumber(value); - - if (!value.length) { - context.addIssue({ - code: 'custom', - message: 'Amount is required', + return z + .object({ + amount: z + .number({ + coerce: true, + invalid_type_error: 'Input must be number only', + }) + .pipe(z.coerce.string()), + }) + .merge(maxSlippageFormSchema) + .superRefine(async ({ amount }, ctx) => { + if (!fromCoinType) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Select a coin to swap from', }); return z.NEVER; } - if (bigNumberValue.lt(MIN_INPUT)) { - context.addIssue({ - code: 'custom', - message: `Minimum ${MIN_INPUT} ${isAsk ? baseCoinSymbol : quoteCoinSymbol}`, + const { totalBalance } = await client.getBalance({ + owner: currentAddress || '', + coinType: fromCoinType, + }); + const data = await client.getCoinMetadata({ coinType: fromCoinType }); + const bnAmount = new BigNumber(amount); + const bnMaxBalance = new BigNumber(totalBalance || 0).shiftedBy(-1 * (data?.decimals ?? 0)); + + if (bnAmount.isGreaterThan(bnMaxBalance)) { + ctx.addIssue({ + path: ['amount'], + code: z.ZodIssueCode.custom, + message: 'Insufficient balance', }); return z.NEVER; } - if (bigNumberValue.lt(0)) { - context.addIssue({ - code: 'custom', - message: 'Amount must be greater than 0', + if (!toCoinType) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Select a coin to swap to', }); return z.NEVER; } - const shiftedValue = isAsk ? baseCoinDecimals : quoteCoinDecimals; - const maxBalance = isAsk ? maxBaseBalance : maxQuoteBalance; - - if (bigNumberValue.shiftedBy(shiftedValue).gt(BigInt(maxBalance).toString())) { - context.addIssue({ - code: 'custom', - message: 'Not available in account', + if (!bnAmount.isFinite() || !bnAmount.isPositive()) { + ctx.addIssue({ + path: ['amount'], + code: z.ZodIssueCode.custom, + message: 'Expected a valid number', }); return z.NEVER; } - - return value; - }), - toAssetType: z.string(), - allowedMaxSlippagePercentage: z.string().transform((percent, context) => { - const numberPercent = Number(percent); - - if (numberPercent < 0 || numberPercent > 100) { - context.addIssue({ - code: 'custom', - message: 'Value must be between 0 and 100', + if (!bnAmount.gt(0)) { + ctx.addIssue({ + path: ['amount'], + code: z.ZodIssueCode.custom, + message: 'Value must be greater than 0', }); return z.NEVER; } + if (!fromCoinType || !toCoinType) { + return z.NEVER; + } + }); + }, [client, currentAddress, fromCoinType, toCoinType]); - return percent; - }), - }); - }, [ - isAsk, - baseCoinDecimals, - quoteCoinDecimals, - maxBaseBalance, - maxQuoteBalance, - baseCoinSymbol, - quoteCoinSymbol, - ]); + type FormType = z.infer; const form = useZodForm({ mode: 'all', schema: validationSchema, defaultValues: { - ...initialValues, - toAssetType: coinsMap.USDC, + allowedMaxSlippagePercentage: maxSlippage, + amount: presetAmount || '', }, }); const { - register, + watch, setValue, - control, handleSubmit, + register, reset, - formState: { isValid, isSubmitting, errors, isDirty }, + formState: { isValid: isFormValid, isSubmitting, errors }, } = form; - useEffect(() => { - if (isDirty) { - setSlippageErrorString(''); - } - }, [isDirty]); - - const renderButtonToCoinsList = useMemo(() => { - return ( - recognized.length > 1 && - recognized.some((coin) => allowedSwapCoinsList.includes(coin.coinType)) - ); - }, [allowedSwapCoinsList, recognized]); + const [allowedMaxSlippagePercentage, amount] = watch(['allowedMaxSlippagePercentage', 'amount']); - const amount = useWatch({ - name: 'amount', - control, + const { data: balance } = useGetBalance({ + coinType: fromCoinType!, + owner: currentAddress, }); - const baseBalance = amount && new BigNumber(amount).shiftedBy(USDC_CONVERSION_RATE).toString(); - const quoteBalance = amount && new BigNumber(amount).shiftedBy(SUI_CONVERSION_RATE).toString(); - - const isPayAll = amount === (isAsk ? formattedBaseTokenBalance : formattedQuoteTokenBalance); - - const atcText = useMemo(() => { - if (isAsk) { - return getSwapPageAtcText(baseCoinSymbol, quoteCoinType, coinsMap); - } - return getSwapPageAtcText(quoteCoinSymbol, baseCoinType, coinsMap); - }, [isAsk, baseCoinSymbol, baseCoinType, coinsMap, quoteCoinSymbol, quoteCoinType]); - + const GAS_RESERVE = 0.1; + const maxBalance = useMemo(() => { + const bnBalance = new BigNumber(balance?.totalBalance || 0).shiftedBy( + -1 * (fromCoinData?.decimals ?? 0), + ); + return isSui && bnBalance.gt(GAS_RESERVE) + ? bnBalance + .minus(GAS_RESERVE) + .decimalPlaces(fromCoinData?.decimals ?? 0) + .toString() + : bnBalance.decimalPlaces(fromCoinData?.decimals ?? 0).toString(); + }, [balance?.totalBalance, fromCoinData?.decimals, isSui]); + + const { data: toCoinData } = useCoinMetadata(toCoinType); + const fromCoinSymbol = fromCoinData?.symbol; + const toCoinSymbol = toCoinData?.symbol; + + const parsed = parseAmount(amount || '0', fromCoinData?.decimals || 0); + const isMaxBalance = new BigNumber(amount).isEqualTo(new BigNumber(maxBalance)); const { - error: estimateError, - data: dataFromEstimate, - isPending: dataFromEstimatePending, - isFetching: dataFromEstimateFetching, - isError: isDataFromEstimateError, - refetch: refetchEstimate, - } = useGetEstimate({ - signer, - accountCapId, - coinType: activeCoinType || '', - poolId, - baseBalance, - quoteBalance, - isAsk, - totalBaseBalance: formattedBaseTokenBalance, - totalQuoteBalance: formattedQuoteTokenBalance, - baseConversionRate: USDC_CONVERSION_RATE, - quoteConversionRate: SUI_CONVERSION_RATE, - enabled: isValid, - amount, - }); - - const recognizedPackagesList = useRecognizedPackages(); - - const txnSummary = useTransactionSummary({ - transaction: dataFromEstimate?.dryRunResponse as DryRunTransactionBlockResponse, - recognizedPackagesList, - currentAddress: activeAccountAddress, + data, + isPending: swapTransactionPending, + isLoading: swapTransactionLoading, + refetch, + error, + } = useSwapTransaction({ + sender: currentAddress, + fromType: fromCoinType || '', + toType: toCoinType || '', + amount: parsed.toString(), + slippage: Number(allowedMaxSlippagePercentage), + enabled: isFormValid && parsed > 0n && !!fromCoinType && !!toCoinType, + source: 'sui-wallet', }); - const totalGas = txnSummary?.gas?.totalGas; - const balanceChanges = dataFromEstimate?.dryRunResponse?.balanceChanges || []; - - const averages = getAverageFromBalanceChanges({ - balanceChanges, - baseCoinType, - quoteCoinType, - isAsk, - baseConversionRate: USDC_CONVERSION_RATE, - quoteConversionRate: SUI_CONVERSION_RATE, - }); - - const balance = getBalanceConversion({ - balance: new BigNumber(amount), - isAsk, - averages, - }); - - const formattedBalance = new BigNumber(balance) - .shiftedBy(isAsk ? SUI_USDC_AVERAGE_CONVERSION_RATE : -SUI_USDC_AVERAGE_CONVERSION_RATE) - .toNumber(); - - const { mutate: handleSwap, isPending: isSwapLoading } = useMutation({ - mutationFn: async (formData: FormValues) => { - const txn = dataFromEstimate?.txn; - - const isExceedingSlippage = await isExceedingSlippageTolerance({ - slipPercentage: formData.allowedMaxSlippagePercentage, - poolId, - deepBookClient, - conversionRate: USDC_CONVERSION_RATE, - isAsk, - average: averages.averageBaseToQuote, - }); - - if (!balanceChanges.length) { - throw new Error(ErrorStrings.NOT_ENOUGH_BALANCE); - } - - if (isExceedingSlippage) { - throw new Error(ErrorStrings.SLIPPAGE_EXCEEDS_TOLERANCE); - } + const swapData = useMemo(() => { + if (!data) return null; + return formatSwapQuote({ + result: data, + sender: currentAddress || '', + fromType: fromCoinType || '', + toType: toCoinType || '', + fromCoinDecimals: fromCoinData?.decimals ?? 0, + toCoinDecimals: toCoinData?.decimals ?? 0, + }); + }, [currentAddress, fromCoinType, toCoinType, fromCoinData, toCoinData, data]); - if (!txn || !signer) { - throw new Error(ErrorStrings.MISSING_DATA); - } + const toCoinBalanceInUSD = useBalanceInUSD(toCoinType || '', swapData?.toAmount ?? 0n); + const inputAmountInUSD = useBalanceInUSD(fromCoinType || '', parsed || 0n); + const { mutate: handleSwap, isPending: handleSwapPending } = useMutation({ + mutationFn: async (formData: FormType) => { + const txn = swapData?.transaction; return signer!.signAndExecuteTransactionBlock({ transactionBlock: txn!, options: { @@ -347,10 +218,10 @@ export function SwapPageContent() { queryClient.invalidateQueries({ queryKey: ['coin-balance'] }); ampli.swappedCoin({ - fromCoinType: isAsk ? baseCoinType : quoteCoinType, - toCoinType: isAsk ? quoteCoinType : baseCoinType, + fromCoinType: fromCoinType || '', + toCoinType: toCoinType || '', totalBalance: Number(amount), - estimatedReturnBalance: Number(formattedBalance), + estimatedReturnBalance: inputAmountInUSD || 0, }); const receiptUrl = `/receipt?txdigest=${encodeURIComponent( @@ -358,154 +229,213 @@ export function SwapPageContent() { )}&from=transactions`; return navigate(receiptUrl); }, - onError: (error: Error) => { - if (error.message === ErrorStrings.SLIPPAGE_EXCEEDS_TOLERANCE) { - setSlippageErrorString(error.message); - } - }, }); - const handleOnsubmit: SubmitHandler = (formData) => { + const handleOnsubmit: SubmitHandler = (formData) => { handleSwap(formData); }; + const showGasFeeBanner = !swapTransactionPending && swapData && isSui && isMaxBalance; + return ( navigate('/')}>
- - - -
+ + + +
+ +
+ + {isMaxBalance ? '~ ' : ''}$ + {new BigNumber(inputAmountInUSD || 0).toFixed(2)} + + ) + } + onActionClicked={() => { + setValue('amount', maxBalance, { shouldValidate: true }); + }} + /> +
+ {showGasFeeBanner && ( + + + {GAS_RESERVE} {fromCoinSymbol} has been set aside to cover estimated max gas + fees for this transaction + + + )} +
+ + { + navigate( + `/swap?${new URLSearchParams({ + type: toCoinType || '', + toType: fromCoinType || '', + }).toString()}`, + ); + reset(); + }} + > +
+
+ +
+
+ + +
- {activeCoinType && ( - - )} - -
- +
+ {swapTransactionLoading ? ( +
+ + + Calculating... + +
+ ) : ( +
+ + {swapData?.formattedToAmount ?? 0} + + + {toCoinSymbol} + +
- {isPayAll ? '~ ' : ''} - {getUSDCurrency(isAsk ? formattedBalance : Number(amount))} + ${new BigNumber(toCoinBalanceInUSD || 0).toFixed(2)} - ) - } - onActionClicked={() => { - setValue( - 'amount', - activeCoinType === SUI_TYPE_ARG - ? formattedBaseTokenBalance - : formattedQuoteTokenBalance, - { shouldValidate: true }, - ); - }} - /> +
+
+ )}
-
- { - navigate( - `/swap?${new URLSearchParams({ - type: activeCoinType === SUI_TYPE_ARG ? coinsMap.USDC : SUI_TYPE_ARG, - }).toString()}`, - ); - reset(); - }} - > -
-
- +
+ setSlippageModalOpen(true)} />
-
- + { + navigate( + `/swap?${new URLSearchParams({ + type: fromCoinType || '', + toType: toCoinType || '', + maxSlippage: allowedMaxSlippagePercentage.toString(), + }).toString()}`, + ); + setSlippageModalOpen(false); + }} + /> - + {error && ( +
+ + + Calculation failed + + + {error.message || 'An error has occurred, try again.'} + + +
+ )} +
- {isValid && ( -
- + {swapData?.estimatedRate && ( +
+ Estimated Rate}> + + 1 {fromCoinSymbol} ≈ {swapData?.estimatedRate} {toCoinSymbol} + +
)} -
- -
- - - - - - - + +
+ + + + + +
); } - -export function SwapPage() { - return ( - - - - ); -} diff --git a/apps/wallet/src/ui/app/pages/swap/useSwapTransaction.ts b/apps/wallet/src/ui/app/pages/swap/useSwapTransaction.ts new file mode 100644 index 0000000000000..b3cdf46d97c8e --- /dev/null +++ b/apps/wallet/src/ui/app/pages/swap/useSwapTransaction.ts @@ -0,0 +1,98 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +import { useSuiClient } from '@mysten/dapp-kit'; +import { type DryRunTransactionBlockResponse } from '@mysten/sui/client'; +import { useQuery, useQueryClient } from '@tanstack/react-query'; + +export type SwapRequest = { + amount: string; + fromType?: string; + slippage: number; + source: string; + sender?: string; + toType?: string; +}; + +export type SwapResponse = { + bytes: string; + error: string; + fee: { + percentage: number; + address: string; + }; + outAmount: string; + provider: string; +}; + +export type SwapResult = + | (SwapResponse & { + dryRunResponse: DryRunTransactionBlockResponse; + }) + | null; + +const getQueryKey = (params: SwapRequest) => ['swap', params]; + +async function* streamAsyncIterator(stream: ReadableStream): AsyncGenerator { + const reader = stream.getReader(); + const decoder = new TextDecoder('utf-8'); + let buffer = ''; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) break; + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split('\n'); + buffer = lines.pop() || ''; + + for (const line of lines) { + if (line.trim()) { + yield JSON.parse(line.trim()); + } + } + } + } finally { + reader.releaseLock(); + } +} + +export function useSwapTransaction({ enabled, ...params }: SwapRequest & { enabled: boolean }) { + const client = useSuiClient(); + const queryClient = useQueryClient(); + + return useQuery({ + queryKey: getQueryKey(params), + queryFn: async ({ signal }) => { + const response = await fetch('https://apps-backend.sui.io/swap', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(params), + signal, + }); + + if (!response.body || !response.ok) { + throw new Error(`Failed to fetch swap data ${response.statusText}`); + } + + for await (const swapResponse of streamAsyncIterator(response.body)) { + if (!swapResponse) continue; + if (swapResponse.error) throw new Error(swapResponse.error); + + const dryRunResponse = await client.dryRunTransactionBlock({ + transactionBlock: swapResponse.bytes, + }); + + queryClient.setQueryData(getQueryKey(params), { + dryRunResponse, + ...swapResponse, + }); + } + + return queryClient.getQueryData(getQueryKey(params)) ?? null; + }, + staleTime: 0, + enabled: enabled && !!params.amount && !!params.sender && !!params.fromType && !!params.toType, + }); +} diff --git a/apps/wallet/src/ui/app/pages/swap/utils.ts b/apps/wallet/src/ui/app/pages/swap/utils.ts index 571817989fcfe..676fd3148795c 100644 --- a/apps/wallet/src/ui/app/pages/swap/utils.ts +++ b/apps/wallet/src/ui/app/pages/swap/utils.ts @@ -2,11 +2,31 @@ // SPDX-License-Identifier: Apache-2.0 import { useActiveAccount } from '_app/hooks/useActiveAccount'; import { useCoinsReFetchingConfig } from '_hooks'; -import { roundFloat, useFormatCoin } from '@mysten/core'; +import { type SwapResult } from '_pages/swap/useSwapTransaction'; +import { useFeatureValue } from '@growthbook/growthbook-react'; +import { + CoinFormat, + formatBalance, + getBalanceChangeSummary, + getOwnerAddress, + roundFloat, + useCoinMetadata, + useFormatCoin, +} from '@mysten/core'; import { useSuiClientQuery } from '@mysten/dapp-kit'; -import { type DeepBookClient } from '@mysten/deepbook'; -import { type BalanceChange } from '@mysten/sui/client'; +import { type TransactionEffects } from '@mysten/sui/client'; +import { Transaction } from '@mysten/sui/transactions'; +import { normalizeStructTag, SUI_DECIMALS, SUI_TYPE_ARG } from '@mysten/sui/utils'; import BigNumber from 'bignumber.js'; +import { useSearchParams } from 'react-router-dom'; +import { z } from 'zod'; + +export const DEFAULT_MAX_SLIPPAGE_PERCENTAGE = 1; + +export const W_USDC_TYPE_ARG = + '0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN'; +export const USDC_TYPE_ARG = + '0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC'; export function useSwapData({ baseCoinType, @@ -67,113 +87,136 @@ export function getUSDCurrency(amount?: number | null) { }); } -export async function isExceedingSlippageTolerance({ - slipPercentage, - poolId, - deepBookClient, - conversionRate, - isAsk, - average, -}: { - slipPercentage: string; - poolId: string; - deepBookClient: DeepBookClient; - conversionRate: number; - isAsk: boolean; - average: string; -}) { - const convertedAverage = new BigNumber(average).shiftedBy(conversionRate).toString(); - - const { bestBidPrice, bestAskPrice } = await deepBookClient.getMarketPrice(poolId); - - if (!bestBidPrice || !bestAskPrice) { - return false; +export const maxSlippageFormSchema = z.object({ + allowedMaxSlippagePercentage: z + .number({ + coerce: true, + invalid_type_error: 'Input must be number only', + }) + .positive() + .max(100, 'Value must be between 0 and 100'), +}); + +export function useCoinTypesFromRouteParams() { + const [searchParams] = useSearchParams(); + const fromCoinType = searchParams.get('type'); + const toCoinType = searchParams.get('toType'); + + // Both are already defined, just use them: + if (fromCoinType && toCoinType) { + return { fromCoinType, toCoinType }; } - const slip = new BigNumber(isAsk ? bestBidPrice.toString() : bestAskPrice.toString()).dividedBy( - convertedAverage, - ); + // Neither is set, default to SUI -> USDC + if (!fromCoinType && !toCoinType) { + return { fromCoinType: SUI_TYPE_ARG, toCoinType: USDC_TYPE_ARG }; + } - return new BigNumber('1').minus(slip).abs().isGreaterThan(slipPercentage); + return { fromCoinType, toCoinType }; } -function getCoinsFromBalanceChanges(coinType: string, balanceChanges: BalanceChange[]) { - return balanceChanges - .filter((balance) => { - return balance.coinType === coinType; - }) - .sort((a, b) => { - const aAmount = new BigNumber(a.amount).abs(); - const bAmount = new BigNumber(b.amount).abs(); +export function useGetBalance({ coinType, owner }: { coinType?: string; owner?: string }) { + const { data: coinMetadata } = useCoinMetadata(coinType); + const refetchInterval = useFeatureValue('wallet-balance-refetch-interval', 20_000); - return aAmount.isGreaterThan(bAmount) ? -1 : 1; - }); + return useSuiClientQuery( + 'getBalance', + { + coinType, + owner: owner!, + }, + { + select: (data) => { + const formatted = formatBalance( + data.totalBalance, + coinMetadata?.decimals ?? 0, + CoinFormat.ROUNDED, + ); + + return { + ...data, + formatted, + }; + }, + refetchInterval, + staleTime: 5_000, + enabled: !!owner && !!coinType, + }, + ); } -export function getAverageFromBalanceChanges({ - balanceChanges, - baseCoinType, - quoteCoinType, - isAsk, - baseConversionRate, - quoteConversionRate, +export const getTotalGasCost = (effects: TransactionEffects) => { + return ( + BigInt(effects.gasUsed.computationCost) + + BigInt(effects.gasUsed.storageCost) - + BigInt(effects.gasUsed.storageRebate) + ); +}; + +export function formatSwapQuote({ + result, + sender, + fromType, + toType, + fromCoinDecimals, + toCoinDecimals, }: { - balanceChanges: BalanceChange[]; - baseCoinType: string; - quoteCoinType: string; - isAsk: boolean; - baseConversionRate: number; - quoteConversionRate: number; + fromCoinDecimals: number; + fromType?: string; + result: SwapResult; + sender: string; + toCoinDecimals: number; + toType?: string; }) { - const baseCoins = getCoinsFromBalanceChanges(baseCoinType, balanceChanges); - const quoteCoins = getCoinsFromBalanceChanges(quoteCoinType, balanceChanges); - - if (!baseCoins.length || !quoteCoins.length) { - return { - averageBaseToQuote: '0', - averageQuoteToBase: '0', - }; - } + if (!result || !fromType || !toType) return null; + + const { dryRunResponse, fee } = result; + const { balanceChanges } = dryRunResponse; + const summary = getBalanceChangeSummary(dryRunResponse, []); + const fromAmount = + summary?.[sender]?.find( + (bc) => normalizeStructTag(bc.coinType) === normalizeStructTag(fromType), + )?.amount ?? 0n; + const toAmount = + summary?.[sender]?.find((bc) => normalizeStructTag(bc.coinType) === normalizeStructTag(toType)) + ?.amount ?? 0n; + + const formattedToAmount = formatBalance(toAmount, toCoinDecimals); + + const estimatedRate = new BigNumber(toAmount.toString()) + .shiftedBy(fromCoinDecimals - toCoinDecimals) + .dividedBy(new BigNumber(fromAmount.toString()).abs()) + .toFormat(toCoinDecimals); + + const accessFeeBalanceChange = balanceChanges.find( + (bc) => ![fee.address, sender].includes(getOwnerAddress(bc.owner)), + ); - const baseCoinAmount = new BigNumber(baseCoins[0].amount).abs(); - const quoteCoinAmount = new BigNumber(quoteCoins[0].amount).abs(); - const feesAmount = new BigNumber(isAsk ? baseCoins[1]?.amount : quoteCoins[1]?.amount) - .shiftedBy(isAsk ? -baseConversionRate : -quoteConversionRate) - .abs(); + const accessFees = new BigNumber((accessFeeBalanceChange?.amount || 0n).toString()).shiftedBy( + -toCoinDecimals, + ); + const coinOut = new BigNumber(toAmount.toString()).shiftedBy(-toCoinDecimals); + const accessFeePercentage = accessFees.dividedBy(coinOut).multipliedBy(100).toFormat(3); - const baseAndFees = baseCoinAmount.plus(feesAmount); - const quoteAndFees = quoteCoinAmount.plus(feesAmount); + const estimatedToAmount = new BigNumber(toAmount.toString()) + .shiftedBy(-toCoinDecimals) + .minus(accessFees) + .toFormat(toCoinDecimals); - const averageQuoteToBase = baseCoinAmount - .dividedBy(isAsk ? quoteCoinAmount : quoteAndFees) - .toString(); - const averageBaseToQuote = quoteCoinAmount - .dividedBy(isAsk ? baseAndFees : baseCoinAmount) - .toString(); + const gas = formatBalance(getTotalGasCost(dryRunResponse.effects), SUI_DECIMALS); return { - averageBaseToQuote, - averageQuoteToBase, - }; -} - -export function getBalanceConversion({ - balance, - averages, - isAsk, -}: { - isAsk: boolean; - balance: BigInt | BigNumber | null; - averages: { - averageBaseToQuote: string; - averageQuoteToBase: string; + provider: result?.provider, + dryRunResponse, + transaction: Transaction.from(result.bytes), + estimatedRate, + formattedToAmount, + accessFeePercentage, + accessFees: accessFees.toFormat(toCoinDecimals), + accessFeeType: accessFeeBalanceChange?.coinType, + estimatedToAmount, + estimatedGas: gas, + toAmount: toAmount.toString(), + feePercentage: fee.percentage, }; -}) { - const bigNumberBalance = new BigNumber(balance?.toString() ?? '0'); - - if (isAsk) { - return bigNumberBalance.multipliedBy(averages.averageBaseToQuote).toString(); - } - - return bigNumberBalance.multipliedBy(averages.averageQuoteToBase).toString(); } diff --git a/apps/wallet/src/ui/app/redux/slices/transaction-requests/index.ts b/apps/wallet/src/ui/app/redux/slices/transaction-requests/index.ts index 082ba7a8e6ab7..a1c50666bc480 100644 --- a/apps/wallet/src/ui/app/redux/slices/transaction-requests/index.ts +++ b/apps/wallet/src/ui/app/redux/slices/transaction-requests/index.ts @@ -12,7 +12,7 @@ import { import type { AppThunkConfig } from '_store/thunk-extras'; import { type SuiTransactionBlockResponse } from '@mysten/sui/client'; import { Transaction } from '@mysten/sui/transactions'; -import { fromB64 } from '@mysten/sui/utils'; +import { fromBase64 } from '@mysten/sui/utils'; import { createAsyncThunk, createEntityAdapter, createSlice } from '@reduxjs/toolkit'; import type { PayloadAction } from '@reduxjs/toolkit'; @@ -56,7 +56,7 @@ export const respondToTransactionRequest = createAsyncThunk< if (txRequest.tx.type === 'sign-message') { txResult = await signer.signMessage( { - message: fromB64(txRequest.tx.message), + message: fromBase64(txRequest.tx.message), }, clientIdentifier, ); diff --git a/apps/wallet/src/ui/app/shared/InputWithAction.tsx b/apps/wallet/src/ui/app/shared/InputWithAction.tsx index b81032b5a8897..2e1938518c988 100644 --- a/apps/wallet/src/ui/app/shared/InputWithAction.tsx +++ b/apps/wallet/src/ui/app/shared/InputWithAction.tsx @@ -124,7 +124,7 @@ export function InputWithAction({ const inputWithActionZodFormStyles = cva( [ - 'transition flex flex-row items-center px-3 py-2 text-body font-semibold', + 'transition flex flex-row items-center px-3 py-2 text-body font-semibold overflow-hidden', 'placeholder:text-gray-60 w-full pr-[calc(20%_+_24px)]', 'border-solid border text-steel-darker', 'relative', @@ -250,7 +250,7 @@ export const InputWithActionButton = forwardRef +
{info} {onActionClicked && ( { children: ReactNode; title?: string; + className?: string; } -export function Text({ children, title, ...styleProps }: TextProps) { +export function Text({ children, title, className, ...styleProps }: TextProps) { return ( -
+
{children}
); diff --git a/bridge/evm/contracts/BridgeCommittee.sol b/bridge/evm/contracts/BridgeCommittee.sol index 349c9c8b905c9..abd144f5f2248 100644 --- a/bridge/evm/contracts/BridgeCommittee.sol +++ b/bridge/evm/contracts/BridgeCommittee.sol @@ -122,6 +122,8 @@ contract BridgeCommittee is IBridgeCommittee, CommitteeUpgradeable { // update the blocklist _updateBlocklist(_blocklist, isBlocklisted); + + emit BlocklistUpdatedV2(message.nonce, _blocklist, isBlocklisted); } /* ========== INTERNAL FUNCTIONS ========== */ @@ -134,8 +136,6 @@ contract BridgeCommittee is IBridgeCommittee, CommitteeUpgradeable { for (uint16 i; i < _blocklist.length; i++) { blocklist[_blocklist[i]] = isBlocklisted; } - - emit BlocklistUpdated(_blocklist, isBlocklisted); } /// @notice Splits the provided signature into its r, s, and v components. diff --git a/bridge/evm/contracts/BridgeConfig.sol b/bridge/evm/contracts/BridgeConfig.sol index 485b801622230..af2ba284857c0 100644 --- a/bridge/evm/contracts/BridgeConfig.sol +++ b/bridge/evm/contracts/BridgeConfig.sol @@ -31,24 +31,25 @@ contract BridgeConfig is IBridgeConfig, CommitteeUpgradeable { uint8 _chainID, address[] memory _supportedTokens, uint64[] memory _tokenPrices, + uint8[] memory _tokenIds, + uint8[] memory _suiDecimals, uint8[] memory _supportedChains ) external initializer { __CommitteeUpgradeable_init(_committee); - require(_supportedTokens[0] == address(0), "BridgeConfig: Must reserve first token for SUI"); - require(_supportedTokens.length == 5, "BridgeConfig: Invalid supported token addresses"); require( _supportedTokens.length == _tokenPrices.length, "BridgeConfig: Invalid token prices" ); + require( + _supportedTokens.length == _tokenIds.length, "BridgeConfig: Invalid token IDs" + ); + require( + _supportedTokens.length == _suiDecimals.length, "BridgeConfig: Invalid Sui decimals" + ); - uint8[] memory _suiDecimals = new uint8[](5); - _suiDecimals[0] = 9; // SUI - _suiDecimals[1] = 8; // wBTC - _suiDecimals[2] = 8; // wETH - _suiDecimals[3] = 6; // USDC - _suiDecimals[4] = 6; // USDT - - for (uint8 i; i < _supportedTokens.length; i++) { - supportedTokens[i] = Token(_supportedTokens[i], _suiDecimals[i], true); + for (uint8 i; i < _tokenIds.length; i++) { + // `is_native` is hardcoded to `true` because we only support Eth native tokens + // at the moment. This needs to change when we support tokens native on other chains. + supportedTokens[_tokenIds[i]] = Token(_supportedTokens[i], _suiDecimals[i], true); } for (uint8 i; i < _supportedChains.length; i++) { @@ -57,7 +58,7 @@ contract BridgeConfig is IBridgeConfig, CommitteeUpgradeable { } for (uint8 i; i < _tokenPrices.length; i++) { - tokenPrices[i] = _tokenPrices[i]; + tokenPrices[_tokenIds[i]] = _tokenPrices[i]; } chainID = _chainID; @@ -117,6 +118,8 @@ contract BridgeConfig is IBridgeConfig, CommitteeUpgradeable { (uint8 tokenID, uint64 price) = BridgeUtils.decodeUpdateTokenPricePayload(message.payload); _updateTokenPrice(tokenID, price); + + emit TokenPriceUpdatedV2(message.nonce, tokenID, price); } function addTokensWithSignatures(bytes[] memory signatures, BridgeUtils.Message memory message) @@ -137,6 +140,8 @@ contract BridgeConfig is IBridgeConfig, CommitteeUpgradeable { for (uint8 i; i < tokenIDs.length; i++) { _addToken(tokenIDs[i], tokenAddresses[i], suiDecimals[i], _tokenPrices[i], native); } + + emit TokensAddedV2(message.nonce, tokenIDs, tokenAddresses, suiDecimals, _tokenPrices); } /* ========== PRIVATE FUNCTIONS ========== */ @@ -149,8 +154,6 @@ contract BridgeConfig is IBridgeConfig, CommitteeUpgradeable { require(tokenPrice > 0, "BridgeConfig: Invalid token price"); tokenPrices[tokenID] = tokenPrice; - - emit TokenPriceUpdated(tokenID, tokenPrice); } /// @notice Updates the token with the provided ID. @@ -175,8 +178,6 @@ contract BridgeConfig is IBridgeConfig, CommitteeUpgradeable { supportedTokens[tokenID] = Token(tokenAddress, suiDecimal, native); tokenPrices[tokenID] = tokenPrice; - - emit TokenAdded(tokenID, tokenAddress, suiDecimal, tokenPrice); } /* ========== MODIFIERS ========== */ diff --git a/bridge/evm/contracts/BridgeLimiter.sol b/bridge/evm/contracts/BridgeLimiter.sol index 12be449dcc9d7..56ce088c5b0f7 100644 --- a/bridge/evm/contracts/BridgeLimiter.sol +++ b/bridge/evm/contracts/BridgeLimiter.sol @@ -178,6 +178,6 @@ contract BridgeLimiter is IBridgeLimiter, CommitteeUpgradeable, OwnableUpgradeab // update the chain limit chainLimits[sourceChainID] = newLimit; - emit LimitUpdated(sourceChainID, newLimit); + emit LimitUpdatedV2(message.nonce, sourceChainID, newLimit); } } diff --git a/bridge/evm/contracts/SuiBridge.sol b/bridge/evm/contracts/SuiBridge.sol index d34eb19fb3153..d7c4981113eb0 100644 --- a/bridge/evm/contracts/SuiBridge.sol +++ b/bridge/evm/contracts/SuiBridge.sol @@ -10,7 +10,6 @@ import "./interfaces/ISuiBridge.sol"; import "./interfaces/IBridgeVault.sol"; import "./interfaces/IBridgeLimiter.sol"; import "./interfaces/IBridgeConfig.sol"; -import "./interfaces/IWETH9.sol"; /// @title SuiBridge /// @notice This contract implements a token bridge that enables users to deposit and withdraw @@ -121,7 +120,8 @@ contract SuiBridge is ISuiBridge, CommitteeUpgradeable, PausableUpgradeable { if (isFreezing) _pause(); else _unpause(); - // pausing event emitted in 'PausableUpgradeable.sol' + + emit EmergencyOperation(message.nonce, isFreezing); } /// @notice Enables the caller to deposit supported tokens to be bridged to a given diff --git a/bridge/evm/contracts/interfaces/IBridgeCommittee.sol b/bridge/evm/contracts/interfaces/IBridgeCommittee.sol index e1d2d5d73d2cd..b62b00ac2b7b1 100644 --- a/bridge/evm/contracts/interfaces/IBridgeCommittee.sol +++ b/bridge/evm/contracts/interfaces/IBridgeCommittee.sol @@ -23,7 +23,11 @@ interface IBridgeCommittee { /* ========== EVENTS ========== */ /// @notice Emitted when the blocklist is updated. + /// @param nonce The governance action nonce. /// @param updatedMembers The addresses of the updated committee members. /// @param isBlocklisted A boolean indicating whether the committee members are blocklisted or not. + event BlocklistUpdatedV2(uint64 nonce, address[] updatedMembers, bool isBlocklisted); + + /// @dev (deprecated in favor of BlocklistUpdatedV2) event BlocklistUpdated(address[] updatedMembers, bool isBlocklisted); } diff --git a/bridge/evm/contracts/interfaces/IBridgeConfig.sol b/bridge/evm/contracts/interfaces/IBridgeConfig.sol index 1b16f9b7e791e..1a57b99ecccb8 100644 --- a/bridge/evm/contracts/interfaces/IBridgeConfig.sol +++ b/bridge/evm/contracts/interfaces/IBridgeConfig.sol @@ -43,6 +43,29 @@ interface IBridgeConfig { /// @notice Returns the chain ID of the bridge. function chainID() external view returns (uint8); + /// @notice Event for the addition of a new token. + /// @param nonce The governance action nonce. + /// @param tokenIDs The IDs of the tokens added. + /// @param tokenAddresses The addresses of the tokens added. + /// @param suiDecimals The added token's decimal places on Sui. + /// @param tokenPrices The prices of the tokens added in USD. + event TokensAddedV2( + uint64 nonce, + uint8[] tokenIDs, + address[] tokenAddresses, + uint8[] suiDecimals, + uint64[] tokenPrices + ); + + /// @dev (deprecated in favor of TokensAddedV2) event TokenAdded(uint8 tokenID, address tokenAddress, uint8 suiDecimal, uint64 tokenPrice); + + /// @notice Event for the price update of a token. + /// @param nonce The governance action nonce. + /// @param tokenID The ID of the token updated. + /// @param tokenPrice The new price of the token in USD. + event TokenPriceUpdatedV2(uint64 nonce, uint8 tokenID, uint64 tokenPrice); + + /// @dev (deprecated in favor of TokenPriceUpdatedV2) event TokenPriceUpdated(uint8 tokenID, uint64 tokenPrice); } diff --git a/bridge/evm/contracts/interfaces/IBridgeLimiter.sol b/bridge/evm/contracts/interfaces/IBridgeLimiter.sol index 1dcf91733c444..e6e9139d5754f 100644 --- a/bridge/evm/contracts/interfaces/IBridgeLimiter.sol +++ b/bridge/evm/contracts/interfaces/IBridgeLimiter.sol @@ -30,7 +30,11 @@ interface IBridgeLimiter { event HourlyTransferAmountUpdated(uint32 hourUpdated, uint256 amount); /// @dev Emitted when the total limit is updated. + /// @param nonce The governance action nonce. /// @param sourceChainID The ID of the source chain. /// @param newLimit The new limit in USD with 4 decimal places (e.g. 10000 -> $1) + event LimitUpdatedV2(uint64 nonce, uint8 sourceChainID, uint64 newLimit); + + /// @dev (deprecated in favor of LimitUpdatedV2) event LimitUpdated(uint8 sourceChainID, uint64 newLimit); } diff --git a/bridge/evm/contracts/interfaces/ISuiBridge.sol b/bridge/evm/contracts/interfaces/ISuiBridge.sol index 7d70d427a9795..146049b08f582 100644 --- a/bridge/evm/contracts/interfaces/ISuiBridge.sol +++ b/bridge/evm/contracts/interfaces/ISuiBridge.sol @@ -40,4 +40,9 @@ interface ISuiBridge { bytes senderAddress, address recipientAddress ); + + /// @notice Emitted when the bridge is paused or unpaused. + /// @param nonce The governance action nonce. + /// @param paused A boolean indicating whether the bridge is paused or not. + event EmergencyOperation(uint64 nonce, bool paused); } diff --git a/bridge/evm/contracts/utils/CommitteeUpgradeable.sol b/bridge/evm/contracts/utils/CommitteeUpgradeable.sol index ea0de12bff2c3..a3385d36041f2 100644 --- a/bridge/evm/contracts/utils/CommitteeUpgradeable.sol +++ b/bridge/evm/contracts/utils/CommitteeUpgradeable.sol @@ -57,7 +57,9 @@ abstract contract CommitteeUpgradeable is // authorize upgrade _upgradeAuthorized = true; // upgrade contract - upgradeToAndCall(implementation, callData); // Upgraded event emitted with new implementation address + upgradeToAndCall(implementation, callData); + + emit ContractUpgraded(message.nonce, proxy, implementation); } /* ========== INTERNAL FUNCTIONS ========== */ @@ -69,4 +71,10 @@ abstract contract CommitteeUpgradeable is require(_upgradeAuthorized, "CommitteeUpgradeable: Unauthorized upgrade"); _upgradeAuthorized = false; } + + /// @notice Event emitted when the contract is upgraded + /// @param nonce The nonce of the upgrade message. + /// @param proxy The address of the proxy contract. + /// @param implementation The address of the new implementation. + event ContractUpgraded(uint256 nonce, address proxy, address implementation); } diff --git a/bridge/evm/deploy_configs/11155111.json b/bridge/evm/deploy_configs/11155111.json index 3a50d7abde10b..0b26d6e6811b4 100644 --- a/bridge/evm/deploy_configs/11155111.json +++ b/bridge/evm/deploy_configs/11155111.json @@ -3,9 +3,11 @@ "committeeMembers": ["0xf04c72634fc11f7078fc8d1e1260d105a6d9c555", "0x6b1b0fb6bb0a217a0fa8a6a880d886437fbfb9a7", "0x278b75716cfdd84612efb78f8ba99240826dca00", "0xcb5c7457b31509f3451d931dd633115451acc0b0"], "minCommitteeStakeRequired": 10000, "sourceChainId": 11, - "supportedChainIDs": [1, 2], + "supportedChainIds": [1, 2], "supportedChainLimitsInDollars": [100000000000, 100000000000], "supportedTokens": ["0x0000000000000000000000000000000000000000", "0x0112D7B36726B3077b72DDb457A9f9c94D9cd71c", "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14", "0x80bF6fb931C8eB99Ab32aeD543ACCFd168fd2a47", "0x4302b99c1DE4de1ceC3eAedAbf88b8ee6Fceb614"], "tokenPrices": [162000000, 6200000000000, 430000000000, 100000000, 100000000], - "wETHAddress": "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14" + "tokenIds": [0, 1, 2, 3, 4], + "suiDecimals": [9, 8, 8, 6, 6], + "weth": "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14" } diff --git a/bridge/evm/deploy_configs/31337.json b/bridge/evm/deploy_configs/31337.json index 2dffed7e00faa..4738e562837ca 100644 --- a/bridge/evm/deploy_configs/31337.json +++ b/bridge/evm/deploy_configs/31337.json @@ -3,8 +3,11 @@ "committeeMembers": ["0x68b43fd906c0b8f024a18c56e06744f7c6157c65", "0xacaef39832cb995c4e049437a3e2ec6a7bad1ab5", "0x8061f127910e8ef56f16a2c411220bad25d61444", "0x508f3f1ff45f4ca3d8e86cdcc91445f00acc59fc"], "minCommitteeStakeRequired": 10000, "sourceChainId": 12, - "supportedChainIDs": [1, 2, 3], + "supportedChainIds": [1, 2, 3], "supportedChainLimitsInDollars": [1000000000000000, 1000000000000000, 1000000000000000], "supportedTokens": [], - "tokenPrices": [128000000, 4325189000000, 259696000000, 100000000, 100000000] + "tokenPrices": [128000000, 4325189000000, 259696000000, 100000000, 100000000], + "tokenIds": [], + "suiDecimals": [], + "weth": "0x0000000000000000000000000000000000000000" } diff --git a/bridge/evm/deploy_configs/31338.json b/bridge/evm/deploy_configs/31338.json index bf8a8b2898594..ab29feeb8fd93 100644 --- a/bridge/evm/deploy_configs/31338.json +++ b/bridge/evm/deploy_configs/31338.json @@ -3,8 +3,11 @@ "committeeMembers": ["27e24ef6bc45152cd145a0ec28b5d55f2b5afec9", "36d7f7732f1eb8c2cf678fab2ce6c64b27ade701", "4580b223963320d7a78b7b4ccdd8b0f7a2fdb24f", "04130d2a70517f7eb6d0e0198ef8a0db06878a7e"], "minCommitteeStakeRequired": 10000, "sourceChainId": 12, - "supportedChainIDs": [1, 2, 3], + "supportedChainIds": [1, 2, 3], "supportedChainLimitsInDollars": [1000000000000000, 1000000000000000, 1000000000000000], "supportedTokens": [], - "tokenPrices": [128000000, 4325189000000, 259696000000, 100000000, 100000000] + "tokenPrices": [128000000, 4325189000000, 259696000000, 100000000, 100000000], + "tokenIds": [], + "suiDecimals": [], + "weth": "0x0000000000000000000000000000000000000000" } diff --git a/bridge/evm/deploy_configs/example.json b/bridge/evm/deploy_configs/example.json deleted file mode 100644 index 88cf3de5a74be..0000000000000 --- a/bridge/evm/deploy_configs/example.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "committeeMemberStake": [0], - "committeeMembers": ["0x000"], - "minCommitteeStakeRequired": 10000, - "sourceChainId": 0, - "supportedChainIDs": [1], - "supportedChainLimitsInDollars": [1000000], - "supportedTokens": ["0x00", "0x00", "0x00", "0x00"], - "tokenPrices": [128000000, 4325189000000, 259696000000, 100000000], - "wETHAddress": "0x00" -} \ No newline at end of file diff --git a/bridge/evm/deploy_configs/mainnet.json b/bridge/evm/deploy_configs/mainnet.json new file mode 100644 index 0000000000000..34ca7cf8e6eee --- /dev/null +++ b/bridge/evm/deploy_configs/mainnet.json @@ -0,0 +1,207 @@ +{ + "committeeMemberStake": [ + 39, + 47, + 36, + 137, + 35, + 36, + 111, + 82, + 40, + 48, + 177, + 144, + 47, + 145, + 51, + 158, + 48, + 153, + 116, + 176, + 109, + 79, + 43, + 98, + 104, + 113, + 52, + 136, + 46, + 100, + 46, + 36, + 146, + 46, + 46, + 105, + 177, + 312, + 310, + 108, + 118, + 92, + 182, + 36, + 114, + 113, + 146, + 151, + 36, + 35, + 104, + 39, + 113, + 271, + 46, + 36, + 39, + 149, + 107, + 242, + 79, + 78, + 42, + 63, + 46, + 105, + 42, + 105, + 45, + 33, + 37, + 56, + 38, + 36, + 190, + 40, + 143, + 81, + 41, + 50, + 286, + 39, + 162, + 36, + 37, + 135, + 90, + 49, + 241, + 26, + 34, + 76, + 38, + 114, + 180, + 33 + ], + "committeeMembers": [ + "0x39024136fdbdec558d2095491367a4ff321fbe3e", + "0xda9e9747a7cab7f1fe2a4e9081a91e433f8c3b84", + "0xe8f24c7f0e3c4851d6ce986bc54e643036d79361", + "0xff0d16ca290f299f6ca272a4446afdc47d0f2d2f", + "0x82fa045e03231a5eef4e944cb126af48f245d9cc", + "0xaaa49ac3eaf87f2ab646aa7550400c95ad327ae8", + "0xefa7fa246d9cd9e71bbb66cf5f4e58da5c914792", + "0x3d685e090223c74f89b5b8d5a7873c09314abc80", + "0x67f0a801aee26d0697747e98782e2e3891c1c951", + "0xdbd00ac2263886faaf4427b82d7ec93bb9e86fa5", + "0x10ed9f56cea76d9768c3e03e194d1114cda3fcfb", + "0xf32071e8d029da4e2d0a9dd18eb5aa7d5923b84e", + "0x5c5ecc147657fdd07d7ce9588205758183792c1b", + "0xd76b5e12dc125250cd1f4dbdc581ed3a75541af5", + "0x33776bdc2f639bc0b4c906b6780801b8de8b1136", + "0xad5ff4050fe8b12b38833d359778429c4d47a940", + "0xc27288bd1d42d622964a6e659361adcbc985159d", + "0x484599913ad96dbbe5a827f0e52c2cc0c04e08e5", + "0x1a66c8a66d4fc9ddf25d442d3f74a0d9fb17fd44", + "0xa5e44c88fc34526bf6dda68629f0d20116847be0", + "0xbfa99854cd154dbe9b2c2334226bb86f63f43813", + "0xfea4153deab782103e0e441b1a367674ae65d552", + "0x916fa23bbedfbe673165f95d1f378e48c08977d6", + "0x47c3208ed2e9b11c6c6babeb78a5fa6202c56e7b", + "0x01d67553870e5d73b5157cfd76083823bda89477", + "0x78e6d0733a07b80fb07c9753953c0240bc397a46", + "0x68b8fc34971dd51f438b00b77100f9579bc8e10e", + "0xd0c895b9f856400d32eb9efa74407feeefd5adec", + "0x82effa7142a2962fb43e389f8e6a89185ad4aebd", + "0x64aa09b650d53a7f7d6fbbb8604b8bed29d9c274", + "0xffee8c759ba17c22835e35cf1afe40418381d44c", + "0xf58738fa449789e1d801392a17b8ff1558e551cc", + "0x76683b2fd26bcb9881c1a5b52a4bbedb71d51e86", + "0x96ab7c996efd8885e0a53e24f609dfffc47f36b2", + "0xe2ac2814715f7092c7a0b54e5b9190279df358df", + "0x5d5fd9e12b069179b9d49e5bf235820658cfa762", + "0xcfbf09860d22c29296574f7bc55433d12ffb65d2", + "0xf90ee80cf156e7bdf0ee4a1a135658b67aacb5cd", + "0xd6bfaf1afd47a0b17956fc0bf445be9fbebc7af9", + "0xe08082446a393d40da75c3a026ae7097ebe6f840", + "0x7394e3737e8a601e72edb9a2d7e13dfc89ea2ef7", + "0x0913a28d1e80dc3e56b5859415a4a995d34dfac3", + "0x32178890ede16ad9628bdac80d5ad9e1b55ecc89", + "0x7655f0e8b9652beae10b1f7b56d43813794e14e2", + "0x4ee1bb44490bc49c8f72bfa947d725af88b7b733", + "0xfa95e81aa17c5cdc50d29224d3529ec67923d03d", + "0x36398267315f8aba5c6c3bcbc26c3447d202421c", + "0xa15d5a0be2c8e59172d075dfbbbf3619626be52c", + "0x5d492b8395be2697a1beeb03e702504a02952ab4", + "0x26e50c99a7ab8016b1d14906f2e2136d8d33ba40", + "0xea5cf6108d2e1a8c8902129df741ad2fa970b98b", + "0x025565de98c394ee1d8a699bd848b5dc9b28d5a1", + "0x62a326ef51b3e6909a54f5ddd3d8c011e60325ec", + "0x66eeb8a57092f8de077fae720c6495c087bd3f9f", + "0x1cd87dac5042abc8eaa066817764ab7bd49d544f", + "0xbce384fa9f7f55ea7881762d3497b4064004d559", + "0x1f4f3d59e1465f1ec666a448e0eca5385691d76d", + "0x1150a4ca6e4dc92196ecadfb0a9e3823d5e92ccb", + "0x0bec3df7e4c7f9a44e1c7cf5f5f383c8740952af", + "0x3041420817f82c78e6fc11898ee99a6eef28afe7", + "0x18239e2e2cbaa0f418cf09c7bb285d99df09611e", + "0x28e49b6da2ca6e3cb8552455db15f828a6edbb27", + "0x5b879c5fedf4e88b17c6c9341a3f62ee1be254cd", + "0x273067b87f3b70390cfba1bcffe12fbff4d2be2c", + "0xe41dc773982f5d31afa0aeb0164edef4b37e0159", + "0x2d1ca70039e4d9142eb4e73c7fe48cbe49d8fa16", + "0xcfcd4cbe783ba62d11708b1af6a06c73548a8dc3", + "0x325ed21026ff5ed575cfba4a7a17c4731af94b8a", + "0xf8bbb376796aee18c319546fcfb334c9df5c592c", + "0x3ed598dcef02b915ab9a1075c8837348e5530c62", + "0xda9c2ab5059c442202575b33f0bb05954a6c6d3f", + "0x3e7cf1e685e0af88779853bdd66717fc137b7c44", + "0x050cd65d5127e704049eaf1060af61ae18346327", + "0x2c3a94f2793992de9453e40d69d0fb20d42b8a0e", + "0x1dd8322069eb61914a2304de98da42bbed352db5", + "0x69d6ae3bbca368073c9fdd73f7856ea2a6af14a4", + "0x7629fd3ee814f9e798a878011d3e5c0f0eaa2b13", + "0x8f4eca214d44e4ece34e2f1e7c87b1f8c2bfc53d", + "0xfbbdd574bc16706961f6087f7f5ccca6d63dd375", + "0x7699e4ec65970416cffe809103d95b362e7c498f", + "0x9b7ba78c021f9e616636074b435a39724de82f64", + "0x949aae4e6f93b211fd714911e095c9a4ac4905b4", + "0x72039b34fa11950d1b170e8c2cded319da1ca165", + "0x8dfb2b2dbfec6a288ffa7f4f1270e083ecc58c1b", + "0x28c504ed997528b913b72e8c0426b22c9ed42859", + "0xbc370552e17f853a53eb10c006b5a6de470b7d63", + "0x3577660a57b84d5cd9c6a3ef3aa62ef6728c2e49", + "0x605051c6fc3d49bd18bea70dbd6e8879cc1260fd", + "0xbe2d217742c617243300888731c910352b6a1aa0", + "0xf323e1cb36615ffa13df3c3e02ccb9dde713dc6e", + "0xe3adb26d62d5d643988ccb371a847ed2c6802f56", + "0x51d2f1075158ef20ca3010fb54e9003ce2c23bc2", + "0xd118e40d9f4a636d954b812826cb101e7a5adccd", + "0x4d5cec85f10f80f9e04f3850a032b0303ebd526a", + "0x0b592dda24dc5030e5ea1146d3adbc1ba0847bb9", + "0x411db69ec10e632db084505e4fded702caa25245" + ], + "minCommitteeStakeRequired": 9133, + "sourceChainId": 10, + "supportedChainIds": [0], + "supportedChainLimitsInDollars": [500000000000000], + "tokenIds": [2], + "supportedTokens": ["0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"], + "tokenPrices": [260000000000], + "suiDecimals": [8], + "weth": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" +} diff --git a/bridge/evm/deploy_configs/sepolia.json b/bridge/evm/deploy_configs/sepolia.json index b56687658c802..63514b181db28 100644 --- a/bridge/evm/deploy_configs/sepolia.json +++ b/bridge/evm/deploy_configs/sepolia.json @@ -137,9 +137,11 @@ ], "minCommitteeStakeRequired": 7600, "sourceChainId": 11, - "supportedChainIDs": [1], + "supportedChainIds": [1], "supportedChainLimitsInDollars": [18446744073709551615], "supportedTokens": ["0x0000000000000000000000000000000000000000", "0xBe9566f1bc9a6a18ad1ed5620Ccb76ff639534d5", "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14", "0x8140EBa492e02Dbf137080E2E4eC0Bd3e10784a0", "0x4E9D6D3dbFFc32399D514A5a03268e5860b6769d"], + "tokenIds": [0, 1, 2, 3, 4], "tokenPrices": [100000000, 7000000000000, 400000000000, 100000000, 100000000], - "wETHAddress": "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14" + "suiDecimals": [9, 8, 8, 6, 6], + "weth": "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14" } diff --git a/bridge/evm/script/deploy_bridge.s.sol b/bridge/evm/script/deploy_bridge.s.sol index 53c5d942b8ef9..079ba92ed52ef 100644 --- a/bridge/evm/script/deploy_bridge.s.sol +++ b/bridge/evm/script/deploy_bridge.s.sol @@ -14,6 +14,25 @@ import "../contracts/SuiBridge.sol"; import "../test/mocks/MockTokens.sol"; contract DeployBridge is Script { + function parseDeployConfig(string memory path) public returns (DeployConfig memory) { + string memory json = vm.readFile(path); + DeployConfig memory config; + + config.committeeMemberStake = abi.decode(vm.parseJson(json, ".committeeMemberStake"), (uint256[])); + config.committeeMembers = abi.decode(vm.parseJson(json, ".committeeMembers"), (address[])); + config.minCommitteeStakeRequired = abi.decode(vm.parseJson(json, ".minCommitteeStakeRequired"), (uint256)); + config.sourceChainId = abi.decode(vm.parseJson(json, ".sourceChainId"), (uint256)); + config.supportedChainIds = abi.decode(vm.parseJson(json, ".supportedChainIds"), (uint256[])); + config.supportedChainLimitsInDollars = abi.decode(vm.parseJson(json, ".supportedChainLimitsInDollars"), (uint256[])); + config.tokenPrices = abi.decode(vm.parseJson(json, ".tokenPrices"), (uint256[])); + config.supportedTokens = abi.decode(vm.parseJson(json, ".supportedTokens"), (address[])); + config.tokenIds = abi.decode(vm.parseJson(json, ".tokenIds"), (uint256[])); + config.suiDecimals = abi.decode(vm.parseJson(json, ".suiDecimals"), (uint256[])); + config.weth = abi.decode(vm.parseJson(json, ".weth"), (address)); + + return config; + } + function run() external { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); @@ -30,15 +49,13 @@ contract DeployBridge is Script { } console.log("config path: ", path); - string memory json = vm.readFile(path); - bytes memory bytesJson = vm.parseJson(json); - DeployConfig memory deployConfig = abi.decode(bytesJson, (DeployConfig)); + DeployConfig memory deployConfig = parseDeployConfig(path); // if deploying to local network, deploy mock tokens if (isLocal) { console.log("Deploying mock tokens for local network"); // deploy WETH - deployConfig.WETH = address(new WETH()); + deployConfig.weth = address(new WETH()); // deploy mock tokens MockWBTC wBTC = new MockWBTC(); @@ -47,35 +64,51 @@ contract DeployBridge is Script { MockKA KA = new MockKA(); console.log("[Deployed] KA:", address(KA)); - // update deployConfig with mock addresses + // update deployConfig with test values deployConfig.supportedTokens = new address[](5); - // In BridgeConfig.sol `supportedTokens is shifted by one - // and the first token is SUI. deployConfig.supportedTokens[0] = address(0); deployConfig.supportedTokens[1] = address(wBTC); - deployConfig.supportedTokens[2] = deployConfig.WETH; + deployConfig.supportedTokens[2] = deployConfig.weth; deployConfig.supportedTokens[3] = address(USDC); deployConfig.supportedTokens[4] = address(USDT); - } - // TODO: validate config values before deploying + deployConfig.tokenIds = new uint256[](5); + deployConfig.tokenIds[0] = 0; + deployConfig.tokenIds[1] = 1; + deployConfig.tokenIds[2] = 2; + deployConfig.tokenIds[3] = 3; + deployConfig.tokenIds[4] = 4; + + deployConfig.suiDecimals = new uint256[](5); + deployConfig.suiDecimals[0] = 9; + deployConfig.suiDecimals[1] = 8; + deployConfig.suiDecimals[2] = 8; + deployConfig.suiDecimals[3] = 6; + deployConfig.suiDecimals[4] = 6; + } - // convert supported chains from uint256 to uint8[] - uint8[] memory supportedChainIDs = new uint8[](deployConfig.supportedChainIDs.length); - for (uint256 i; i < deployConfig.supportedChainIDs.length; i++) { - supportedChainIDs[i] = uint8(deployConfig.supportedChainIDs[i]); + // convert supported chains from uint256 to uint8 + uint8[] memory supportedChainIds = new uint8[](deployConfig.supportedChainIds.length); + for (uint256 i; i < deployConfig.supportedChainIds.length; i++) { + supportedChainIds[i] = uint8(deployConfig.supportedChainIds[i]); } - // deploy bridge config - // price of Sui (id = 0) should not be included in tokenPrices require( deployConfig.supportedTokens.length == deployConfig.tokenPrices.length, - "supportedTokens.length + 1 != tokenPrices.length" + "supportedTokens.length != tokenPrices.length" + ); + require( + deployConfig.supportedTokens.length == deployConfig.tokenIds.length, + "supportedTokens.length != tokenIds.length" + ); + require( + deployConfig.supportedTokens.length == deployConfig.suiDecimals.length, + "supportedTokens.length != suiDecimals.length" ); // deploy Bridge Committee =================================================================== - // convert committeeMembers stake from uint256 to uint16[] + // convert committeeMembers stake from uint256 to uint16 uint16[] memory committeeMemberStake = new uint16[](deployConfig.committeeMemberStake.length); for (uint256 i; i < deployConfig.committeeMemberStake.length; i++) { @@ -106,6 +139,18 @@ contract DeployBridge is Script { tokenPrices[i] = uint64(deployConfig.tokenPrices[i]); } + // convert Sui Decimals from uint256 to uint8 + uint8[] memory suiDecimals = new uint8[](deployConfig.suiDecimals.length); + for (uint256 i; i < deployConfig.suiDecimals.length; i++) { + suiDecimals[i] = uint8(deployConfig.suiDecimals[i]); + } + + // convert Token Id from uint256 to uint8 + uint8[] memory tokenIds = new uint8[](deployConfig.tokenIds.length); + for (uint256 i; i < deployConfig.tokenIds.length; i++) { + tokenIds[i] = uint8(deployConfig.tokenIds[i]); + } + address bridgeConfig = Upgrades.deployUUPSProxy( "BridgeConfig.sol", abi.encodeCall( @@ -115,7 +160,9 @@ contract DeployBridge is Script { uint8(deployConfig.sourceChainId), deployConfig.supportedTokens, tokenPrices, - supportedChainIDs + tokenIds, + suiDecimals, + supportedChainIds ) ), opts @@ -129,7 +176,7 @@ contract DeployBridge is Script { // deploy vault ============================================================================= - BridgeVault vault = new BridgeVault(deployConfig.WETH); + BridgeVault vault = new BridgeVault(deployConfig.weth); // deploy limiter =========================================================================== @@ -143,7 +190,7 @@ contract DeployBridge is Script { address limiter = Upgrades.deployUUPSProxy( "BridgeLimiter.sol", abi.encodeCall( - BridgeLimiter.initialize, (bridgeCommittee, supportedChainIDs, chainLimits) + BridgeLimiter.initialize, (bridgeCommittee, supportedChainIds, chainLimits) ), opts ); @@ -190,9 +237,11 @@ struct DeployConfig { address[] committeeMembers; uint256 minCommitteeStakeRequired; uint256 sourceChainId; - uint256[] supportedChainIDs; + uint256[] supportedChainIds; uint256[] supportedChainLimitsInDollars; address[] supportedTokens; uint256[] tokenPrices; - address WETH; + uint256[] tokenIds; + uint256[] suiDecimals; + address weth; } diff --git a/bridge/evm/test/BridgeBaseTest.t.sol b/bridge/evm/test/BridgeBaseTest.t.sol index 034533d9b9ef6..bc2e3c194dc97 100644 --- a/bridge/evm/test/BridgeBaseTest.t.sol +++ b/bridge/evm/test/BridgeBaseTest.t.sol @@ -46,6 +46,8 @@ contract BridgeBaseTest is Test { uint64 ETH_PRICE = 2596_96000000; uint64 USDC_PRICE = 1_00000000; uint64[] tokenPrices; + uint8[] suiDecimals; + uint8[] tokenIds; address[] supportedTokens; uint8[] supportedChains; @@ -117,17 +119,29 @@ contract BridgeBaseTest is Test { supportedChains = new uint8[](1); supportedChains[0] = 0; tokenPrices = new uint64[](5); + suiDecimals = new uint8[](5); + tokenIds = new uint8[](5); + suiDecimals[0] = 9; + suiDecimals[1] = 8; + suiDecimals[2] = 8; + suiDecimals[3] = 6; + suiDecimals[4] = 6; tokenPrices[0] = SUI_PRICE; tokenPrices[1] = BTC_PRICE; tokenPrices[2] = ETH_PRICE; tokenPrices[3] = USDC_PRICE; tokenPrices[4] = USDC_PRICE; + tokenIds[0] = 0; + tokenIds[1] = 1; + tokenIds[2] = 2; + tokenIds[3] = 3; + tokenIds[4] = 4; address _config = Upgrades.deployUUPSProxy( "BridgeConfig.sol", abi.encodeCall( BridgeConfig.initialize, - (address(committee), chainID, supportedTokens, tokenPrices, supportedChains) + (address(committee), chainID, supportedTokens, tokenPrices, tokenIds, suiDecimals, supportedChains) ), opts ); diff --git a/bridge/evm/test/BridgeCommitteeTest.t.sol b/bridge/evm/test/BridgeCommitteeTest.t.sol index 42c82596ef370..6133264282901 100644 --- a/bridge/evm/test/BridgeCommitteeTest.t.sol +++ b/bridge/evm/test/BridgeCommitteeTest.t.sol @@ -411,7 +411,7 @@ contract BridgeCommitteeTest is BridgeBaseTest { "BridgeConfig.sol", abi.encodeCall( BridgeConfig.initialize, - (address(committee), chainID, supportedTokens, tokenPrices, supportedChains) + (address(committee), chainID, supportedTokens, tokenPrices, tokenIds, suiDecimals, supportedChains) ), opts ); diff --git a/bridge/evm/test/BridgeConfigTest.t.sol b/bridge/evm/test/BridgeConfigTest.t.sol index 0a57c8bc6495b..79a8a1817220f 100644 --- a/bridge/evm/test/BridgeConfigTest.t.sol +++ b/bridge/evm/test/BridgeConfigTest.t.sol @@ -374,7 +374,7 @@ contract BridgeConfigTest is BridgeBaseTest { "BridgeConfig.sol", abi.encodeCall( BridgeConfig.initialize, - (address(committee), chainID, supportedTokens, tokenPrices, _supportedChains) + (address(committee), chainID, supportedTokens, tokenPrices, tokenIds, suiDecimals, _supportedDestinationChains) ), opts ); @@ -463,7 +463,7 @@ contract BridgeConfigTest is BridgeBaseTest { "BridgeConfig.sol", abi.encodeCall( BridgeConfig.initialize, - (address(committee), 12, supportedTokens, tokenPrices, _supportedDestinationChains) + (address(committee), 12, supportedTokens, tokenPrices, tokenIds, suiDecimals, _supportedDestinationChains) ), opts ); diff --git a/bridge/evm/test/BridgeLimiterTest.t.sol b/bridge/evm/test/BridgeLimiterTest.t.sol index 44ff9fcaa4138..b5346402cd250 100644 --- a/bridge/evm/test/BridgeLimiterTest.t.sol +++ b/bridge/evm/test/BridgeLimiterTest.t.sol @@ -163,7 +163,7 @@ contract BridgeLimiterTest is BridgeBaseTest { "BridgeConfig.sol", abi.encodeCall( BridgeConfig.initialize, - (address(committee), chainID, _supportedTokens, tokenPrices, supportedChains) + (address(committee), chainID, _supportedTokens, tokenPrices, tokenIds, suiDecimals, supportedChains) ), opts ); @@ -265,7 +265,7 @@ contract BridgeLimiterTest is BridgeBaseTest { "BridgeConfig.sol", abi.encodeCall( BridgeConfig.initialize, - (address(committee), chainID, supportedTokens, tokenPrices, _supportedChains) + (address(committee), chainID, supportedTokens, tokenPrices, tokenIds, suiDecimals, _supportedChains) ), opts ); @@ -351,7 +351,7 @@ contract BridgeLimiterTest is BridgeBaseTest { "BridgeConfig.sol", abi.encodeCall( BridgeConfig.initialize, - (address(committee), chainID, supportedTokens, tokenPrices, _supportedChains) + (address(committee), chainID, supportedTokens, tokenPrices, tokenIds, suiDecimals, _supportedChains) ), opts ); diff --git a/bridge/evm/test/CommitteeUpgradeableTest.t.sol b/bridge/evm/test/CommitteeUpgradeableTest.t.sol index 064afaf7f500d..a56058e1032f9 100644 --- a/bridge/evm/test/CommitteeUpgradeableTest.t.sol +++ b/bridge/evm/test/CommitteeUpgradeableTest.t.sol @@ -46,7 +46,7 @@ contract CommitteeUpgradeableTest is BridgeBaseTest { "BridgeConfig.sol", abi.encodeCall( BridgeConfig.initialize, - (_committee, _chainID, supportedTokens, tokenPrices, _supportedDestinationChains) + (_committee, _chainID, supportedTokens, tokenPrices, tokenIds, suiDecimals, _supportedDestinationChains) ), opts ); diff --git a/bridge/evm/test/SuiBridgeTest.t.sol b/bridge/evm/test/SuiBridgeTest.t.sol index 7d1f4afd0c2b7..c67fcc2906a80 100644 --- a/bridge/evm/test/SuiBridgeTest.t.sol +++ b/bridge/evm/test/SuiBridgeTest.t.sol @@ -677,7 +677,7 @@ contract SuiBridgeTest is BridgeBaseTest, ISuiBridge { "BridgeConfig.sol", abi.encodeCall( BridgeConfig.initialize, - (address(committee), 11, _supportedTokens, tokenPrices, _supportedDestinationChains) + (address(committee), 11, _supportedTokens, tokenPrices, tokenIds, suiDecimals, _supportedDestinationChains) ), opts ); @@ -791,6 +791,8 @@ contract SuiBridgeTest is BridgeBaseTest, ISuiBridge { _chainID, _supportedTokens, tokenPrices, + tokenIds, + suiDecimals, _supportedDestinationChains ) ), @@ -869,7 +871,7 @@ contract SuiBridgeTest is BridgeBaseTest, ISuiBridge { "BridgeConfig.sol", abi.encodeCall( BridgeConfig.initialize, - (address(committee), chainID, supportedTokens, tokenPrices, supportedChains) + (address(committee), chainID, supportedTokens, tokenPrices, tokenIds, suiDecimals, supportedChains) ), opts ); @@ -1020,6 +1022,8 @@ contract SuiBridgeTest is BridgeBaseTest, ISuiBridge { _chainID, _supportedTokens, tokenPrices, + tokenIds, + suiDecimals, _supportedDestinationChains ) ), diff --git a/bridge/runbook/validator_runbook.md b/bridge/runbook/validator_runbook.md index 3e6cb69ef481f..ade1bea631f98 100644 --- a/bridge/runbook/validator_runbook.md +++ b/bridge/runbook/validator_runbook.md @@ -1,186 +1 @@ ---- -title: Sui Bridge Validator Runbook ---- - -## Prerequisite - -Install `sui`, `sui-bridge-cli` binaries: -```bash -# install from tip of `main` -$ cargo install --locked --git "https://github.com/MystenLabs/sui.git" sui sui-bridge-cli -# install with a commit sha -$ cargo install --locked --git "https://github.com/MystenLabs/sui.git" --rev {SHA} sui sui-bridge-cli -``` - -## Committee Registeration - -### Prepare for Metadata - -The required metadata includes two things: -* `BridgeAuthorityKey`, a ECDSA key to sign messages. Since this is a hot key that is kept in memory, it’s fine to use the following tool to generate one and write to file. -* a REST API URL where the bridge node listens to and serves requests. Example: `https://bridge.example-sui-validator.io:443`. Make sure the port is correct and the url does not contain any invalid characters, for exmaple quotes. - -To create a `BridgeAuthorityKey`, run -```bash -$ sui-bridge-cli create-bridge-validator-key {PATH_TO_WRITE} -``` -This creates the keypair and writes it to `{PATH_TO_WRITE}`. - -*Note: it's highly recommended you create a new key pair in a secure environment (e.g. in the same machine where your node will run) to avoid key compromise.* - -### Registration -Once you have both authority key file and REST API URL ready, you can register them by using sui cli: -```bash -$ sui validator register-bridge-committee --bridge-authority-key-path --bridge-authority-url -``` - -#### Offline Signing -If your validator account key is kept in cold storage or you want to do offline signing, use flag `--print-only` and provide validator address with `--validator-address`. This prints serialized unsigned transaction bytes, then you can use your preferred signing process to produce signed bytes. Run the following command to execute it: -```bash -$ sui client execute-signed-tx -``` - -#### Update Metadata -Both key and URL are changeable **before the committee is finalized**. If you wish to update metadata, simply rerun `sui validator register-bridge-committee`. - -#### View Registered Metadata -To double check your registered the correct metadata onchain, run -```bash -$ sui-bridge-cli view-bridge-registration --sui-rpc-url {SUI_FULLNODE_URL} -``` - -## Bridge Node - -### Bridge Node Hardware Requirements - -Suggested hardware requirements: -* CPU: 6 physical cores -* Memory: 16GB -* Storage: 200GB -* Network: 100Mbps - -### WAF Protection for Bridge Node - -In order to protect against DDOS and attacks intended to expend validator resources, rate limit protection of the bridge server is required. -In addition to protection, this will give node operators fine-grained control over the rate of requests the receive, and observability into those requests. - -The currently recommended rate-limit is `50 requests/second per unique IP`. - -#### WAF Options - -You can use a managed cloud service, for example: -* [Cloudflare WAF](https://www.cloudflare.com/en-ca/application-services/products/waf/) -* [AWS WAF](https://aws.amazon.com/waf/) -* [GCP Cloud Armor](https://cloud.google.com/security/products/armor) - -It's also possible to use an open source load balancer such as haproxy for a simple, ip-based rate limit. -An example, shortened HAProxy config for this looks like: -``` -frontend http-in - bind *:80 - # Define an ACL to count requests per IP and block if over limit - acl too_many_requests src_http_req_rate() gt 50 - # Track the request rate per IP - stick-table type ip size 1m expire 1m store http_req_rate(1s) - # Check request rate and deny if the limit is exceeded - http-request track-sc0 src - http-request deny if too_many_requests - - default_backend bridgevalidator - -backend bridgevalidator - # Note the port needs to match the value in Bridge Node config, default is 9191 - server bridgevalidator 0.0.0.0:9191 -``` - -If choosing to use an open source load-balancing option, make sure to set up metrics collection and alerting on the service. - -### Bridge Node Config -Use `sui-bridge-cli` command to create a template. If you want to run `BridgeClient` (see the following section), pass `--run-client` as a parameter. - -```bash -$ sui-bridge-cli create-bridge-node-config-template {PATH} -$ sui-bridge-cli create-bridge-node-config-template --run-client {PATH} -``` - -In the generated config: -* `server-listen-port` : the port that Bridge Node listens to handle requests -* `metrics-port`: port to export prometheus metrics -* `bridge-authority-key-path` is the path to the Bridge Validator key, generated from `sui-bridge-cli create-bridge-validator-key` from above command. -* `run-client`: if Bridge Client should be enabled in Bridge Node (more instructions for this below) -* `approved-governance-actions` : a list of governance actions that you want to support. -* `sui:sui-rpc-url`: Sui RPC URL -* `sui:sui-bridge-chain-id`: 0 for Sui Mainnet, 1 for Sui Testnet -* `eth:eth-rpc-url`: Ethereum RPC URL -* `eth:eth-bridge-proxy-address`: The proxy address for Bridge Solidity contracts on Ethereum. -* `eth:eth-bridge-chain-id`: 10 for Ethereum Mainnet, 11 for Sepolia Testnet -* `eth:eth-contracts-start-block-fallback`: The starting block BridgeNodes queries for from Ethereum FullNode. This number should be the block where Solidity contracts are deployed or slightly before. -* `metrics:push-url`: The url of the remote Sui metrics pipeline: `https://metrics-proxy.[testnet|mainnet].sui.io:8443/publish/metrics` - -With `run-client: true`, these additional fields can be found in the generated config: -* `db-path`: path of BridgeClient DB, for BridgeClient -* `sui:bridge-client-key-path`: the file path of Bridge Client key. This key can be generated with `sui-bridge-cli create-bridge-client-key` as shown above. When `run-client` is true but `sui:bridge-client-key-path` not provided, it defaults to use Bridge Validator key to submit transactions on Sui. However this is not recommended for the sake of key separation. - -### Bridge Client -`BridgeClient` orchestrates bridge transfer requests. -* It is **optional** to run for a `BridgeNode`. -* `BridgeClient` submits transaction on Sui Network. Thus when it's enabled, a Sui Account Key with enough SUI balance is needed. - -To enable `bridge_client` feature on a `BridgeNode`, set the following parameters in `BridgeNodeConfig`: -```yaml -run-client: true -db-path: -sui: - bridge-client-key-path: # optional, when absent, use bridge-authority-key-path as the keypair for BridgeClient -``` - - -To create a `BridgeClient` keypair, run -``` -sui-bridge-cli create-bridge-client-key -``` -This prints the newly created Sui Address. Then we need to fund this address with some SUI for operations. - - -### Build Bridge Node - -Build or install Bridge Node in one of the following ways: - -1. `cargo install` -```bash -$ cargo install --locked --git "https://github.com/MystenLabs/sui.git" --branch {BRANCH-NAME} sui-bridge -# OR -$ cargo install --locked --git "https://github.com/MystenLabs/sui.git" --rev {SHA-NAME} sui-bridge -``` - -2. compile from source code -```bash -$ git clone https://github.com/MystenLabs/sui.git -$ cd sui -$ git fetch origin {BRANCH-NAME|SHA} -$ git checkout {BRANCH-NAME|SHA} -$ cargo build --release --bin sui-bridge -``` - -3. `curl`/`wget` pre-built binaries (for linux/amd64 only) -``` -curl https://sui-releases.s3.us-east-1.amazonaws.com/{SHA}/sui-bridge -o sui-bridge -``` - -4. use pre-built docker image. Pull from docker hub: `mysten/sui-tools:{SHA}` - - -### Run Bridge Node -It is similar to running a sui-node using systemd or ansible. The command to start the bridge node is: - -```bash -$ RUST_LOG=info,sui_bridge=debug sui-bridge --config-path {BRIDGE-NODE-CONFIG-PATH} -``` - -### Ingress -Bridge Node listens for tcp connections over port `9191` (or your preferred port as configured in Bridge Node Config), you’ll need to allow incoming connections for that port on the host which is running Bridge Node. - -Test ingress with curl on a remote machine and expect a `200` response: -```bash -$ curl -v {YOUR_BRIDGE_URL} -``` +The runbook is now located at sui/docs/content/guides/operator/bridge-node-configuration.mdx. You can view it online at https://docs.sui.io/guides/operator/bridge-node-configuration \ No newline at end of file diff --git a/consensus/config/src/parameters.rs b/consensus/config/src/parameters.rs index a1306c77479ce..496d33c5f2080 100644 --- a/consensus/config/src/parameters.rs +++ b/consensus/config/src/parameters.rs @@ -19,7 +19,8 @@ pub struct Parameters { #[serde(skip)] pub db_path: PathBuf, - /// Time to wait for parent round leader before sealing a block. + /// Time to wait for parent round leader before sealing a block, from when parent round + /// has a quorum. #[serde(default = "Parameters::default_leader_timeout")] pub leader_timeout: Duration, @@ -102,6 +103,9 @@ impl Parameters { // leading to long reconfiguration delays. This is because simtest is single threaded, // and spending too much time in consensus can lead to starvation elsewhere. Duration::from_millis(400) + } else if cfg!(test) { + // Avoid excessive CPU, data and logs in tests. + Duration::from_millis(250) } else { Duration::from_millis(50) } diff --git a/consensus/config/tests/snapshots/parameters_test__parameters.snap b/consensus/config/tests/snapshots/parameters_test__parameters.snap index ff7422faefd10..56e46f1bb3009 100644 --- a/consensus/config/tests/snapshots/parameters_test__parameters.snap +++ b/consensus/config/tests/snapshots/parameters_test__parameters.snap @@ -31,3 +31,4 @@ tonic: connection_buffer_size: 33554432 excessive_message_size: 16777216 message_size_limit: 67108864 + diff --git a/consensus/core/src/authority_node.rs b/consensus/core/src/authority_node.rs index 28b37769db82b..1b8b818b79721 100644 --- a/consensus/core/src/authority_node.rs +++ b/consensus/core/src/authority_node.rs @@ -21,7 +21,7 @@ use crate::{ core::{Core, CoreSignals}, core_thread::{ChannelCoreThreadDispatcher, CoreThreadHandle}, dag_state::DagState, - leader_schedule::{LeaderSchedule, LeaderSwapTable}, + leader_schedule::LeaderSchedule, leader_timeout::{LeaderTimeoutTask, LeaderTimeoutTaskHandle}, metrics::initialise_metrics, network::{ @@ -233,20 +233,10 @@ where let block_manager = BlockManager::new(context.clone(), dag_state.clone(), block_verifier.clone()); - let leader_schedule = if context - .protocol_config - .mysticeti_leader_scoring_and_schedule() - { - Arc::new(LeaderSchedule::from_store( - context.clone(), - dag_state.clone(), - )) - } else { - Arc::new(LeaderSchedule::new( - context.clone(), - LeaderSwapTable::default(), - )) - }; + let leader_schedule = Arc::new(LeaderSchedule::from_store( + context.clone(), + dag_state.clone(), + )); let commit_consumer_monitor = commit_consumer.monitor(); commit_consumer_monitor @@ -426,7 +416,7 @@ mod tests { use std::{collections::BTreeSet, sync::Arc, time::Duration}; use consensus_config::{local_committee_and_keys, Parameters}; - use mysten_metrics::monitored_mpsc::{unbounded_channel, UnboundedReceiver}; + use mysten_metrics::monitored_mpsc::UnboundedReceiver; use prometheus::Registry; use rstest::rstest; use sui_protocol_config::ProtocolConfig; @@ -457,8 +447,7 @@ mod tests { let protocol_keypair = keypairs[own_index].1.clone(); let network_keypair = keypairs[own_index].0.clone(); - let (sender, _receiver) = unbounded_channel("consensus_output"); - let commit_consumer = CommitConsumer::new(sender, 0); + let (commit_consumer, _, _) = CommitConsumer::new(0); let authority = ConsensusAuthority::start( network_type, @@ -487,12 +476,16 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn test_authority_committee( #[values(ConsensusNetwork::Anemo, ConsensusNetwork::Tonic)] network_type: ConsensusNetwork, + #[values(0, 5, 10)] gc_depth: u32, ) { let db_registry = Registry::new(); DBMetrics::init(&db_registry); const NUM_OF_AUTHORITIES: usize = 4; let (committee, keypairs) = local_committee_and_keys(0, [1; NUM_OF_AUTHORITIES].to_vec()); + let mut protocol_config = ProtocolConfig::get_for_max_version_UNSAFE(); + protocol_config.set_consensus_gc_depth_for_testing(gc_depth); + let temp_dirs = (0..NUM_OF_AUTHORITIES) .map(|_| TempDir::new().unwrap()) .collect::>(); @@ -509,6 +502,7 @@ mod tests { keypairs.clone(), network_type, boot_counters[index], + protocol_config.clone(), ) .await; boot_counters[index] += 1; @@ -565,6 +559,7 @@ mod tests { keypairs.clone(), network_type, boot_counters[index], + protocol_config.clone(), ) .await; boot_counters[index] += 1; @@ -582,6 +577,7 @@ mod tests { #[tokio::test(flavor = "current_thread")] async fn test_amnesia_recovery_success( #[values(ConsensusNetwork::Anemo, ConsensusNetwork::Tonic)] network_type: ConsensusNetwork, + #[values(0, 5, 10)] gc_depth: u32, ) { telemetry_subscribers::init_for_testing(); let db_registry = Registry::new(); @@ -594,6 +590,9 @@ mod tests { let mut temp_dirs = BTreeMap::new(); let mut boot_counters = [0; NUM_OF_AUTHORITIES]; + let mut protocol_config = ProtocolConfig::get_for_max_version_UNSAFE(); + protocol_config.set_consensus_gc_depth_for_testing(gc_depth); + for (index, _authority_info) in committee.authorities() { let dir = TempDir::new().unwrap(); let (authority, receiver) = make_authority( @@ -603,6 +602,7 @@ mod tests { keypairs.clone(), network_type, boot_counters[index], + protocol_config.clone(), ) .await; assert!(authority.sync_last_known_own_block_enabled(), "Expected syncing of last known own block to be enabled as all authorities are of empty db and boot for first time."); @@ -651,6 +651,7 @@ mod tests { keypairs.clone(), network_type, boot_counters[index_1], + protocol_config.clone(), ) .await; assert!( @@ -671,6 +672,7 @@ mod tests { keypairs, network_type, boot_counters[index_2], + protocol_config.clone(), ) .await; assert!( @@ -704,6 +706,7 @@ mod tests { keypairs: Vec<(NetworkKeyPair, ProtocolKeyPair)>, network_type: ConsensusNetwork, boot_counter: u64, + protocol_config: ProtocolConfig, ) -> (ConsensusAuthority, UnboundedReceiver) { let registry = Registry::new(); @@ -721,15 +724,14 @@ mod tests { let protocol_keypair = keypairs[index].1.clone(); let network_keypair = keypairs[index].0.clone(); - let (sender, receiver) = unbounded_channel("consensus_output"); - let commit_consumer = CommitConsumer::new(sender, 0); + let (commit_consumer, commit_receiver, _) = CommitConsumer::new(0); let authority = ConsensusAuthority::start( network_type, index, committee, parameters, - ProtocolConfig::get_for_max_version_UNSAFE(), + protocol_config, protocol_keypair, network_keypair, Arc::new(txn_verifier), @@ -739,6 +741,6 @@ mod tests { ) .await; - (authority, receiver) + (authority, commit_receiver) } } diff --git a/consensus/core/src/authority_service.rs b/consensus/core/src/authority_service.rs index 444312190f9a1..702bf657f7b6d 100644 --- a/consensus/core/src/authority_service.rs +++ b/consensus/core/src/authority_service.rs @@ -94,7 +94,7 @@ impl NetworkService for AuthorityService { .metrics .node_metrics .invalid_blocks - .with_label_values(&[peer_hostname, "handle_send_block"]) + .with_label_values(&[peer_hostname, "handle_send_block", "UnexpectedAuthority"]) .inc(); let e = ConsensusError::UnexpectedAuthority(signed_block.author(), peer); info!("Block with wrong authority from {}: {}", peer, e); @@ -108,7 +108,7 @@ impl NetworkService for AuthorityService { .metrics .node_metrics .invalid_blocks - .with_label_values(&[peer_hostname, "handle_send_block"]) + .with_label_values(&[peer_hostname, "handle_send_block", e.clone().name()]) .inc(); info!("Invalid block from {}: {}", peer, e); return Err(e); @@ -600,20 +600,21 @@ async fn make_recv_future( #[cfg(test)] mod tests { - use crate::commit::CommitRange; - use crate::test_dag_builder::DagBuilder; use crate::{ authority_service::AuthorityService, block::BlockAPI, block::{BlockRef, SignedBlock, TestBlock, VerifiedBlock}, + commit::CommitRange, commit_vote_monitor::CommitVoteMonitor, context::Context, core_thread::{CoreError, CoreThreadDispatcher}, dag_state::DagState, error::ConsensusResult, network::{BlockStream, NetworkClient, NetworkService}, + round_prober::QuorumRound, storage::mem_store::MemStore, synchronizer::Synchronizer, + test_dag_builder::DagBuilder, Round, }; use async_trait::async_trait; @@ -665,7 +666,11 @@ mod tests { todo!() } - fn set_propagation_delay(&self, _delay: Round) -> Result<(), CoreError> { + fn set_propagation_delay_and_quorum_rounds( + &self, + _delay: Round, + _quorum_rounds: Vec, + ) -> Result<(), CoreError> { todo!() } diff --git a/consensus/core/src/base_committer.rs b/consensus/core/src/base_committer.rs index 41a0b5cbd6616..7c390f9d47f2a 100644 --- a/consensus/core/src/base_committer.rs +++ b/consensus/core/src/base_committer.rs @@ -227,17 +227,31 @@ impl BaseCommitter { leader_block: &VerifiedBlock, all_votes: &mut HashMap, ) -> bool { + let (gc_enabled, gc_round) = { + let dag_state = self.dag_state.read(); + (dag_state.gc_enabled(), dag_state.gc_round()) + }; + let mut votes_stake_aggregator = StakeAggregator::::new(); for reference in potential_certificate.ancestors() { let is_vote = if let Some(is_vote) = all_votes.get(reference) { *is_vote } else { - let potential_vote = self - .dag_state - .read() - .get_block(reference) - .unwrap_or_else(|| panic!("Block not found in storage: {:?}", reference)); - let is_vote = self.is_vote(&potential_vote, leader_block); + let potential_vote = self.dag_state.read().get_block(reference); + + let is_vote = if gc_enabled { + if let Some(potential_vote) = potential_vote { + self.is_vote(&potential_vote, leader_block) + } else { + assert!(reference.round <= gc_round, "Block not found in storage: {:?} , and is not below gc_round: {gc_round}", reference); + false + } + } else { + let potential_vote = potential_vote + .unwrap_or_else(|| panic!("Block not found in storage: {:?}", reference)); + self.is_vote(&potential_vote, leader_block) + }; + all_votes.insert(*reference, is_vote); is_vote }; diff --git a/consensus/core/src/block_manager.rs b/consensus/core/src/block_manager.rs index ba4a6e740e063..e5d9112b319e4 100644 --- a/consensus/core/src/block_manager.rs +++ b/consensus/core/src/block_manager.rs @@ -14,7 +14,7 @@ use parking_lot::RwLock; use tracing::{debug, trace, warn}; use crate::{ - block::{BlockAPI, BlockRef, VerifiedBlock}, + block::{BlockAPI, BlockRef, VerifiedBlock, GENESIS_ROUND}, block_verifier::BlockVerifier, context::Context, dag_state::DagState, @@ -114,69 +114,15 @@ impl BlockManager { missing_blocks.extend(ancestors_to_fetch); continue; } - TryAcceptResult::Processed => continue, + TryAcceptResult::Processed | TryAcceptResult::Skipped => continue, }; // If the block is accepted, try to unsuspend its children blocks if any. - let unsuspended_blocks = self.try_unsuspend_children_blocks(&block); - - // Try to verify the block and its children for timestamp, with ancestor blocks. - let mut blocks_to_accept: BTreeMap = BTreeMap::new(); - let mut blocks_to_reject: BTreeMap = BTreeMap::new(); - { - 'block: for b in iter::once(block).chain(unsuspended_blocks) { - let ancestors = self.dag_state.read().get_blocks(b.ancestors()); - assert_eq!(b.ancestors().len(), ancestors.len()); - let mut ancestor_blocks = vec![]; - 'ancestor: for (ancestor_ref, found) in - b.ancestors().iter().zip(ancestors.into_iter()) - { - if let Some(found_block) = found { - // This invariant should be guaranteed by DagState. - assert_eq!(ancestor_ref, &found_block.reference()); - ancestor_blocks.push(found_block); - continue 'ancestor; - } - // blocks_to_accept have not been added to DagState yet, but they - // can appear in ancestors. - if blocks_to_accept.contains_key(ancestor_ref) { - ancestor_blocks.push(blocks_to_accept[ancestor_ref].clone()); - continue 'ancestor; - } - // If an ancestor is already rejected, reject this block as well. - if blocks_to_reject.contains_key(ancestor_ref) { - blocks_to_reject.insert(b.reference(), b); - continue 'block; - } - panic!("Unsuspended block {:?} has a missing ancestor! Ancestor not found in DagState: {:?}", b, ancestor_ref); - } - if let Err(e) = self.block_verifier.check_ancestors(&b, &ancestor_blocks) { - warn!("Block {:?} failed to verify ancestors: {}", b, e); - blocks_to_reject.insert(b.reference(), b); - } else { - blocks_to_accept.insert(b.reference(), b); - } - } - } - for (block_ref, block) in blocks_to_reject { - self.context - .metrics - .node_metrics - .invalid_blocks - .with_label_values(&[&block_ref.author.to_string(), "accept_block"]) - .inc(); - warn!("Invalid block {:?} is rejected", block); - } - - // TODO: report blocks_to_reject to peers. - - // Insert the accepted blocks into DAG state so future blocks including them as - // ancestors do not get suspended. - let blocks_to_accept: Vec<_> = blocks_to_accept.into_values().collect(); - self.dag_state - .write() - .accept_blocks(blocks_to_accept.clone()); + let unsuspended_blocks = self.try_unsuspend_children_blocks(block.reference()); + // Verify block timestamps + let blocks_to_accept = self + .verify_block_timestamps_and_accept(iter::once(block).chain(unsuspended_blocks)); accepted_blocks.extend(blocks_to_accept); } @@ -198,6 +144,104 @@ impl BlockManager { (accepted_blocks, missing_blocks) } + // TODO: remove once timestamping is refactored to the new approach. + // Verifies each block's timestamp based on its ancestors, and persists in store all the valid blocks that should be accepted. Method + // returns the accepted and persisted blocks. + fn verify_block_timestamps_and_accept( + &mut self, + unsuspended_blocks: impl IntoIterator, + ) -> Vec { + let (gc_enabled, gc_round) = { + let dag_state = self.dag_state.read(); + (dag_state.gc_enabled(), dag_state.gc_round()) + }; + // Try to verify the block and its children for timestamp, with ancestor blocks. + let mut blocks_to_accept: BTreeMap = BTreeMap::new(); + let mut blocks_to_reject: BTreeMap = BTreeMap::new(); + { + 'block: for b in unsuspended_blocks { + let ancestors = self.dag_state.read().get_blocks(b.ancestors()); + assert_eq!(b.ancestors().len(), ancestors.len()); + let mut ancestor_blocks = vec![]; + 'ancestor: for (ancestor_ref, found) in + b.ancestors().iter().zip(ancestors.into_iter()) + { + if let Some(found_block) = found { + // This invariant should be guaranteed by DagState. + assert_eq!(ancestor_ref, &found_block.reference()); + ancestor_blocks.push(Some(found_block)); + continue 'ancestor; + } + // blocks_to_accept have not been added to DagState yet, but they + // can appear in ancestors. + if blocks_to_accept.contains_key(ancestor_ref) { + ancestor_blocks.push(Some(blocks_to_accept[ancestor_ref].clone())); + continue 'ancestor; + } + // If an ancestor is already rejected, reject this block as well. + if blocks_to_reject.contains_key(ancestor_ref) { + blocks_to_reject.insert(b.reference(), b); + continue 'block; + } + + // When gc is enabled it's possible that we indeed won't find any ancestors that are passed gc_round. That's ok. We don't need to panic here. + // We do want to panic if gc_enabled we and have an ancestor that is > gc_round, or gc is disabled. + if gc_enabled + && ancestor_ref.round > GENESIS_ROUND + && ancestor_ref.round <= gc_round + { + debug!( + "Block {:?} has a missing ancestor: {:?} passed GC round {}", + b.reference(), + ancestor_ref, + gc_round + ); + ancestor_blocks.push(None); + } else { + panic!("Unsuspended block {:?} has a missing ancestor! Ancestor not found in DagState: {:?}", b, ancestor_ref); + } + } + if let Err(e) = + self.block_verifier + .check_ancestors(&b, &ancestor_blocks, gc_enabled, gc_round) + { + warn!("Block {:?} failed to verify ancestors: {}", b, e); + blocks_to_reject.insert(b.reference(), b); + } else { + blocks_to_accept.insert(b.reference(), b); + } + } + } + + // TODO: report blocks_to_reject to peers. + for (block_ref, block) in blocks_to_reject { + let hostname = self + .context + .committee + .authority(block_ref.author) + .hostname + .clone(); + + self.context + .metrics + .node_metrics + .invalid_blocks + .with_label_values(&[&hostname, "accept_block", "InvalidAncestors"]) + .inc(); + warn!("Invalid block {:?} is rejected", block); + } + + let blocks_to_accept = blocks_to_accept.values().cloned().collect::>(); + + // Insert the accepted blocks into DAG state so future blocks including them as + // ancestors do not get suspended. + self.dag_state + .write() + .accept_blocks(blocks_to_accept.clone()); + + blocks_to_accept + } + /// Tries to accept the provided block. To accept a block its ancestors must have been already successfully accepted. If /// block is accepted then Some result is returned. None is returned when either the block is suspended or the block /// has been already accepted before. @@ -206,17 +250,47 @@ impl BlockManager { let mut missing_ancestors = BTreeSet::new(); let mut ancestors_to_fetch = BTreeSet::new(); let dag_state = self.dag_state.read(); + let gc_round = dag_state.gc_round(); + let gc_enabled = dag_state.gc_enabled(); // If block has been already received and suspended, or already processed and stored, or is a genesis block, then skip it. if self.suspended_blocks.contains_key(&block_ref) || dag_state.contains_block(&block_ref) { return TryAcceptResult::Processed; } - let ancestors = block.ancestors(); + // If the block is <= gc_round, then we simply skip its processing as there is no meaning do any action on it or even store it. + if gc_enabled && block.round() <= gc_round { + let hostname = self + .context + .committee + .authority(block.author()) + .hostname + .as_str(); + self.context + .metrics + .node_metrics + .block_manager_skipped_blocks + .with_label_values(&[hostname]) + .inc(); + return TryAcceptResult::Skipped; + } + + // Keep only the ancestors that are greater than the GC round to check for their existence. Keep in mind that if GC is disabled + // then gc_round will be 0 and all ancestors will be considered. + let ancestors = if gc_enabled { + block + .ancestors() + .iter() + .filter(|ancestor| ancestor.round == GENESIS_ROUND || ancestor.round > gc_round) + .cloned() + .collect::>() + } else { + block.ancestors().to_vec() + }; // make sure that we have all the required ancestors in store for (found, ancestor) in dag_state - .contains_blocks(ancestors.to_vec()) + .contains_blocks(ancestors.clone()) .into_iter() .zip(ancestors.iter()) { @@ -280,23 +354,18 @@ impl BlockManager { /// Given an accepted block `accepted_block` it attempts to accept all the suspended children blocks assuming such exist. /// All the unsuspended / accepted blocks are returned as a vector in causal order. - fn try_unsuspend_children_blocks( - &mut self, - accepted_block: &VerifiedBlock, - ) -> Vec { + fn try_unsuspend_children_blocks(&mut self, accepted_block: BlockRef) -> Vec { let mut unsuspended_blocks = vec![]; - let mut to_process_blocks = vec![accepted_block.clone()]; + let mut to_process_blocks = vec![accepted_block]; - while let Some(block) = to_process_blocks.pop() { + while let Some(block_ref) = to_process_blocks.pop() { // And try to check if its direct children can be unsuspended - if let Some(block_refs_with_missing_deps) = - self.missing_ancestors.remove(&block.reference()) - { + if let Some(block_refs_with_missing_deps) = self.missing_ancestors.remove(&block_ref) { for r in block_refs_with_missing_deps { // For each dependency try to unsuspend it. If that's successful then we add it to the queue so // we can recursively try to unsuspend its children. - if let Some(block) = self.try_unsuspend_block(&r, &block.reference()) { - to_process_blocks.push(block.block.clone()); + if let Some(block) = self.try_unsuspend_block(&r, &block_ref) { + to_process_blocks.push(block.block.reference()); unsuspended_blocks.push(block); } } @@ -359,6 +428,69 @@ impl BlockManager { None } + /// Tries to unsuspend any blocks for the latest gc round. If gc round hasn't changed then no blocks will be unsuspended due to + /// this action. + pub(crate) fn try_unsuspend_blocks_for_latest_gc_round(&mut self) { + let _s = monitored_scope("BlockManager::try_unsuspend_blocks_for_latest_gc_round"); + let (gc_enabled, gc_round) = { + let dag_state = self.dag_state.read(); + (dag_state.gc_enabled(), dag_state.gc_round()) + }; + let mut blocks_unsuspended_below_gc_round = 0; + let mut blocks_gc_ed = 0; + + if !gc_enabled { + trace!("GC is disabled, no blocks will attempt to get unsuspended."); + return; + } + + while let Some((block_ref, _children_refs)) = self.missing_ancestors.first_key_value() { + // If the first block in the missing ancestors is higher than the gc_round, then we can't unsuspend it yet. So we just put it back + // and we terminate the iteration as any next entry will be of equal or higher round anyways. + if block_ref.round > gc_round { + return; + } + + blocks_gc_ed += 1; + + assert!(!self.suspended_blocks.contains_key(block_ref), "Block should not be suspended, as we are causally GC'ing and no suspended block should exist for a missing ancestor."); + + // Also remove it from the missing list - we don't want to keep looking for it. + self.missing_blocks.remove(block_ref); + + // Find all the children blocks that have a dependency on this one and try to unsuspend them + let unsuspended_blocks = self.try_unsuspend_children_blocks(*block_ref); + + unsuspended_blocks.iter().for_each(|block| { + if block.round() <= gc_round { + blocks_unsuspended_below_gc_round += 1; + } + }); + + // Now validate their timestamps and accept them + let accepted_blocks = self.verify_block_timestamps_and_accept(unsuspended_blocks); + for block in accepted_blocks { + let hostname = self + .context + .committee + .authority(block.author()) + .hostname + .as_str(); + self.context + .metrics + .node_metrics + .block_manager_gc_unsuspended_blocks + .with_label_values(&[hostname]) + .inc(); + } + } + + debug!( + "Total {} blocks unsuspended and total blocks {} gc'ed <= gc_round {}", + blocks_unsuspended_below_gc_round, blocks_gc_ed, gc_round + ); + } + /// Returns all the blocks that are currently missing and needed in order to accept suspended /// blocks. pub(crate) fn missing_blocks(&self) -> BTreeSet { @@ -413,6 +545,9 @@ enum TryAcceptResult { // The block has been processed before and already exists in BlockManager (and is suspended) or // in DagState (so has been already accepted). No further processing has been done at this point. Processed, + // When a received block is <= gc_round, then we simply skip its processing as there is no meaning + // do any action on it or even store it. + Skipped, } #[cfg(test)] @@ -422,16 +557,20 @@ mod tests { use consensus_config::AuthorityIndex; use parking_lot::RwLock; use rand::{prelude::StdRng, seq::SliceRandom, SeedableRng}; + use rstest::rstest; use crate::{ - block::{BlockAPI, BlockRef, SignedBlock, VerifiedBlock}, + block::{BlockAPI, BlockDigest, BlockRef, SignedBlock, VerifiedBlock}, block_manager::BlockManager, block_verifier::{BlockVerifier, NoopBlockVerifier}, + commit::TrustedCommit, context::Context, dag_state::DagState, error::{ConsensusError, ConsensusResult}, storage::mem_store::MemStore, test_dag_builder::DagBuilder, + test_dag_parser::parse_dag, + CommitDigest, Round, }; #[tokio::test] @@ -549,7 +688,7 @@ mod tests { let (accepted_blocks, missing) = block_manager.try_accept_blocks(all_blocks.clone()); // THEN - assert!(accepted_blocks.len() == 8); + assert_eq!(accepted_blocks.len(), 8); assert_eq!( accepted_blocks, all_blocks @@ -566,10 +705,158 @@ mod tests { assert!(accepted_blocks.is_empty()); } + /// Tests that the block manager accepts blocks when some or all of their causal history is below or equal to the GC round. #[tokio::test] - async fn accept_blocks_unsuspend_children_blocks() { + async fn accept_blocks_with_causal_history_below_gc_round() { // GIVEN - let (context, _key_pairs) = Context::new_for_test(4); + let (mut context, _key_pairs) = Context::new_for_test(4); + + // We set the gc depth to 4 + context + .protocol_config + .set_consensus_gc_depth_for_testing(4); + let context = Arc::new(context); + let store = Arc::new(MemStore::new()); + let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone()))); + + // We "fake" the commit for round 10, so we can test the GC round 6 (commit_round - gc_depth = 10 - 4 = 6) + let last_commit = TrustedCommit::new_for_test( + 10, + CommitDigest::MIN, + context.clock.timestamp_utc_ms(), + BlockRef::new(10, AuthorityIndex::new_for_test(0), BlockDigest::MIN), + vec![], + ); + dag_state.write().set_last_commit(last_commit); + assert_eq!( + dag_state.read().gc_round(), + 6, + "GC round should have moved to round 6" + ); + + let mut block_manager = + BlockManager::new(context.clone(), dag_state, Arc::new(NoopBlockVerifier)); + + // create a DAG of 10 rounds with some weak links for the blocks of round 9 + let dag_str = "DAG { + Round 0 : { 4 }, + Round 1 : { * }, + Round 2 : { * }, + Round 3 : { * }, + Round 4 : { * }, + Round 5 : { * }, + Round 6 : { * }, + Round 7 : { + A -> [*], + B -> [*], + C -> [*], + } + Round 8 : { + A -> [*], + B -> [*], + C -> [*], + }, + Round 9 : { + A -> [A8, B8, C8, D6], + B -> [A8, B8, C8, D6], + C -> [A8, B8, C8, D6], + D -> [A8, B8, C8, D6], + }, + Round 10 : { * }, + }"; + + let (_, dag_builder) = parse_dag(dag_str).expect("Invalid dag"); + + // Now take all the blocks for round 7 & 8 , which are above the gc_round = 6. + // All those blocks should eventually be returned as accepted. Pay attention that without GC none of those blocks should get accepted. + let blocks_ranges = vec![7..=8 as Round, 9..=10 as Round]; + + for rounds_range in blocks_ranges { + let all_blocks = dag_builder + .blocks + .values() + .filter(|block| rounds_range.contains(&block.round())) + .cloned() + .collect::>(); + + // WHEN + let mut reversed_blocks = all_blocks.clone(); + reversed_blocks.sort_by_key(|b| std::cmp::Reverse(b.reference())); + let (mut accepted_blocks, missing) = block_manager.try_accept_blocks(reversed_blocks); + accepted_blocks.sort_by_key(|a| a.reference()); + + // THEN + assert_eq!(accepted_blocks, all_blocks.to_vec()); + assert!(missing.is_empty()); + assert!(block_manager.is_empty()); + + let (accepted_blocks, _) = block_manager.try_accept_blocks(all_blocks); + assert!(accepted_blocks.is_empty()); + } + } + + /// Blocks that are attempted to be accepted but are <= gc_round they will be skipped for processing. Nothing + /// should be stored or trigger any unsuspension etc. + #[tokio::test] + async fn skip_accepting_blocks_below_gc_round() { + // GIVEN + let (mut context, _key_pairs) = Context::new_for_test(4); + // We set the gc depth to 4 + context + .protocol_config + .set_consensus_gc_depth_for_testing(4); + let context = Arc::new(context); + let store = Arc::new(MemStore::new()); + let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone()))); + + // We "fake" the commit for round 10, so we can test the GC round 6 (commit_round - gc_depth = 10 - 4 = 6) + let last_commit = TrustedCommit::new_for_test( + 10, + CommitDigest::MIN, + context.clock.timestamp_utc_ms(), + BlockRef::new(10, AuthorityIndex::new_for_test(0), BlockDigest::MIN), + vec![], + ); + dag_state.write().set_last_commit(last_commit); + assert_eq!( + dag_state.read().gc_round(), + 6, + "GC round should have moved to round 6" + ); + + let mut block_manager = + BlockManager::new(context.clone(), dag_state, Arc::new(NoopBlockVerifier)); + + // create a DAG of 6 rounds + let mut dag_builder = DagBuilder::new(context.clone()); + dag_builder.layers(1..=6).build(); + + let all_blocks = dag_builder.blocks.values().cloned().collect::>(); + + // WHEN + let (accepted_blocks, missing) = block_manager.try_accept_blocks(all_blocks.clone()); + + // THEN + assert!(accepted_blocks.is_empty()); + assert!(missing.is_empty()); + assert!(block_manager.is_empty()); + } + + /// The test generate blocks for a well connected DAG and feed them to block manager in random order. In the end all the + /// blocks should be uniquely suspended and no missing blocks should exist. The test will run for both gc_enabled/disabled. + /// When gc is enabeld we set a high gc_depth value so in practice gc_round will be 0, but we'll be able to test in the common case + /// that this work exactly the same way as when gc is disabled. + #[rstest] + #[tokio::test] + async fn accept_blocks_unsuspend_children_blocks(#[values(false, true)] gc_enabled: bool) { + // GIVEN + let (mut context, _key_pairs) = Context::new_for_test(4); + + if gc_enabled { + context + .protocol_config + .set_consensus_gc_depth_for_testing(10); + } let context = Arc::new(context); // create a DAG of rounds 1 ~ 3 @@ -610,6 +897,82 @@ mod tests { } } + #[rstest] + #[tokio::test] + async fn unsuspend_blocks_for_latest_gc_round(#[values(5, 10, 14)] gc_depth: u32) { + telemetry_subscribers::init_for_testing(); + // GIVEN + let (mut context, _key_pairs) = Context::new_for_test(4); + + if gc_depth > 0 { + context + .protocol_config + .set_consensus_gc_depth_for_testing(gc_depth); + } + let context = Arc::new(context); + + // create a DAG of rounds 1 ~ gc_depth * 2 + let mut dag_builder = DagBuilder::new(context.clone()); + dag_builder.layers(1..=gc_depth * 2).build(); + + // Pay attention that we start from round 2. Round 1 will always be missing so no matter what we do we can't unsuspend it unless + // gc_round has advanced to round >= 1. + let mut all_blocks = dag_builder + .blocks + .values() + .filter(|block| block.round() > 1) + .cloned() + .collect::>(); + + // Now randomize the sequence of sending the blocks to block manager. In the end all the blocks should be uniquely + // suspended and no missing blocks should exist. + for seed in 0..100u8 { + all_blocks.shuffle(&mut StdRng::from_seed([seed; 32])); + + let store = Arc::new(MemStore::new()); + let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone()))); + + let mut block_manager = BlockManager::new( + context.clone(), + dag_state.clone(), + Arc::new(NoopBlockVerifier), + ); + + // WHEN + for block in &all_blocks { + let (accepted_blocks, _) = block_manager.try_accept_blocks(vec![block.clone()]); + assert!(accepted_blocks.is_empty()); + } + assert!(!block_manager.is_empty()); + + // AND + // Trigger a commit which will advance GC round + let last_commit = TrustedCommit::new_for_test( + gc_depth * 2, + CommitDigest::MIN, + context.clock.timestamp_utc_ms(), + BlockRef::new( + gc_depth * 2, + AuthorityIndex::new_for_test(0), + BlockDigest::MIN, + ), + vec![], + ); + dag_state.write().set_last_commit(last_commit); + + // AND + block_manager.try_unsuspend_blocks_for_latest_gc_round(); + + // THEN + assert!(block_manager.is_empty()); + + // AND ensure that all have been accepted to the DAG + for block in &all_blocks { + assert!(dag_state.read().contains_block(&block.reference())); + } + } + } + struct TestBlockVerifier { fail: BTreeSet, } @@ -628,7 +991,9 @@ mod tests { fn check_ancestors( &self, block: &VerifiedBlock, - _ancestors: &[VerifiedBlock], + _ancestors: &[Option], + _gc_enabled: bool, + _gc_round: Round, ) -> ConsensusResult<()> { if self.fail.contains(&block.reference()) { Err(ConsensusError::InvalidBlockTimestamp { diff --git a/consensus/core/src/block_verifier.rs b/consensus/core/src/block_verifier.rs index 8582e06a5dc56..f794c96b7007d 100644 --- a/consensus/core/src/block_verifier.rs +++ b/consensus/core/src/block_verifier.rs @@ -11,6 +11,7 @@ use crate::{ context::Context, error::{ConsensusError, ConsensusResult}, transaction::TransactionVerifier, + Round, }; pub(crate) trait BlockVerifier: Send + Sync + 'static { @@ -26,7 +27,9 @@ pub(crate) trait BlockVerifier: Send + Sync + 'static { fn check_ancestors( &self, block: &VerifiedBlock, - ancestors: &[VerifiedBlock], + ancestors: &[Option], + gc_enabled: bool, + gc_round: Round, ) -> ConsensusResult<()>; } @@ -185,20 +188,44 @@ impl BlockVerifier for SignedBlockVerifier { fn check_ancestors( &self, block: &VerifiedBlock, - ancestors: &[VerifiedBlock], + ancestors: &[Option], + gc_enabled: bool, + gc_round: Round, ) -> ConsensusResult<()> { - assert_eq!(block.ancestors().len(), ancestors.len()); - // This checks the invariant that block timestamp >= max ancestor timestamp. - let mut max_timestamp_ms = BlockTimestampMs::MIN; - for (ancestor_ref, ancestor_block) in block.ancestors().iter().zip(ancestors.iter()) { - assert_eq!(ancestor_ref, &ancestor_block.reference()); - max_timestamp_ms = max_timestamp_ms.max(ancestor_block.timestamp_ms()); - } - if max_timestamp_ms > block.timestamp_ms() { - return Err(ConsensusError::InvalidBlockTimestamp { - max_timestamp_ms, - block_timestamp_ms: block.timestamp_ms(), - }); + if gc_enabled { + // TODO: will be removed with new timestamp calculation is in place as all these will be irrelevant. + // When gc is enabled we don't have gaurantees that all ancestors will be available. We'll take into account only the passed gc_round ones + // for the timestamp check. + let mut max_timestamp_ms = BlockTimestampMs::MIN; + for ancestor in ancestors.iter().flatten() { + if ancestor.round() <= gc_round { + continue; + } + max_timestamp_ms = max_timestamp_ms.max(ancestor.timestamp_ms()); + if max_timestamp_ms > block.timestamp_ms() { + return Err(ConsensusError::InvalidBlockTimestamp { + max_timestamp_ms, + block_timestamp_ms: block.timestamp_ms(), + }); + } + } + } else { + assert_eq!(block.ancestors().len(), ancestors.len()); + // This checks the invariant that block timestamp >= max ancestor timestamp. + let mut max_timestamp_ms = BlockTimestampMs::MIN; + for (ancestor_ref, ancestor_block) in block.ancestors().iter().zip(ancestors.iter()) { + let ancestor_block = ancestor_block + .as_ref() + .expect("There should never be an empty slot"); + assert_eq!(ancestor_ref, &ancestor_block.reference()); + max_timestamp_ms = max_timestamp_ms.max(ancestor_block.timestamp_ms()); + } + if max_timestamp_ms > block.timestamp_ms() { + return Err(ConsensusError::InvalidBlockTimestamp { + max_timestamp_ms, + block_timestamp_ms: block.timestamp_ms(), + }); + } } Ok(()) } @@ -215,7 +242,9 @@ impl BlockVerifier for NoopBlockVerifier { fn check_ancestors( &self, _block: &VerifiedBlock, - _ancestors: &[VerifiedBlock], + _ancestors: &[Option], + _gc_enabled: bool, + _gc_round: Round, ) -> ConsensusResult<()> { Ok(()) } @@ -224,6 +253,7 @@ impl BlockVerifier for NoopBlockVerifier { #[cfg(test)] mod test { use consensus_config::AuthorityIndex; + use rstest::rstest; use super::*; use crate::{ @@ -524,22 +554,27 @@ mod test { } } + /// Tests the block's ancestors for timestamp monotonicity. Test will run for both when gc is enabled and disabled, but + /// with none of the ancestors being below the gc_round. + #[rstest] #[tokio::test] - async fn test_check_ancestors() { + async fn test_check_ancestors(#[values(false, true)] gc_enabled: bool) { let num_authorities = 4; let (context, _keypairs) = Context::new_for_test(num_authorities); let context = Arc::new(context); let verifier = SignedBlockVerifier::new(context.clone(), Arc::new(TxnSizeVerifier {})); + let gc_round = 0; let mut ancestor_blocks = vec![]; for i in 0..num_authorities { let test_block = TestBlock::new(10, i as u32) .set_timestamp_ms(1000 + 100 * i as BlockTimestampMs) .build(); - ancestor_blocks.push(VerifiedBlock::new_for_test(test_block)); + ancestor_blocks.push(Some(VerifiedBlock::new_for_test(test_block))); } let ancestor_refs = ancestor_blocks .iter() + .flatten() .map(|block| block.reference()) .collect::>(); @@ -551,7 +586,7 @@ mod test { .build(); let verified_block = VerifiedBlock::new_for_test(block); assert!(verifier - .check_ancestors(&verified_block, &ancestor_blocks) + .check_ancestors(&verified_block, &ancestor_blocks, gc_enabled, gc_round) .is_ok()); } @@ -563,7 +598,81 @@ mod test { .build(); let verified_block = VerifiedBlock::new_for_test(block); assert!(matches!( - verifier.check_ancestors(&verified_block, &ancestor_blocks), + verifier.check_ancestors(&verified_block, &ancestor_blocks, gc_enabled, gc_round), + Err(ConsensusError::InvalidBlockTimestamp { + max_timestamp_ms: _, + block_timestamp_ms: _ + }) + )); + } + } + + #[tokio::test] + async fn test_check_ancestors_passed_gc_round() { + let num_authorities = 4; + let (context, _keypairs) = Context::new_for_test(num_authorities); + let context = Arc::new(context); + let verifier = SignedBlockVerifier::new(context.clone(), Arc::new(TxnSizeVerifier {})); + let gc_enabled = true; + let gc_round = 3; + + let mut ancestor_blocks = vec![]; + + // Create one block just on the `gc_round` (so it should be considered garbage collected). This has higher + // timestamp that the block we are testing. + let test_block = TestBlock::new(gc_round, 0_u32) + .set_timestamp_ms(1500 as BlockTimestampMs) + .build(); + ancestor_blocks.push(Some(VerifiedBlock::new_for_test(test_block))); + + // Rest of the blocks + for i in 1..=3 { + let test_block = TestBlock::new(gc_round + 1, i as u32) + .set_timestamp_ms(1000 + 100 * i as BlockTimestampMs) + .build(); + ancestor_blocks.push(Some(VerifiedBlock::new_for_test(test_block))); + } + + let ancestor_refs = ancestor_blocks + .iter() + .flatten() + .map(|block| block.reference()) + .collect::>(); + + // Block respecting timestamp invariant. + { + let block = TestBlock::new(gc_round + 2, 0) + .set_ancestors(ancestor_refs.clone()) + .set_timestamp_ms(1600) + .build(); + let verified_block = VerifiedBlock::new_for_test(block); + assert!(verifier + .check_ancestors(&verified_block, &ancestor_blocks, gc_enabled, gc_round) + .is_ok()); + } + + // Block not respecting timestamp invariant for the block that is garbage collected + // Validation should pass. + { + let block = TestBlock::new(11, 0) + .set_ancestors(ancestor_refs.clone()) + .set_timestamp_ms(1400) + .build(); + let verified_block = VerifiedBlock::new_for_test(block); + assert!(verifier + .check_ancestors(&verified_block, &ancestor_blocks, gc_enabled, gc_round) + .is_ok()); + } + + // Block not respecting timestamp invariant for the blocks that are not garbage collected + { + let block = TestBlock::new(11, 0) + .set_ancestors(ancestor_refs.clone()) + .set_timestamp_ms(1100) + .build(); + let verified_block = VerifiedBlock::new_for_test(block); + assert!(matches!( + verifier.check_ancestors(&verified_block, &ancestor_blocks, gc_enabled, gc_round), Err(ConsensusError::InvalidBlockTimestamp { max_timestamp_ms: _, block_timestamp_ms: _ diff --git a/consensus/core/src/broadcaster.rs b/consensus/core/src/broadcaster.rs index 6a3a26042ead9..d753bf39932c1 100644 --- a/consensus/core/src/broadcaster.rs +++ b/consensus/core/src/broadcaster.rs @@ -316,7 +316,7 @@ mod test { if index == context.own_index { continue; } - assert!(blocks_sent.get(&index).is_none()); + assert!(!blocks_sent.contains_key(&index)); } // ... until LAST_BLOCK_RETRY_INTERVAL diff --git a/consensus/core/src/commit.rs b/consensus/core/src/commit.rs index d40ee0a5a4ced..d1fbed756b461 100644 --- a/consensus/core/src/commit.rs +++ b/consensus/core/src/commit.rs @@ -19,6 +19,7 @@ use crate::{ block::{BlockAPI, BlockRef, BlockTimestampMs, Round, Slot, VerifiedBlock}, leader_scoring::ReputationScores, storage::Store, + TransactionIndex, }; /// Index of a commit among all consensus commits. @@ -109,6 +110,7 @@ pub(crate) struct CommitV1 { leader: BlockRef, /// Refs to committed blocks, in the commit order. blocks: Vec, + // TODO(fastpath): record rejected transactions. } impl CommitAPI for CommitV1 { @@ -293,6 +295,8 @@ pub struct CommittedSubDag { pub leader: BlockRef, /// All the committed blocks that are part of this sub-dag pub blocks: Vec, + /// Indices of rejected transactions in each block. + pub rejected_transactions_by_block: Vec>, /// The timestamp of the commit, obtained from the timestamp of the leader block. pub timestamp_ms: BlockTimestampMs, /// The reference of the commit. @@ -309,13 +313,16 @@ impl CommittedSubDag { pub fn new( leader: BlockRef, blocks: Vec, + rejected_transactions_by_block: Vec>, timestamp_ms: BlockTimestampMs, commit_ref: CommitRef, reputation_scores_desc: Vec<(AuthorityIndex, u64)>, ) -> Self { + assert_eq!(blocks.len(), rejected_transactions_by_block.len()); Self { leader, blocks, + rejected_transactions_by_block, timestamp_ms, commit_ref, reputation_scores_desc, @@ -386,11 +393,14 @@ pub fn load_committed_subdag_from_store( commit_block }) .collect::>(); + // TODO(fastpath): recover rejected transaction indices from commit. + let rejected_transactions = vec![vec![]; blocks.len()]; let leader_block_idx = leader_block_idx.expect("Leader block must be in the sub-dag"); let leader_block_ref = blocks[leader_block_idx].reference(); CommittedSubDag::new( leader_block_ref, blocks, + rejected_transactions, commit.timestamp_ms(), commit.reference(), reputation_scores_desc, @@ -508,16 +518,6 @@ pub(crate) struct CommitInfo { pub(crate) reputation_scores: ReputationScores, } -impl CommitInfo { - // Returns a new CommitInfo. - pub(crate) fn new(committed_rounds: Vec, reputation_scores: ReputationScores) -> Self { - CommitInfo { - committed_rounds, - reputation_scores, - } - } -} - /// CommitRange stores a range of CommitIndex. The range contains the start (inclusive) /// and end (inclusive) commit indices and can be ordered for use as the key of a table. /// diff --git a/consensus/core/src/commit_consumer.rs b/consensus/core/src/commit_consumer.rs index 2d06208d03964..4e40e6da4a376 100644 --- a/consensus/core/src/commit_consumer.rs +++ b/consensus/core/src/commit_consumer.rs @@ -4,13 +4,20 @@ use std::sync::{Arc, RwLock}; use tokio::sync::watch; -use mysten_metrics::monitored_mpsc::UnboundedSender; +use mysten_metrics::monitored_mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; -use crate::{CommitIndex, CommittedSubDag}; +use crate::{CommitIndex, CommittedSubDag, TransactionIndex, VerifiedBlock}; +#[derive(Clone)] pub struct CommitConsumer { - // A channel to send the committed sub dags through - pub(crate) sender: UnboundedSender, + // A channel to output the committed sub dags. + pub(crate) commit_sender: UnboundedSender, + // A channel to output certified and rejected transactions by batches of blocks. + // Each tuple contains the block containing transactions, and indices of rejected transactions. + // In each block, transactions that are not rejected are considered certified. + // Batches of blocks are sent together, to improve efficiency. + #[allow(unused)] + pub(crate) transaction_sender: UnboundedSender)>>, // Index of the last commit that the consumer has processed. This is useful for // crash/recovery so mysticeti can replay the commits from the next index. // First commit in the replayed sequence will have index last_processed_commit_index + 1. @@ -22,15 +29,26 @@ pub struct CommitConsumer { impl CommitConsumer { pub fn new( - sender: UnboundedSender, last_processed_commit_index: CommitIndex, - ) -> Self { + ) -> ( + Self, + UnboundedReceiver, + UnboundedReceiver)>>, + ) { + let (commit_sender, commit_receiver) = unbounded_channel("consensus_output"); + let (transaction_sender, transaction_receiver) = unbounded_channel("consensus_certified"); + let monitor = Arc::new(CommitConsumerMonitor::new(last_processed_commit_index)); - Self { - sender, - last_processed_commit_index, - monitor, - } + ( + Self { + commit_sender, + transaction_sender, + last_processed_commit_index, + monitor, + }, + commit_receiver, + transaction_receiver, + ) } pub fn monitor(&self) -> Arc { @@ -54,7 +72,7 @@ impl CommitConsumerMonitor { } } - pub(crate) fn highest_handled_commit(&self) -> CommitIndex { + pub fn highest_handled_commit(&self) -> CommitIndex { *self.highest_handled_commit.borrow() } diff --git a/consensus/core/src/commit_observer.rs b/consensus/core/src/commit_observer.rs index a25113de387fb..9196235c3b934 100644 --- a/consensus/core/src/commit_observer.rs +++ b/consensus/core/src/commit_observer.rs @@ -55,7 +55,7 @@ impl CommitObserver { let mut observer = Self { context, commit_interpreter: Linearizer::new(dag_state.clone(), leader_schedule.clone()), - sender: commit_consumer.sender, + sender: commit_consumer.commit_sender, store, leader_schedule, }; @@ -207,7 +207,7 @@ impl CommitObserver { #[cfg(test)] mod tests { - use mysten_metrics::monitored_mpsc::{unbounded_channel, UnboundedReceiver}; + use mysten_metrics::monitored_mpsc::UnboundedReceiver; use parking_lot::RwLock; use super::*; @@ -227,7 +227,8 @@ mod tests { mem_store.clone(), ))); let last_processed_commit_index = 0; - let (sender, mut receiver) = unbounded_channel("consensus_output"); + let (commit_consumer, mut commit_receiver, _transaction_receiver) = + CommitConsumer::new(last_processed_commit_index); let leader_schedule = Arc::new(LeaderSchedule::from_store( context.clone(), @@ -236,7 +237,7 @@ mod tests { let mut observer = CommitObserver::new( context.clone(), - CommitConsumer::new(sender, last_processed_commit_index), + commit_consumer, dag_state.clone(), mem_store.clone(), leader_schedule, @@ -289,7 +290,7 @@ mod tests { // Check commits sent over consensus output channel is accurate let mut processed_subdag_index = 0; - while let Ok(subdag) = receiver.try_recv() { + while let Ok(subdag) = commit_receiver.try_recv() { assert_eq!(subdag, commits[processed_subdag_index]); assert_eq!(subdag.reputation_scores_desc, vec![]); processed_subdag_index = subdag.commit_ref.index as usize; @@ -299,7 +300,7 @@ mod tests { } assert_eq!(processed_subdag_index, leaders.len()); - verify_channel_empty(&mut receiver); + verify_channel_empty(&mut commit_receiver); // Check commits have been persisted to storage let last_commit = mem_store.read_last_commit().unwrap().unwrap(); @@ -326,8 +327,8 @@ mod tests { mem_store.clone(), ))); let last_processed_commit_index = 0; - let (sender, mut receiver) = unbounded_channel("consensus_output"); - + let (commit_consumer, mut commit_receiver, _transaction_receiver) = + CommitConsumer::new(last_processed_commit_index); let leader_schedule = Arc::new(LeaderSchedule::from_store( context.clone(), dag_state.clone(), @@ -335,7 +336,7 @@ mod tests { let mut observer = CommitObserver::new( context.clone(), - CommitConsumer::new(sender.clone(), last_processed_commit_index), + commit_consumer, dag_state.clone(), mem_store.clone(), leader_schedule.clone(), @@ -370,7 +371,7 @@ mod tests { // Check commits sent over consensus output channel is accurate let mut processed_subdag_index = 0; - while let Ok(subdag) = receiver.try_recv() { + while let Ok(subdag) = commit_receiver.try_recv() { tracing::info!("Processed {subdag}"); assert_eq!(subdag, commits[processed_subdag_index]); assert_eq!(subdag.reputation_scores_desc, vec![]); @@ -381,7 +382,7 @@ mod tests { } assert_eq!(processed_subdag_index, expected_last_processed_index); - verify_channel_empty(&mut receiver); + verify_channel_empty(&mut commit_receiver); // Check last stored commit is correct let last_commit = mem_store.read_last_commit().unwrap().unwrap(); @@ -406,7 +407,7 @@ mod tests { ); let expected_last_sent_index = num_rounds as usize; - while let Ok(subdag) = receiver.try_recv() { + while let Ok(subdag) = commit_receiver.try_recv() { tracing::info!("{subdag} was sent but not processed by consumer"); assert_eq!(subdag, commits[processed_subdag_index]); assert_eq!(subdag.reputation_scores_desc, vec![]); @@ -417,7 +418,7 @@ mod tests { } assert_eq!(processed_subdag_index, expected_last_sent_index); - verify_channel_empty(&mut receiver); + verify_channel_empty(&mut commit_receiver); // Check last stored commit is correct. We should persist the last commit // that was sent over the channel regardless of how the consumer handled @@ -427,9 +428,11 @@ mod tests { // Re-create commit observer starting from index 2 which represents the // last processed index from the consumer over consensus output channel + let (commit_consumer, mut commit_receiver, _transaction_receiver) = + CommitConsumer::new(expected_last_processed_index as CommitIndex); let _observer = CommitObserver::new( context.clone(), - CommitConsumer::new(sender, expected_last_processed_index as CommitIndex), + commit_consumer, dag_state.clone(), mem_store.clone(), leader_schedule, @@ -438,7 +441,7 @@ mod tests { // Check commits sent over consensus output channel is accurate starting // from last processed index of 2 and finishing at last sent index of 3. processed_subdag_index = expected_last_processed_index; - while let Ok(subdag) = receiver.try_recv() { + while let Ok(subdag) = commit_receiver.try_recv() { tracing::info!("Processed {subdag} on resubmission"); assert_eq!(subdag, commits[processed_subdag_index]); assert_eq!(subdag.reputation_scores_desc, vec![]); @@ -449,7 +452,7 @@ mod tests { } assert_eq!(processed_subdag_index, expected_last_sent_index); - verify_channel_empty(&mut receiver); + verify_channel_empty(&mut commit_receiver); } #[tokio::test] @@ -463,7 +466,8 @@ mod tests { mem_store.clone(), ))); let last_processed_commit_index = 0; - let (sender, mut receiver) = unbounded_channel("consensus_output"); + let (commit_consumer, mut commit_receiver, _transaction_receiver) = + CommitConsumer::new(last_processed_commit_index); let leader_schedule = Arc::new(LeaderSchedule::from_store( context.clone(), @@ -472,7 +476,7 @@ mod tests { let mut observer = CommitObserver::new( context.clone(), - CommitConsumer::new(sender.clone(), last_processed_commit_index), + commit_consumer, dag_state.clone(), mem_store.clone(), leader_schedule.clone(), @@ -499,7 +503,7 @@ mod tests { // Check commits sent over consensus output channel is accurate let mut processed_subdag_index = 0; - while let Ok(subdag) = receiver.try_recv() { + while let Ok(subdag) = commit_receiver.try_recv() { tracing::info!("Processed {subdag}"); assert_eq!(subdag, commits[processed_subdag_index]); assert_eq!(subdag.reputation_scores_desc, vec![]); @@ -510,7 +514,7 @@ mod tests { } assert_eq!(processed_subdag_index, expected_last_processed_index); - verify_channel_empty(&mut receiver); + verify_channel_empty(&mut commit_receiver); // Check last stored commit is correct let last_commit = mem_store.read_last_commit().unwrap().unwrap(); @@ -521,9 +525,11 @@ mod tests { // Re-create commit observer starting from index 3 which represents the // last processed index from the consumer over consensus output channel + let (commit_consumer, mut commit_receiver, _transaction_receiver) = + CommitConsumer::new(expected_last_processed_index as CommitIndex); let _observer = CommitObserver::new( context.clone(), - CommitConsumer::new(sender, expected_last_processed_index as CommitIndex), + commit_consumer, dag_state.clone(), mem_store.clone(), leader_schedule, @@ -531,7 +537,7 @@ mod tests { // No commits should be resubmitted as consensus store's last commit index // is equal to last processed index by consumer - verify_channel_empty(&mut receiver); + verify_channel_empty(&mut commit_receiver); } /// After receiving all expected subdags, ensure channel is empty diff --git a/consensus/core/src/commit_syncer.rs b/consensus/core/src/commit_syncer.rs index c106d9445bec3..44a38d595d148 100644 --- a/consensus/core/src/commit_syncer.rs +++ b/consensus/core/src/commit_syncer.rs @@ -35,12 +35,12 @@ use consensus_config::AuthorityIndex; use futures::{stream::FuturesOrdered, StreamExt as _}; use itertools::Itertools as _; use mysten_metrics::spawn_logged_monitored_task; -use parking_lot::{Mutex, RwLock}; -use rand::prelude::SliceRandom as _; +use parking_lot::RwLock; +use rand::{prelude::SliceRandom as _, rngs::ThreadRng}; use tokio::{ sync::oneshot, task::{JoinHandle, JoinSet}, - time::{sleep, Instant, MissedTickBehavior}, + time::{sleep, MissedTickBehavior}, }; use tracing::{debug, info, warn}; @@ -81,8 +81,6 @@ pub(crate) struct CommitSyncer { // Shared components wrapper. inner: Arc>, - // State of peers shared by fetch tasks, to determine the next peer to fetch against. - peer_state: Arc>, // States only used by the scheduler. @@ -113,7 +111,6 @@ impl CommitSyncer { block_verifier: Arc, dag_state: Arc>, ) -> Self { - let peer_state = Arc::new(Mutex::new(PeerState::new(&context))); let inner = Arc::new(Inner { context, core_thread_dispatcher, @@ -126,7 +123,6 @@ impl CommitSyncer { let synced_commit_index = inner.dag_state.read().last_commit_index(); CommitSyncer { inner, - peer_state, inflight_fetches: JoinSet::new(), pending_fetches: BTreeSet::new(), fetched_ranges: BTreeMap::new(), @@ -295,7 +291,8 @@ impl CommitSyncer { } debug!( - "Fetched certified blocks: {}", + "Fetched certified blocks for commit range {:?}: {}", + fetched_commit_range, blocks.iter().map(|b| b.reference().to_string()).join(","), ); // If core thread cannot handle the incoming blocks, it is ok to block here. @@ -305,7 +302,10 @@ impl CommitSyncer { match self.inner.core_thread_dispatcher.add_blocks(blocks).await { Ok(missing) => { if !missing.is_empty() { - warn!("Fetched blocks have missing ancestors: {:?}", missing); + warn!( + "Fetched blocks have missing ancestors: {:?} for commit range {:?}", + missing, fetched_commit_range + ); } } Err(e) => { @@ -354,11 +354,8 @@ impl CommitSyncer { let Some(commit_range) = self.pending_fetches.pop_first() else { break; }; - self.inflight_fetches.spawn(Self::fetch_loop( - self.inner.clone(), - self.peer_state.clone(), - commit_range, - )); + self.inflight_fetches + .spawn(Self::fetch_loop(self.inner.clone(), commit_range)); } let metrics = &self.inner.context.metrics.node_metrics; @@ -378,9 +375,17 @@ impl CommitSyncer { // Returns the fetched commits and blocks referenced by the commits. async fn fetch_loop( inner: Arc>, - peer_state: Arc>, commit_range: CommitRange, ) -> (CommitIndex, Vec, Vec) { + // Individual request base timeout. + const TIMEOUT: Duration = Duration::from_secs(10); + // Max per-request timeout will be base timeout times a multiplier. + // At the extreme, this means there will be 120s timeout to fetch max_blocks_per_fetch blocks. + const MAX_TIMEOUT_MULTIPLIER: u32 = 12; + // timeout * max number of targets should be reasonably small, so the + // system can adjust to slow network or large data sizes quickly. + const MAX_NUM_TARGETS: usize = 24; + let mut timeout_multiplier = 0; let _timer = inner .context .metrics @@ -389,23 +394,83 @@ impl CommitSyncer { .start_timer(); info!("Starting to fetch commits in {commit_range:?} ...",); loop { - match Self::fetch_once(inner.clone(), peer_state.clone(), commit_range.clone()).await { - Ok((commits, blocks)) => { - info!("Finished fetching commits in {commit_range:?}",); - return (commit_range.end(), commits, blocks); - } - Err(e) => { - warn!("Failed to fetch: {}", e); - let error: &'static str = e.into(); - inner - .context - .metrics - .node_metrics - .commit_sync_fetch_once_errors - .with_label_values(&[error]) - .inc(); + // Attempt to fetch commits and blocks through min(committee size, MAX_NUM_TARGETS) peers. + let mut target_authorities = inner + .context + .committee + .authorities() + .filter_map(|(i, _)| { + if i != inner.context.own_index { + Some(i) + } else { + None + } + }) + .collect_vec(); + target_authorities.shuffle(&mut ThreadRng::default()); + target_authorities.truncate(MAX_NUM_TARGETS); + // Increase timeout multiplier for each loop until MAX_TIMEOUT_MULTIPLIER. + timeout_multiplier = (timeout_multiplier + 1).min(MAX_TIMEOUT_MULTIPLIER); + let request_timeout = TIMEOUT * timeout_multiplier; + // Give enough overall timeout for fetching commits and blocks. + // - Timeout for fetching commits and commit certifying blocks. + // - Timeout for fetching blocks referenced by the commits. + // - Time spent on pipelining requests to fetch blocks. + // - Another headroom to allow fetch_once() to timeout gracefully if possible. + let fetch_timeout = request_timeout * 4; + // Try fetching from selected target authority. + for authority in target_authorities { + match tokio::time::timeout( + fetch_timeout, + Self::fetch_once( + inner.clone(), + authority, + commit_range.clone(), + request_timeout, + ), + ) + .await + { + Ok(Ok((commits, blocks))) => { + info!("Finished fetching commits in {commit_range:?}",); + return (commit_range.end(), commits, blocks); + } + Ok(Err(e)) => { + let hostname = inner + .context + .committee + .authority(authority) + .hostname + .clone(); + warn!("Failed to fetch {commit_range:?} from {hostname}: {}", e); + inner + .context + .metrics + .node_metrics + .commit_sync_fetch_once_errors + .with_label_values(&[&hostname, e.name()]) + .inc(); + } + Err(_) => { + let hostname = inner + .context + .committee + .authority(authority) + .hostname + .clone(); + warn!("Timed out fetching {commit_range:?} from {authority}",); + inner + .context + .metrics + .node_metrics + .commit_sync_fetch_once_errors + .with_label_values(&[&hostname, "FetchTimeout"]) + .inc(); + } } } + // Avoid busy looping, by waiting for a while before retrying. + sleep(TIMEOUT).await; } } @@ -414,15 +479,10 @@ impl CommitSyncer { // and sent to Core for processing. async fn fetch_once( inner: Arc>, - peer_state: Arc>, + target_authority: AuthorityIndex, commit_range: CommitRange, + timeout: Duration, ) -> ConsensusResult<(Vec, Vec)> { - const FETCH_COMMITS_TIMEOUT: Duration = Duration::from_secs(30); - const FETCH_BLOCKS_TIMEOUT: Duration = Duration::from_secs(120); - const FETCH_RETRY_BASE_INTERVAL: Duration = Duration::from_secs(1); - const FETCH_RETRY_INTERVAL_LIMIT: u32 = 30; - const MAX_RETRY_INTERVAL: Duration = Duration::from_secs(1); - let _timer = inner .context .metrics @@ -430,50 +490,13 @@ impl CommitSyncer { .commit_sync_fetch_once_latency .start_timer(); - // 1. Find an available authority to fetch commits and blocks from, and wait - // if it is not yet ready. - let Some((available_time, retries, target_authority)) = - peer_state.lock().available_authorities.pop_first() - else { - sleep(MAX_RETRY_INTERVAL).await; - return Err(ConsensusError::NoAvailableAuthorityToFetchCommits); - }; - let now = Instant::now(); - if now < available_time { - sleep(available_time - now).await; - } - - // 2. Fetch commits in the commit range from the selected authority. - let (serialized_commits, serialized_blocks) = match inner + // 1. Fetch commits in the commit range from the target authority. + let (serialized_commits, serialized_blocks) = inner .network_client - .fetch_commits( - target_authority, - commit_range.clone(), - FETCH_COMMITS_TIMEOUT, - ) - .await - { - Ok(result) => { - let mut peer_state = peer_state.lock(); - let now = Instant::now(); - peer_state - .available_authorities - .insert((now, 0, target_authority)); - result - } - Err(e) => { - let mut peer_state = peer_state.lock(); - let now = Instant::now(); - peer_state.available_authorities.insert(( - now + FETCH_RETRY_BASE_INTERVAL * retries.min(FETCH_RETRY_INTERVAL_LIMIT), - retries.saturating_add(1), - target_authority, - )); - return Err(e); - } - }; + .fetch_commits(target_authority, commit_range.clone(), timeout) + .await?; - // 3. Verify the response contains blocks that can certify the last returned commit, + // 2. Verify the response contains blocks that can certify the last returned commit, // and the returned commits are chained by digest, so earlier commits are certified // as well. let commits = inner.verify_commits( @@ -483,17 +506,20 @@ impl CommitSyncer { serialized_blocks, )?; - // 4. Fetch blocks referenced by the commits, from the same authority. + // 3. Fetch blocks referenced by the commits, from the same authority. let block_refs: Vec<_> = commits.iter().flat_map(|c| c.blocks()).cloned().collect(); + let num_chunks = block_refs + .len() + .div_ceil(inner.context.parameters.max_blocks_per_fetch) + as u32; let mut requests: FuturesOrdered<_> = block_refs .chunks(inner.context.parameters.max_blocks_per_fetch) .enumerate() .map(|(i, request_block_refs)| { - let i = i as u32; let inner = inner.clone(); async move { - // Pipeline the requests to avoid overloading the target. - sleep(Duration::from_millis(200) * i).await; + // 4. Send out pipelined fetch requests to avoid overloading the target authority. + sleep(timeout * i as u32 / num_chunks).await; // TODO: add some retries. let serialized_blocks = inner .network_client @@ -501,7 +527,7 @@ impl CommitSyncer { target_authority, request_block_refs.to_vec(), vec![], - FETCH_BLOCKS_TIMEOUT, + timeout, ) .await?; // 5. Verify the same number of blocks are returned as requested. @@ -703,40 +729,6 @@ impl Inner { } } -struct PeerState { - // The value is a tuple of - // - the next available time for the authority to fetch from, - // - count of current consecutive failures fetching from the authority, reset on success, - // - authority index. - // TODO: move this to a separate module, add load balancing, add throttling, and consider - // health of peer via previous request failures and leader scores. - available_authorities: BTreeSet<(Instant, u32, AuthorityIndex)>, -} - -impl PeerState { - fn new(context: &Context) -> Self { - // Randomize the initial order of authorities. - let mut shuffled_authority_indices: Vec<_> = context - .committee - .authorities() - .filter_map(|(index, _)| { - if index != context.own_index { - Some(index) - } else { - None - } - }) - .collect(); - shuffled_authority_indices.shuffle(&mut rand::thread_rng()); - Self { - available_authorities: shuffled_authority_indices - .into_iter() - .map(|i| (Instant::now(), 0, i)) - .collect(), - } - } -} - #[cfg(test)] mod tests { use std::{sync::Arc, time::Duration}; diff --git a/consensus/core/src/core.rs b/consensus/core/src/core.rs index 39b6f7b672121..ab9569243cef8 100644 --- a/consensus/core/src/core.rs +++ b/consensus/core/src/core.rs @@ -8,7 +8,7 @@ use consensus_config::{local_committee_and_keys, Stake}; use consensus_config::{AuthorityIndex, ProtocolKeyPair}; use itertools::Itertools as _; #[cfg(test)] -use mysten_metrics::monitored_mpsc::{unbounded_channel, UnboundedReceiver}; +use mysten_metrics::monitored_mpsc::UnboundedReceiver; use mysten_metrics::monitored_scope; use parking_lot::RwLock; use sui_macros::fail_point; @@ -30,6 +30,7 @@ use crate::{ dag_state::DagState, error::{ConsensusError, ConsensusResult}, leader_schedule::LeaderSchedule, + round_prober::QuorumRound, stake_aggregator::{QuorumThreshold, StakeAggregator}, threshold_clock::ThresholdClock, transaction::TransactionConsumer, @@ -505,132 +506,106 @@ impl Core { .with_label_values(&["Core::try_commit"]) .start_timer(); - if !self - .context - .protocol_config - .mysticeti_leader_scoring_and_schedule() - { - let decided_leaders = self.committer.try_decide(self.last_decided_leader); - if let Some(last) = decided_leaders.last() { - self.last_decided_leader = last.slot(); - self.context - .metrics - .node_metrics - .last_decided_leader_round - .set(self.last_decided_leader.round as i64); - } - - let committed_leaders = decided_leaders - .into_iter() - .filter_map(|leader| leader.into_committed_block()) - .collect::>(); - if !committed_leaders.is_empty() { - debug!( - "Committing leaders: {}", - committed_leaders - .iter() - .map(|b| b.reference().to_string()) - .join(",") + let mut committed_subdags = Vec::new(); + // TODO: Add optimization to abort early without quorum for a round. + loop { + // LeaderSchedule has a limit to how many sequenced leaders can be committed + // before a change is triggered. Calling into leader schedule will get you + // how many commits till next leader change. We will loop back and recalculate + // any discarded leaders with the new schedule. + let mut commits_until_update = self + .leader_schedule + .commits_until_leader_schedule_update(self.dag_state.clone()); + if commits_until_update == 0 { + let last_commit_index = self.dag_state.read().last_commit_index(); + tracing::info!( + "Leader schedule change triggered at commit index {last_commit_index}" ); - } - self.commit_observer.handle_commit(committed_leaders) - } else { - let mut committed_subdags = Vec::new(); - // TODO: Add optimization to abort early without quorum for a round. - loop { - // LeaderSchedule has a limit to how many sequenced leaders can be committed - // before a change is triggered. Calling into leader schedule will get you - // how many commits till next leader change. We will loop back and recalculate - // any discarded leaders with the new schedule. - let mut commits_until_update = self + if self + .context + .protocol_config + .consensus_distributed_vote_scoring_strategy() + { + self.leader_schedule + .update_leader_schedule_v2(&self.dag_state); + } else { + self.leader_schedule + .update_leader_schedule_v1(&self.dag_state); + } + commits_until_update = self .leader_schedule .commits_until_leader_schedule_update(self.dag_state.clone()); - if commits_until_update == 0 { - let last_commit_index = self.dag_state.read().last_commit_index(); - tracing::info!( - "Leader schedule change triggered at commit index {last_commit_index}" - ); - if self - .context - .protocol_config - .consensus_distributed_vote_scoring_strategy() - { - self.leader_schedule - .update_leader_schedule_v2(&self.dag_state); - } else { - self.leader_schedule - .update_leader_schedule_v1(&self.dag_state); - } - commits_until_update = self - .leader_schedule - .commits_until_leader_schedule_update(self.dag_state.clone()); - fail_point!("consensus-after-leader-schedule-change"); - } - assert!(commits_until_update > 0); + fail_point!("consensus-after-leader-schedule-change"); + } + assert!(commits_until_update > 0); - // TODO: limit commits by commits_until_update, which may be needed when leader schedule length - // is reduced. - let decided_leaders = self.committer.try_decide(self.last_decided_leader); + // TODO: limit commits by commits_until_update, which may be needed when leader schedule length + // is reduced. + let decided_leaders = self.committer.try_decide(self.last_decided_leader); - let Some(last_decided) = decided_leaders.last().cloned() else { - break; - }; - tracing::info!("Decided {} leaders and {commits_until_update} commits can be made before next leader schedule change", decided_leaders.len()); + let Some(last_decided) = decided_leaders.last().cloned() else { + break; + }; + tracing::debug!("Decided {} leaders and {commits_until_update} commits can be made before next leader schedule change", decided_leaders.len()); - let mut sequenced_leaders = decided_leaders - .into_iter() - .filter_map(|leader| leader.into_committed_block()) - .collect::>(); - - // If the sequenced leaders are truncated to fit the leader schedule, use the last sequenced leader - // as the last decided leader. Otherwise, use the last decided leader from try_commit(). - let sequenced_leaders = if sequenced_leaders.len() >= commits_until_update { - let _ = sequenced_leaders.split_off(commits_until_update); - self.last_decided_leader = sequenced_leaders.last().unwrap().slot(); - sequenced_leaders - } else { - self.last_decided_leader = last_decided.slot(); - sequenced_leaders - }; + let mut sequenced_leaders = decided_leaders + .into_iter() + .filter_map(|leader| leader.into_committed_block()) + .collect::>(); - self.context - .metrics - .node_metrics - .last_decided_leader_round - .set(self.last_decided_leader.round as i64); + // If the sequenced leaders are truncated to fit the leader schedule, use the last sequenced leader + // as the last decided leader. Otherwise, use the last decided leader from try_commit(). + let sequenced_leaders = if sequenced_leaders.len() >= commits_until_update { + let _ = sequenced_leaders.split_off(commits_until_update); + self.last_decided_leader = sequenced_leaders.last().unwrap().slot(); + sequenced_leaders + } else { + self.last_decided_leader = last_decided.slot(); + sequenced_leaders + }; - if sequenced_leaders.is_empty() { - break; - } - tracing::info!( - "Committing {} leaders: {}", - sequenced_leaders.len(), - sequenced_leaders - .iter() - .map(|b| b.reference().to_string()) - .join(",") - ); + self.context + .metrics + .node_metrics + .last_decided_leader_round + .set(self.last_decided_leader.round as i64); - // TODO: refcount subdags - let subdags = self.commit_observer.handle_commit(sequenced_leaders)?; - if self - .context - .protocol_config - .consensus_distributed_vote_scoring_strategy() - { - self.dag_state.write().add_scoring_subdags(subdags.clone()); - } else { - // TODO: Remove when DistributedVoteScoring is enabled. - self.dag_state - .write() - .add_unscored_committed_subdags(subdags.clone()); - } - committed_subdags.extend(subdags); + if sequenced_leaders.is_empty() { + break; + } + tracing::info!( + "Committing {} leaders: {}", + sequenced_leaders.len(), + sequenced_leaders + .iter() + .map(|b| b.reference().to_string()) + .join(",") + ); + + // TODO: refcount subdags + let subdags = self.commit_observer.handle_commit(sequenced_leaders)?; + if self + .context + .protocol_config + .consensus_distributed_vote_scoring_strategy() + { + self.dag_state.write().add_scoring_subdags(subdags.clone()); + } else { + // TODO: Remove when DistributedVoteScoring is enabled. + self.dag_state + .write() + .add_unscored_committed_subdags(subdags.clone()); } - Ok(committed_subdags) + // Try to unsuspend blocks if gc_round has advanced. + self.block_manager + .try_unsuspend_blocks_for_latest_gc_round(); + + committed_subdags.extend(subdags); } + + Ok(committed_subdags) } pub(crate) fn get_missing_blocks(&self) -> BTreeSet { @@ -645,7 +620,12 @@ impl Core { } /// Sets the delay by round for propagating blocks to a quorum. - pub(crate) fn set_propagation_delay(&mut self, delay: Round) { + // TODO: Will set the quorum round per authority in ancestor state manager. + pub(crate) fn set_propagation_delay_and_quorum_rounds( + &mut self, + delay: Round, + _quorum_rounds: Vec, + ) { info!("Propagation round delay set to: {delay}"); self.propagation_delay = delay; } @@ -714,10 +694,15 @@ impl Core { /// Retrieves the next ancestors to propose to form a block at `clock_round` round. fn ancestors_to_propose(&mut self, clock_round: Round) -> Vec { // Now take the ancestors before the clock_round (excluded) for each authority. - let ancestors = self - .dag_state - .read() - .get_last_cached_block_per_authority(clock_round); + let (ancestors, gc_enabled, gc_round) = { + let dag_state = self.dag_state.read(); + ( + dag_state.get_last_cached_block_per_authority(clock_round), + dag_state.gc_enabled(), + dag_state.gc_round(), + ) + }; + assert_eq!( ancestors.len(), self.context.committee.size(), @@ -731,6 +716,12 @@ impl Core { ancestors .into_iter() .filter(|block| block.author() != self.context.own_index) + .filter(|block| { + if gc_enabled && gc_round > GENESIS_ROUND { + return block.round() > gc_round; + } + true + }) .flat_map(|block| { if let Some(last_block_ref) = self.last_included_ancestors[block.author()] { return (last_block_ref.round < block.round()).then_some(block); @@ -934,10 +925,10 @@ impl CoreTextFixture { // Need at least one subscriber to the block broadcast channel. let block_receiver = signal_receivers.block_broadcast_receiver(); - let (commit_sender, commit_receiver) = unbounded_channel("consensus_output"); + let (commit_consumer, commit_receiver, _transaction_receiver) = CommitConsumer::new(0); let commit_observer = CommitObserver::new( context.clone(), - CommitConsumer::new(commit_sender.clone(), 0), + commit_consumer, dag_state.clone(), store.clone(), leader_schedule.clone(), @@ -973,7 +964,6 @@ mod test { use std::{collections::BTreeSet, time::Duration}; use consensus_config::{AuthorityIndex, Parameters}; - use mysten_metrics::monitored_mpsc::unbounded_channel; use sui_protocol_config::ProtocolConfig; use tokio::time::sleep; @@ -981,7 +971,7 @@ mod test { use crate::{ block::{genesis_blocks, TestBlock}, block_verifier::NoopBlockVerifier, - commit::{CommitAPI as _, CommitRange}, + commit::CommitAPI as _, leader_scoring::ReputationScores, storage::{mem_store::MemStore, Store, WriteBatch}, test_dag_builder::DagBuilder, @@ -1033,10 +1023,10 @@ mod test { dag_state.clone(), )); - let (sender, _receiver) = unbounded_channel("consensus_output"); + let (commit_consumer, _commit_receiver, _transaction_receiver) = CommitConsumer::new(0); let commit_observer = CommitObserver::new( context.clone(), - CommitConsumer::new(sender.clone(), 0), + commit_consumer, dag_state.clone(), store.clone(), leader_schedule.clone(), @@ -1151,10 +1141,10 @@ mod test { dag_state.clone(), )); - let (sender, _receiver) = unbounded_channel("consensus_output"); + let (commit_consumer, _commit_receiver, _transaction_receiver) = CommitConsumer::new(0); let commit_observer = CommitObserver::new( context.clone(), - CommitConsumer::new(sender.clone(), 0), + commit_consumer, dag_state.clone(), store.clone(), leader_schedule.clone(), @@ -1248,10 +1238,10 @@ mod test { dag_state.clone(), )); - let (sender, _receiver) = unbounded_channel("consensus_output"); + let (commit_consumer, _commit_receiver, _transaction_receiver) = CommitConsumer::new(0); let commit_observer = CommitObserver::new( context.clone(), - CommitConsumer::new(sender.clone(), 0), + commit_consumer, dag_state.clone(), store.clone(), leader_schedule.clone(), @@ -1353,10 +1343,10 @@ mod test { // Need at least one subscriber to the block broadcast channel. let _block_receiver = signal_receivers.block_broadcast_receiver(); - let (sender, _receiver) = unbounded_channel("consensus_output"); + let (commit_consumer, _commit_receiver, _transaction_receiver) = CommitConsumer::new(0); let commit_observer = CommitObserver::new( context.clone(), - CommitConsumer::new(sender.clone(), 0), + commit_consumer, dag_state.clone(), store.clone(), leader_schedule.clone(), @@ -1442,10 +1432,10 @@ mod test { // Need at least one subscriber to the block broadcast channel. let _block_receiver = signal_receivers.block_broadcast_receiver(); - let (sender, _receiver) = unbounded_channel("consensus_output"); + let (commit_consumer, _commit_receiver, _transaction_receiver) = CommitConsumer::new(0); let commit_observer = CommitObserver::new( context.clone(), - CommitConsumer::new(sender.clone(), 0), + commit_consumer, dag_state.clone(), store.clone(), leader_schedule.clone(), @@ -1630,10 +1620,10 @@ mod test { // Need at least one subscriber to the block broadcast channel. let _block_receiver = signal_receivers.block_broadcast_receiver(); - let (sender, _receiver) = unbounded_channel("consensus_output"); + let (commit_consumer, _commit_receiver, _transaction_receiver) = CommitConsumer::new(0); let commit_observer = CommitObserver::new( context.clone(), - CommitConsumer::new(sender.clone(), 0), + commit_consumer, dag_state.clone(), store.clone(), leader_schedule.clone(), @@ -1695,10 +1685,10 @@ mod test { // Need at least one subscriber to the block broadcast channel. let _block_receiver = signal_receivers.block_broadcast_receiver(); - let (sender, _receiver) = unbounded_channel("consensus_output"); + let (commit_consumer, _commit_receiver, _transaction_receiver) = CommitConsumer::new(0); let commit_observer = CommitObserver::new( context.clone(), - CommitConsumer::new(sender.clone(), 0), + commit_consumer, dag_state.clone(), store.clone(), leader_schedule.clone(), @@ -1726,7 +1716,7 @@ mod test { ); // Use a large propagation delay to disable proposing. - core.set_propagation_delay(1000); + core.set_propagation_delay_and_quorum_rounds(1000, vec![]); // Make propagation delay the only reason for not proposing. core.set_subscriber_exists(true); @@ -1735,7 +1725,7 @@ mod test { assert!(core.try_propose(true).unwrap().is_none()); // Let Core know there is no propagation delay. - core.set_propagation_delay(0); + core.set_propagation_delay_and_quorum_rounds(0, vec![]); // Proposing now would succeed. assert!(core.try_propose(true).unwrap().is_some()); @@ -1986,127 +1976,6 @@ mod test { } } - #[tokio::test(flavor = "current_thread", start_paused = true)] - async fn test_no_leader_schedule_change() { - telemetry_subscribers::init_for_testing(); - let default_params = Parameters::default(); - - let (mut context, _) = Context::new_for_test(4); - context - .protocol_config - .set_mysticeti_leader_scoring_and_schedule_for_testing(false); - // create the cores and their signals for all the authorities - let mut cores = create_cores(context, vec![1, 1, 1, 1]); - - // Now iterate over a few rounds and ensure the corresponding signals are created while network advances - let mut last_round_blocks = Vec::new(); - for round in 1..=30 { - let mut this_round_blocks = Vec::new(); - - for core_fixture in &mut cores { - // Wait for min round delay to allow blocks to be proposed. - sleep(default_params.min_round_delay).await; - // add the blocks from last round - // this will trigger a block creation for the round and a signal should be emitted - core_fixture - .core - .add_blocks(last_round_blocks.clone()) - .unwrap(); - - // A "new round" signal should be received given that all the blocks of previous round have been processed - let new_round = receive( - Duration::from_secs(1), - core_fixture.signal_receivers.new_round_receiver(), - ) - .await; - assert_eq!(new_round, round); - - // Check that a new block has been proposed. - let block = tokio::time::timeout( - Duration::from_secs(1), - core_fixture.block_receiver.recv(), - ) - .await - .unwrap() - .unwrap(); - assert_eq!(block.round(), round); - assert_eq!(block.author(), core_fixture.core.context.own_index); - - // append the new block to this round blocks - this_round_blocks.push(core_fixture.core.last_proposed_block().clone()); - - let block = core_fixture.core.last_proposed_block(); - - // ensure that produced block is referring to the blocks of last_round - assert_eq!( - block.ancestors().len(), - core_fixture.core.context.committee.size() - ); - for ancestor in block.ancestors() { - if block.round() > 1 { - // don't bother with round 1 block which just contains the genesis blocks. - assert!( - last_round_blocks - .iter() - .any(|block| block.reference() == *ancestor), - "Reference from previous round should be added" - ); - } - } - } - - last_round_blocks = this_round_blocks; - } - - for core_fixture in cores { - // Check commits have been persisted to store - let last_commit = core_fixture - .store - .read_last_commit() - .unwrap() - .expect("last commit should be set"); - // There are 28 leader rounds with rounds completed up to and including - // round 29. Round 30 blocks will only include their own blocks, so the - // 28th leader will not be committed. - assert_eq!(last_commit.index(), 27); - let all_stored_commits = core_fixture - .store - .scan_commits((0..=CommitIndex::MAX).into()) - .unwrap(); - assert_eq!(all_stored_commits.len(), 27); - assert_eq!( - core_fixture - .core - .leader_schedule - .leader_swap_table - .read() - .bad_nodes - .len(), - 0 - ); - assert_eq!( - core_fixture - .core - .leader_schedule - .leader_swap_table - .read() - .good_nodes - .len(), - 0 - ); - let expected_reputation_scores = ReputationScores::new(CommitRange::default(), vec![]); - assert_eq!( - core_fixture - .core - .leader_schedule - .leader_swap_table - .read() - .reputation_scores, - expected_reputation_scores - ); - } - } - #[tokio::test(flavor = "current_thread", start_paused = true)] async fn test_commit_on_leader_schedule_change_boundary_without_multileader() { parameterized_test_commit_on_leader_schedule_change_boundary(Some(1)).await; diff --git a/consensus/core/src/core_thread.rs b/consensus/core/src/core_thread.rs index 1e9ec7e925c7b..5b2a83748672e 100644 --- a/consensus/core/src/core_thread.rs +++ b/consensus/core/src/core_thread.rs @@ -15,7 +15,7 @@ use mysten_metrics::{ monitored_mpsc::{channel, Receiver, Sender, WeakSender}, monitored_scope, spawn_logged_monitored_task, }; -use parking_lot::{Mutex, RwLock}; +use parking_lot::RwLock; use thiserror::Error; use tokio::sync::{oneshot, watch}; use tracing::warn; @@ -27,6 +27,7 @@ use crate::{ core_thread::CoreError::Shutdown, dag_state::DagState, error::{ConsensusError, ConsensusResult}, + round_prober::QuorumRound, BlockAPI as _, }; @@ -65,8 +66,13 @@ pub trait CoreThreadDispatcher: Sync + Send + 'static { /// It is not a guarantee that produced blocks will be accepted by peers. fn set_subscriber_exists(&self, exists: bool) -> Result<(), CoreError>; - /// Sets the estimated delay to propagate a block to a quorum of peers, in number of rounds. - fn set_propagation_delay(&self, delay: Round) -> Result<(), CoreError>; + /// Sets the estimated delay to propagate a block to a quorum of peers, in + /// number of rounds, and the quorum rounds for all authorities. + fn set_propagation_delay_and_quorum_rounds( + &self, + delay: Round, + quorum_rounds: Vec, + ) -> Result<(), CoreError>; fn set_last_known_proposed_round(&self, round: Round) -> Result<(), CoreError>; @@ -91,7 +97,7 @@ struct CoreThread { core: Core, receiver: Receiver, rx_subscriber_exists: watch::Receiver, - rx_propagation_delay: watch::Receiver, + rx_propagation_delay_and_quorum_rounds: watch::Receiver<(Round, Vec)>, rx_last_known_proposed_round: watch::Receiver, context: Arc, } @@ -141,11 +147,11 @@ impl CoreThread { self.core.new_block(Round::MAX, true)?; } } - _ = self.rx_propagation_delay.changed() => { - let _scope = monitored_scope("CoreThread::loop::set_propagation_delay"); + _ = self.rx_propagation_delay_and_quorum_rounds.changed() => { + let _scope = monitored_scope("CoreThread::loop::set_propagation_delay_and_quorum_rounds"); let should_propose_before = self.core.should_propose(); - let delay = *self.rx_propagation_delay.borrow(); - self.core.set_propagation_delay(delay); + let (delay, quorum_rounds) = self.rx_propagation_delay_and_quorum_rounds.borrow().clone(); + self.core.set_propagation_delay_and_quorum_rounds(delay, quorum_rounds); if !should_propose_before && self.core.should_propose() { // If core cannnot propose before but can propose now, try to produce a new block to ensure liveness, // because block proposal could have been skipped. @@ -164,7 +170,7 @@ pub(crate) struct ChannelCoreThreadDispatcher { context: Arc, sender: WeakSender, tx_subscriber_exists: Arc>, - tx_propagation_delay: Arc>, + tx_propagation_delay_and_quorum_rounds: Arc)>>, tx_last_known_proposed_round: Arc>, highest_received_rounds: Arc>, } @@ -190,16 +196,17 @@ impl ChannelCoreThreadDispatcher { let (sender, receiver) = channel("consensus_core_commands", CORE_THREAD_COMMANDS_CHANNEL_SIZE); let (tx_subscriber_exists, mut rx_subscriber_exists) = watch::channel(false); - let (tx_propagation_delay, mut rx_propagation_delay) = watch::channel(0); + let (tx_propagation_delay_and_quorum_rounds, mut rx_propagation_delay_and_quorum_rounds) = + watch::channel((0, vec![(0, 0); context.committee.size()])); let (tx_last_known_proposed_round, mut rx_last_known_proposed_round) = watch::channel(0); rx_subscriber_exists.mark_unchanged(); - rx_propagation_delay.mark_unchanged(); + rx_propagation_delay_and_quorum_rounds.mark_unchanged(); rx_last_known_proposed_round.mark_unchanged(); let core_thread = CoreThread { core, receiver, rx_subscriber_exists, - rx_propagation_delay, + rx_propagation_delay_and_quorum_rounds, rx_last_known_proposed_round, context: context.clone(), }; @@ -221,7 +228,9 @@ impl ChannelCoreThreadDispatcher { context, sender: sender.downgrade(), tx_subscriber_exists: Arc::new(tx_subscriber_exists), - tx_propagation_delay: Arc::new(tx_propagation_delay), + tx_propagation_delay_and_quorum_rounds: Arc::new( + tx_propagation_delay_and_quorum_rounds, + ), tx_last_known_proposed_round: Arc::new(tx_last_known_proposed_round), highest_received_rounds: Arc::new(highest_received_rounds), }; @@ -279,9 +288,13 @@ impl CoreThreadDispatcher for ChannelCoreThreadDispatcher { .map_err(|e| Shutdown(e.to_string())) } - fn set_propagation_delay(&self, delay: Round) -> Result<(), CoreError> { - self.tx_propagation_delay - .send(delay) + fn set_propagation_delay_and_quorum_rounds( + &self, + delay: Round, + quorum_rounds: Vec, + ) -> Result<(), CoreError> { + self.tx_propagation_delay_and_quorum_rounds + .send((delay, quorum_rounds)) .map_err(|e| Shutdown(e.to_string())) } @@ -300,13 +313,15 @@ impl CoreThreadDispatcher for ChannelCoreThreadDispatcher { } // TODO: complete the Mock for thread dispatcher to be used from several tests +#[cfg(test)] #[derive(Default)] pub(crate) struct MockCoreThreadDispatcher { - add_blocks: Mutex>, - missing_blocks: Mutex>, - last_known_proposed_round: Mutex>, + add_blocks: parking_lot::Mutex>, + missing_blocks: parking_lot::Mutex>, + last_known_proposed_round: parking_lot::Mutex>, } +#[cfg(test)] impl MockCoreThreadDispatcher { #[cfg(test)] pub(crate) async fn get_add_blocks(&self) -> Vec { @@ -327,6 +342,7 @@ impl MockCoreThreadDispatcher { } } +#[cfg(test)] #[async_trait] impl CoreThreadDispatcher for MockCoreThreadDispatcher { async fn add_blocks( @@ -353,7 +369,11 @@ impl CoreThreadDispatcher for MockCoreThreadDispatcher { todo!() } - fn set_propagation_delay(&self, _delay: Round) -> Result<(), CoreError> { + fn set_propagation_delay_and_quorum_rounds( + &self, + _delay: Round, + _quorum_rounds: Vec, + ) -> Result<(), CoreError> { todo!() } @@ -370,7 +390,6 @@ impl CoreThreadDispatcher for MockCoreThreadDispatcher { #[cfg(test)] mod test { - use mysten_metrics::monitored_mpsc::unbounded_channel; use parking_lot::RwLock; use super::*; @@ -403,14 +422,14 @@ mod test { let transaction_consumer = TransactionConsumer::new(tx_receiver, context.clone()); let (signals, signal_receivers) = CoreSignals::new(context.clone()); let _block_receiver = signal_receivers.block_broadcast_receiver(); - let (sender, _receiver) = unbounded_channel("consensus_output"); + let (commit_consumer, _commit_receiver, _transaction_receiver) = CommitConsumer::new(0); let leader_schedule = Arc::new(LeaderSchedule::from_store( context.clone(), dag_state.clone(), )); let commit_observer = CommitObserver::new( context.clone(), - CommitConsumer::new(sender.clone(), 0), + commit_consumer, dag_state.clone(), store, leader_schedule.clone(), diff --git a/consensus/core/src/dag_state.rs b/consensus/core/src/dag_state.rs index cb1112633287e..83aada9cb05a7 100644 --- a/consensus/core/src/dag_state.rs +++ b/consensus/core/src/dag_state.rs @@ -7,11 +7,12 @@ use std::{ ops::Bound::{Excluded, Included, Unbounded}, panic, sync::Arc, + vec, }; use consensus_config::AuthorityIndex; use itertools::Itertools as _; -use tracing::{debug, error}; +use tracing::{debug, error, info}; use crate::{ block::{ @@ -44,11 +45,21 @@ pub(crate) struct DagState { // Contains recent blocks within CACHED_ROUNDS from the last committed round per authority. // Note: all uncommitted blocks are kept in memory. + // + // When GC is enabled, this map has a different semantic. It holds all the recent data for each authority making sure that it always have available + // CACHED_ROUNDS worth of data. The entries are evicted based on the latest GC round, however the eviction process will respect the CACHED_ROUNDS. + // For each authority, blocks are only evicted when their round is less than or equal to both `gc_round`, and `highest authority round - cached rounds`. + // This ensures that the GC requirements are respected (we never clean up any block above `gc_round`), and there are enough blocks cached. recent_blocks: BTreeMap, - // Contains block refs of recent_blocks. - // Each element in the Vec corresponds to the authority with the index. - recent_refs: Vec>, + // Indexes recent block refs by their authorities. + // Vec position corresponds to the authority index. + recent_refs_by_authority: Vec>, + + // Keeps track of the highest round that has been evicted for each authority. Any blocks that are of round <= evict_round + // should be considered evicted, and if any exist we should not consider the causauly complete in the order they appear. + // The `evicted_rounds` size should be the same as the committee size. + evicted_rounds: Vec, // Highest round of blocks accepted. highest_accepted_round: Round, @@ -132,17 +143,10 @@ impl DagState { last_committed_rounds[block_ref.author] = max(last_committed_rounds[block_ref.author], block_ref.round); } - if context - .protocol_config - .mysticeti_leader_scoring_and_schedule() - { - let committed_subdag = load_committed_subdag_from_store( - store.as_ref(), - commit.clone(), - vec![], - ); // We don't need to recover reputation scores for unscored_committed_subdags - unscored_committed_subdags.push(committed_subdag); - } + let committed_subdag = + load_committed_subdag_from_store(store.as_ref(), commit.clone(), vec![]); + // We don't need to recover reputation scores for unscored_committed_subdags + unscored_committed_subdags.push(committed_subdag); }); } @@ -163,7 +167,7 @@ impl DagState { context, genesis, recent_blocks: BTreeMap::new(), - recent_refs: vec![BTreeSet::new(); num_authorities], + recent_refs_by_authority: vec![BTreeSet::new(); num_authorities], highest_accepted_round: 0, last_commit, last_commit_round_advancement_time: None, @@ -176,20 +180,57 @@ impl DagState { unscored_committed_subdags, store, cached_rounds, + evicted_rounds: vec![0; num_authorities], }; for (i, round) in last_committed_rounds.into_iter().enumerate() { let authority_index = state.context.committee.to_authority_index(i).unwrap(); - let blocks = state - .store - .scan_blocks_by_author( - authority_index, - Self::eviction_round(round, cached_rounds) + 1, - ) - .unwrap(); - for block in blocks { - state.update_block_metadata(&block); + let (blocks, eviction_round) = if state.gc_enabled() { + // Find the latest block for the authority to calculate the eviction round. Then we want to scan and load the blocks from the eviction round and onwards only. + // As reminder, the eviction round is taking into account the gc_round. + let last_block = state + .store + .scan_last_blocks_by_author(authority_index, 1, None) + .expect("Database error"); + let last_block_round = last_block + .last() + .map(|b| b.round()) + .unwrap_or(GENESIS_ROUND); + + let eviction_round = Self::gc_eviction_round( + last_block_round, + state.gc_round(), + state.cached_rounds, + ); + let blocks = state + .store + .scan_blocks_by_author(authority_index, eviction_round + 1) + .expect("Database error"); + (blocks, eviction_round) + } else { + let eviction_round = Self::eviction_round(round, cached_rounds); + let blocks = state + .store + .scan_blocks_by_author(authority_index, eviction_round + 1) + .expect("Database error"); + (blocks, eviction_round) + }; + + state.evicted_rounds[authority_index] = eviction_round; + + // Update the block metadata for the authority. + for block in &blocks { + state.update_block_metadata(block); } + + info!( + "Recovered blocks {}: {:?}", + authority_index, + blocks + .iter() + .map(|b| b.reference()) + .collect::>() + ); } state @@ -245,7 +286,7 @@ impl DagState { fn update_block_metadata(&mut self, block: &VerifiedBlock) { let block_ref = block.reference(); self.recent_blocks.insert(block_ref, block.clone()); - self.recent_refs[block_ref.author].insert(block_ref); + self.recent_refs_by_authority[block_ref.author].insert(block_ref); self.highest_accepted_round = max(self.highest_accepted_round, block.round()); self.context .metrics @@ -253,7 +294,7 @@ impl DagState { .highest_accepted_round .set(self.highest_accepted_round as i64); - let highest_accepted_round_for_author = self.recent_refs[block_ref.author] + let highest_accepted_round_for_author = self.recent_refs_by_authority[block_ref.author] .last() .map(|block_ref| block_ref.round) .expect("There should be by now at least one block ref"); @@ -414,7 +455,7 @@ impl DagState { /// Retrieves the last block proposed for the specified `authority`. If no block is found in cache /// then the genesis block is returned as no other block has been received from that authority. pub(crate) fn get_last_block_for_authority(&self, authority: AuthorityIndex) -> VerifiedBlock { - if let Some(last) = self.recent_refs[authority].last() { + if let Some(last) = self.recent_refs_by_authority[authority].last() { return self .recent_blocks .get(last) @@ -442,7 +483,7 @@ impl DagState { start: Round, ) -> Vec { let mut blocks = vec![]; - for block_ref in self.recent_refs[authority].range(( + for block_ref in self.recent_refs_by_authority[authority].range(( Included(BlockRef::new(start, authority, BlockDigest::MIN)), Unbounded, )) { @@ -477,14 +518,14 @@ impl DagState { return blocks; } - for (authority_index, block_refs) in self.recent_refs.iter().enumerate() { + for (authority_index, block_refs) in self.recent_refs_by_authority.iter().enumerate() { let authority_index = self .context .committee .to_authority_index(authority_index) .unwrap(); - let last_evicted_round = self.authority_eviction_round(authority_index); + let last_evicted_round = self.evicted_rounds[authority_index]; if end_round.saturating_sub(1) <= last_evicted_round { panic!("Attempted to request for blocks of rounds < {end_round}, when the last evicted round is {last_evicted_round} for authority {authority_index}", ); } @@ -520,14 +561,12 @@ impl DagState { return true; } - if slot.round <= self.authority_eviction_round(slot.authority) { - panic!( - "Attempted to check for slot {slot} that is <= the last evicted round {}", - self.authority_eviction_round(slot.authority) - ); + let eviction_round = self.evicted_rounds[slot.authority]; + if slot.round <= eviction_round { + panic!("{}", format!("Attempted to check for slot {slot} that is <= the last{}evicted round {eviction_round}", if self.gc_enabled() { " gc " } else { " " } )); } - let mut result = self.recent_refs[slot.authority].range(( + let mut result = self.recent_refs_by_authority[slot.authority].range(( Included(BlockRef::new(slot.round, slot.authority, BlockDigest::MIN)), Included(BlockRef::new(slot.round, slot.authority, BlockDigest::MAX)), )); @@ -541,7 +580,7 @@ impl DagState { let mut missing = Vec::new(); for (index, block_ref) in block_refs.into_iter().enumerate() { - let recent_refs = &self.recent_refs[block_ref.author]; + let recent_refs = &self.recent_refs_by_authority[block_ref.author]; if recent_refs.contains(&block_ref) || self.genesis.contains_key(&block_ref) { exist[index] = true; } else if recent_refs.is_empty() || recent_refs.last().unwrap().round < block_ref.round @@ -728,6 +767,25 @@ impl DagState { self.last_committed_rounds.clone() } + /// The GC round is the highest round that blocks of equal or lower round are considered obsolete and no longer possible to be committed. + /// There is no meaning accepting any blocks with round <= gc_round. The Garbage Collection (GC) round is calculated based on the latest + /// committed leader round. When GC is disabled that will return the genesis round. + pub(crate) fn gc_round(&self) -> Round { + let gc_depth = self.context.protocol_config.gc_depth(); + if gc_depth > 0 { + // GC is enabled, only then calculate the diff + self.last_commit_round().saturating_sub(gc_depth) + } else { + // Otherwise just return genesis round. That also acts as a safety mechanism so we never attempt to truncate anything + // even accidentally. + GENESIS_ROUND + } + } + + pub(crate) fn gc_enabled(&self) -> bool { + self.context.protocol_config.gc_depth() > 0 + } + /// After each flush, DagState becomes persisted in storage and it expected to recover /// all internal states from storage after restarts. pub(crate) fn flush(&mut self) { @@ -741,22 +799,7 @@ impl DagState { // Flush buffered data to storage. let blocks = std::mem::take(&mut self.blocks_to_write); let commits = std::mem::take(&mut self.commits_to_write); - let commit_info_to_write = if self - .context - .protocol_config - .mysticeti_leader_scoring_and_schedule() - { - std::mem::take(&mut self.commit_info_to_write) - } else if commits.is_empty() { - vec![] - } else { - let last_commit_ref = commits.last().as_ref().unwrap().reference(); - let commit_info = CommitInfo::new( - self.last_committed_rounds.clone(), - ReputationScores::default(), - ); - vec![(last_commit_ref, commit_info)] - }; + let commit_info_to_write = std::mem::take(&mut self.commit_info_to_write); if blocks.is_empty() && commits.is_empty() { return; @@ -783,30 +826,29 @@ impl DagState { .inc(); // Clean up old cached data. After flushing, all cached blocks are guaranteed to be persisted. - let mut total_recent_refs = 0; - for (authority_refs, last_committed_round) in self - .recent_refs - .iter_mut() - .zip(self.last_committed_rounds.iter()) - { - while let Some(block_ref) = authority_refs.first() { - if block_ref.round - <= Self::eviction_round(*last_committed_round, self.cached_rounds) - { + for (authority_index, _) in self.context.committee.authorities() { + let eviction_round = self.calculate_authority_eviction_round(authority_index); + while let Some(block_ref) = self.recent_refs_by_authority[authority_index].first() { + if block_ref.round <= eviction_round { self.recent_blocks.remove(block_ref); - authority_refs.pop_first(); + self.recent_refs_by_authority[authority_index].pop_first(); } else { break; } } - total_recent_refs += authority_refs.len(); + self.evicted_rounds[authority_index] = eviction_round; } let metrics = &self.context.metrics.node_metrics; metrics .dag_state_recent_blocks .set(self.recent_blocks.len() as i64); - metrics.dag_state_recent_refs.set(total_recent_refs as i64); + metrics.dag_state_recent_refs.set( + self.recent_refs_by_authority + .iter() + .map(BTreeSet::len) + .sum::() as i64, + ); } /// Detects and returns the blocks of the round that forms the last quorum. The method will return @@ -893,12 +935,21 @@ impl DagState { self.genesis.values().cloned().collect() } - /// The last round that got evicted after a cache clean up operation. After this round we are + /// The last round that should get evicted after a cache clean up operation. After this round we are /// guaranteed to have all the produced blocks from that authority. For any round that is /// <= `last_evicted_round` we don't have such guarantees as out of order blocks might exist. - fn authority_eviction_round(&self, authority_index: AuthorityIndex) -> Round { - let commit_round = self.last_committed_rounds[authority_index]; - Self::eviction_round(commit_round, self.cached_rounds) + fn calculate_authority_eviction_round(&self, authority_index: AuthorityIndex) -> Round { + if self.gc_enabled() { + let last_round = self.recent_refs_by_authority[authority_index] + .last() + .map(|block_ref| block_ref.round) + .unwrap_or(GENESIS_ROUND); + + Self::gc_eviction_round(last_round, self.gc_round(), self.cached_rounds) + } else { + let commit_round = self.last_committed_rounds[authority_index]; + Self::eviction_round(commit_round, self.cached_rounds) + } } /// Calculates the last eviction round based on the provided `commit_round`. Any blocks with @@ -907,6 +958,12 @@ impl DagState { commit_round.saturating_sub(cached_rounds) } + /// Calculates the eviction round for the given authority. The goal is to keep at least `cached_rounds` + /// of the latest blocks in the cache (if enough data is available), while evicting blocks with rounds <= `gc_round` when possible. + fn gc_eviction_round(last_round: Round, gc_round: Round, cached_rounds: u32) -> Round { + gc_round.min(last_round.saturating_sub(cached_rounds)) + } + #[cfg(test)] pub(crate) fn set_last_commit(&mut self, commit: TrustedCommit) { self.last_commit = Some(commit); @@ -918,12 +975,14 @@ mod test { use std::vec; use parking_lot::RwLock; + use rstest::rstest; use super::*; use crate::{ block::{BlockDigest, BlockRef, BlockTimestampMs, TestBlock, VerifiedBlock}, storage::{mem_store::MemStore, WriteBatch}, test_dag_builder::DagBuilder, + test_dag_parser::parse_dag, }; #[tokio::test] @@ -1353,6 +1412,79 @@ mod test { dag_state.contains_cached_block_at_slot(Slot::new(8, AuthorityIndex::new_for_test(0))); } + #[tokio::test] + #[should_panic( + expected = "Attempted to check for slot B3 that is <= the last gc evicted round 3" + )] + async fn test_contains_cached_block_at_slot_panics_when_ask_out_of_range_gc_enabled() { + /// Keep 2 rounds from the highest committed round. This is considered universal and minimum necessary blocks to hold + /// for the correct node operation. + const GC_DEPTH: u32 = 2; + /// Keep at least 3 rounds in cache for each authority. + const CACHED_ROUNDS: Round = 3; + + let (mut context, _) = Context::new_for_test(4); + context + .protocol_config + .set_consensus_gc_depth_for_testing(GC_DEPTH); + context.parameters.dag_state_cached_rounds = CACHED_ROUNDS; + + let context = Arc::new(context); + let store = Arc::new(MemStore::new()); + let mut dag_state = DagState::new(context.clone(), store.clone()); + + // Create for rounds 1..=6. Skip creating blocks for authority 0 for rounds 4 - 6. + let mut dag_builder = DagBuilder::new(context.clone()); + dag_builder.layers(1..=3).build(); + dag_builder + .layers(4..=6) + .authorities(vec![AuthorityIndex::new_for_test(0)]) + .skip_block() + .build(); + + // Accept all blocks + dag_builder + .all_blocks() + .into_iter() + .for_each(|block| dag_state.accept_block(block)); + + // Now add a commit for leader round 5 to trigger an eviction + dag_state.add_commit(TrustedCommit::new_for_test( + 1 as CommitIndex, + CommitDigest::MIN, + 0, + dag_builder.leader_block(5).unwrap().reference(), + vec![], + )); + + dag_state.flush(); + + // Ensure that gc round has been updated + assert_eq!(dag_state.gc_round(), 3, "GC round should be 3"); + + // Now what we expect to happen is for: + // * Nodes 1 - 3 should have in cache blocks from gc_round (3) and onwards. + // * Node 0 should have in cache blocks from it's latest round, 3, up to round 1, which is the number of cached_rounds. + for authority_index in 1..=3 { + for round in 4..=6 { + assert!(dag_state.contains_cached_block_at_slot(Slot::new( + round, + AuthorityIndex::new_for_test(authority_index) + ))); + } + } + + for round in 1..=3 { + assert!(dag_state + .contains_cached_block_at_slot(Slot::new(round, AuthorityIndex::new_for_test(0)))); + } + + // When trying to request for authority 1 at block slot 3 it should panic, as anything + // that is <= 3 should be evicted + let _ = + dag_state.contains_cached_block_at_slot(Slot::new(3, AuthorityIndex::new_for_test(1))); + } + #[tokio::test] async fn test_get_blocks_in_cache_or_store() { let (context, _) = Context::new_for_test(4); @@ -1412,14 +1544,22 @@ mod test { } // TODO: Remove when DistributedVoteScoring is enabled. + #[rstest] #[tokio::test] - async fn test_flush_and_recovery_with_unscored_subdag() { + async fn test_flush_and_recovery_with_unscored_subdag(#[values(0, 5)] gc_depth: u32) { telemetry_subscribers::init_for_testing(); let num_authorities: u32 = 4; let (mut context, _) = Context::new_for_test(num_authorities as usize); context .protocol_config .set_consensus_distributed_vote_scoring_strategy_for_testing(false); + + if gc_depth > 0 { + context + .protocol_config + .set_consensus_gc_depth_for_testing(gc_depth); + } + let context = Arc::new(context); let store = Arc::new(MemStore::new()); let mut dag_state = DagState::new(context.clone(), store.clone()); @@ -1429,24 +1569,8 @@ mod test { let mut dag_builder = DagBuilder::new(context.clone()); dag_builder.layers(1..=num_rounds).build(); let mut commits = vec![]; - let leaders = dag_builder - .leader_blocks(1..=num_rounds) - .into_iter() - .flatten() - .collect::>(); - let mut last_committed_rounds = vec![0; 4]; - for (idx, leader) in leaders.into_iter().enumerate() { - let commit_index = idx as u32 + 1; - let (subdag, commit) = dag_builder.get_sub_dag_and_commit( - leader.clone(), - last_committed_rounds.clone(), - commit_index, - ); - for block in subdag.blocks.iter() { - last_committed_rounds[block.author().value()] = - max(block.round(), last_committed_rounds[block.author().value()]); - } + for (_subdag, commit) in dag_builder.get_sub_dag_and_commits(1..=num_rounds) { commits.push(commit); } @@ -1481,7 +1605,10 @@ mod test { // Last commit index should be 10. assert_eq!(dag_state.last_commit_index(), 10); - assert_eq!(dag_state.last_committed_rounds(), last_committed_rounds); + assert_eq!( + dag_state.last_committed_rounds(), + dag_builder.last_committed_rounds.clone() + ); // Destroy the dag state. drop(dag_state); @@ -1542,24 +1669,7 @@ mod test { let mut dag_builder = DagBuilder::new(context.clone()); dag_builder.layers(1..=num_rounds).build(); let mut commits = vec![]; - let leaders = dag_builder - .leader_blocks(1..=num_rounds) - .into_iter() - .flatten() - .collect::>(); - - let mut last_committed_rounds = vec![0; 4]; - for (idx, leader) in leaders.into_iter().enumerate() { - let commit_index = idx as u32 + 1; - let (subdag, commit) = dag_builder.get_sub_dag_and_commit( - leader.clone(), - last_committed_rounds.clone(), - commit_index, - ); - for block in subdag.blocks.iter() { - last_committed_rounds[block.author().value()] = - max(block.round(), last_committed_rounds[block.author().value()]); - } + for (_subdag, commit) in dag_builder.get_sub_dag_and_commits(1..=num_rounds) { commits.push(commit); } @@ -1594,7 +1704,10 @@ mod test { // Last commit index should be 10. assert_eq!(dag_state.last_commit_index(), 10); - assert_eq!(dag_state.last_committed_rounds(), last_committed_rounds); + assert_eq!( + dag_state.last_committed_rounds(), + dag_builder.last_committed_rounds.clone() + ); // Destroy the dag state. drop(dag_state); @@ -1641,6 +1754,144 @@ mod test { assert_eq!(dag_state.scoring_subdags_count(), 5); } + #[tokio::test] + async fn test_flush_and_recovery_gc_enabled() { + telemetry_subscribers::init_for_testing(); + + const GC_DEPTH: u32 = 3; + const CACHED_ROUNDS: u32 = 4; + + let num_authorities: u32 = 4; + let (mut context, _) = Context::new_for_test(num_authorities as usize); + context.parameters.dag_state_cached_rounds = CACHED_ROUNDS; + context + .protocol_config + .set_consensus_gc_depth_for_testing(GC_DEPTH); + + let context = Arc::new(context); + + let store = Arc::new(MemStore::new()); + let mut dag_state = DagState::new(context.clone(), store.clone()); + + let num_rounds: u32 = 10; + let mut dag_builder = DagBuilder::new(context.clone()); + dag_builder.layers(1..=5).build(); + dag_builder + .layers(6..=8) + .authorities(vec![AuthorityIndex::new_for_test(0)]) + .skip_block() + .build(); + dag_builder.layers(9..=num_rounds).build(); + + let mut commits = vec![]; + for (_subdag, commit) in dag_builder.get_sub_dag_and_commits(1..=num_rounds) { + commits.push(commit); + } + + // Add the blocks from first 8 rounds and first 7 commits to the dag state + // It's 7 commits because we missing the commit of round 8 where authority 0 is the leader, but produced no block + let temp_commits = commits.split_off(7); + dag_state.accept_blocks(dag_builder.blocks(1..=8)); + for commit in commits.clone() { + dag_state.add_commit(commit); + } + + // Flush the dag state + dag_state.flush(); + + // Add the rest of the blocks and commits to the dag state + dag_state.accept_blocks(dag_builder.blocks(9..=num_rounds)); + for commit in temp_commits.clone() { + dag_state.add_commit(commit); + } + + // All blocks should be found in DagState. + let all_blocks = dag_builder.blocks(1..=num_rounds); + let block_refs = all_blocks + .iter() + .map(|block| block.reference()) + .collect::>(); + let result = dag_state + .get_blocks(&block_refs) + .into_iter() + .map(|b| b.unwrap()) + .collect::>(); + assert_eq!(result, all_blocks); + + // Last commit index should be 9 + assert_eq!(dag_state.last_commit_index(), 9); + assert_eq!( + dag_state.last_committed_rounds(), + dag_builder.last_committed_rounds.clone() + ); + + // Destroy the dag state. + drop(dag_state); + + // Recover the state from the store + let dag_state = DagState::new(context.clone(), store.clone()); + + // Blocks of first 5 rounds should be found in DagState. + let blocks = dag_builder.blocks(1..=5); + let block_refs = blocks + .iter() + .map(|block| block.reference()) + .collect::>(); + let result = dag_state + .get_blocks(&block_refs) + .into_iter() + .map(|b| b.unwrap()) + .collect::>(); + assert_eq!(result, blocks); + + // Blocks above round 9 should not be in DagState, because they are not flushed. + let missing_blocks = dag_builder.blocks(9..=num_rounds); + let block_refs = missing_blocks + .iter() + .map(|block| block.reference()) + .collect::>(); + let retrieved_blocks = dag_state + .get_blocks(&block_refs) + .into_iter() + .flatten() + .collect::>(); + assert!(retrieved_blocks.is_empty()); + + // Last commit index should be 7. + assert_eq!(dag_state.last_commit_index(), 7); + + // This is the last_commmit_rounds of the first 7 commits that were flushed + let expected_last_committed_rounds = vec![5, 6, 6, 7]; + assert_eq!( + dag_state.last_committed_rounds(), + expected_last_committed_rounds + ); + // Unscored subdags will be recoverd based on the flushed commits and no commit info + assert_eq!(dag_state.scoring_subdags_count(), 7); + + // Ensure that cached blocks exist only for specific rounds per authority + for (authority_index, _) in context.committee.authorities() { + let blocks = dag_state.get_cached_blocks(authority_index, 1); + + // Ensure that eviction rounds have been properly recovered + // DagState should hold cached blocks for authority 0 for rounds [2..=5] as no higher blocks exist and due to CACHED_ROUNDS = 4 + // we want at max to hold blocks for 4 rounds in cache. + if authority_index == AuthorityIndex::new_for_test(0) { + assert_eq!(blocks.len(), 4); + assert_eq!(dag_state.evicted_rounds[authority_index.value()], 1); + assert!(blocks + .into_iter() + .all(|block| block.round() >= 2 && block.round() <= 5)); + } else { + assert_eq!(blocks.len(), 4); + assert_eq!(dag_state.evicted_rounds[authority_index.value()], 4); + assert!(blocks + .into_iter() + .all(|block| block.round() >= 5 && block.round() <= 8)); + } + } + } + #[tokio::test] async fn test_get_cached_blocks() { let (mut context, _) = Context::new_for_test(4); @@ -1696,13 +1947,20 @@ mod test { assert_eq!(cached_blocks[0].round(), 12); } + #[rstest] #[tokio::test] - async fn test_get_cached_last_block_per_authority() { + async fn test_get_cached_last_block_per_authority(#[values(0, 1)] gc_depth: u32) { // GIVEN const CACHED_ROUNDS: Round = 2; let (mut context, _) = Context::new_for_test(4); context.parameters.dag_state_cached_rounds = CACHED_ROUNDS; + if gc_depth > 0 { + context + .protocol_config + .set_consensus_gc_depth_for_testing(gc_depth); + } + let context = Arc::new(context); let store = Arc::new(MemStore::new()); let mut dag_state = DagState::new(context.clone(), store.clone()); @@ -1711,24 +1969,35 @@ mod test { // Create one block (round 1) for authority 1 // Create two blocks (rounds 1,2) for authority 2 // Create three blocks (rounds 1,2,3) for authority 3 - let mut all_blocks = Vec::new(); - for author in 1..=3 { - for round in 1..=author { - let block = VerifiedBlock::new_for_test(TestBlock::new(round, author).build()); - all_blocks.push(block.clone()); - dag_state.accept_block(block); - } + let dag_str = "DAG { + Round 0 : { 4 }, + Round 1 : { + B -> [*], + C -> [*], + D -> [*], + }, + Round 2 : { + C -> [*], + D -> [*], + }, + Round 3 : { + D -> [*], + }, + }"; + + let (_, dag_builder) = parse_dag(dag_str).expect("Invalid dag"); + + // Accept all blocks + for block in dag_builder.all_blocks() { + dag_state.accept_block(block); } dag_state.add_commit(TrustedCommit::new_for_test( 1 as CommitIndex, CommitDigest::MIN, context.clock.timestamp_utc_ms(), - all_blocks.last().unwrap().reference(), - all_blocks - .into_iter() - .map(|block| block.reference()) - .collect::>(), + dag_builder.leader_block(3).unwrap().reference(), + vec![], )); // WHEN search for the latest blocks @@ -1743,6 +2012,9 @@ mod test { // WHEN we flush the DagState - after adding a commit with all the blocks, we expect this to trigger // a clean up in the internal cache. That will keep the all the blocks with rounds >= authority_commit_round - CACHED_ROUND. + // + // When GC is enabled then we'll keep all the blocks that are > gc_round (2) and for those who don't have blocks > gc_round, we'll keep + // all their highest round blocks for CACHED_ROUNDS. dag_state.flush(); // AND we request before round 3 @@ -1803,6 +2075,72 @@ mod test { dag_state.get_last_cached_block_per_authority(end_round); } + #[tokio::test] + #[should_panic( + expected = "Attempted to request for blocks of rounds < 2, when the last evicted round is 1 for authority C" + )] + async fn test_get_cached_last_block_per_authority_requesting_out_of_round_range_gc_enabled() { + // GIVEN + const CACHED_ROUNDS: Round = 1; + const GC_DEPTH: u32 = 1; + let (mut context, _) = Context::new_for_test(4); + context.parameters.dag_state_cached_rounds = CACHED_ROUNDS; + context + .protocol_config + .set_consensus_gc_depth_for_testing(GC_DEPTH); + + let context = Arc::new(context); + let store = Arc::new(MemStore::new()); + let mut dag_state = DagState::new(context.clone(), store.clone()); + + // Create no blocks for authority 0 + // Create one block (round 1) for authority 1 + // Create two blocks (rounds 1,2) for authority 2 + // Create three blocks (rounds 1,2,3) for authority 3 + let mut dag_builder = DagBuilder::new(context.clone()); + dag_builder + .layers(1..=1) + .authorities(vec![AuthorityIndex::new_for_test(0)]) + .skip_block() + .build(); + dag_builder + .layers(2..=2) + .authorities(vec![ + AuthorityIndex::new_for_test(0), + AuthorityIndex::new_for_test(1), + ]) + .skip_block() + .build(); + dag_builder + .layers(3..=3) + .authorities(vec![ + AuthorityIndex::new_for_test(0), + AuthorityIndex::new_for_test(1), + AuthorityIndex::new_for_test(2), + ]) + .skip_block() + .build(); + + // Accept all blocks + for block in dag_builder.all_blocks() { + dag_state.accept_block(block); + } + + dag_state.add_commit(TrustedCommit::new_for_test( + 1 as CommitIndex, + CommitDigest::MIN, + 0, + dag_builder.leader_block(3).unwrap().reference(), + vec![], + )); + + // Flush the store so we update the evict rounds + dag_state.flush(); + + // THEN the method should panic, as some authorities have already evicted rounds <= round 2 + dag_state.get_last_cached_block_per_authority(2); + } + #[tokio::test] async fn test_last_quorum() { // GIVEN diff --git a/consensus/core/src/error.rs b/consensus/core/src/error.rs index 9dadc6c84da06..d78915b0c3097 100644 --- a/consensus/core/src/error.rs +++ b/consensus/core/src/error.rs @@ -126,9 +126,6 @@ pub(crate) enum ConsensusError { block_timestamp_ms: u64, }, - #[error("No available authority to fetch commits")] - NoAvailableAuthorityToFetchCommits, - #[error("Received no commit from peer {peer}")] NoCommitReceived { peer: AuthorityIndex }, @@ -192,6 +189,13 @@ pub(crate) enum ConsensusError { Shutdown, } +impl ConsensusError { + /// Returns the error name - only the enun name without any parameters - as a static string. + pub fn name(&self) -> &'static str { + self.into() + } +} + pub type ConsensusResult = Result; #[macro_export] @@ -209,3 +213,39 @@ macro_rules! ensure { } }; } + +#[cfg(test)] +mod test { + use super::*; + + /// This test ensures that consensus errors when converted to a static string are the same as the enum name without + /// any parameterers included to the result string. + #[test] + fn test_error_name() { + { + let error = ConsensusError::InvalidAncestorRound { + ancestor: 10, + block: 11, + }; + let error: &'static str = error.into(); + + assert_eq!(error, "InvalidAncestorRound"); + } + + { + let error = ConsensusError::InvalidAuthorityIndex { + index: AuthorityIndex::new_for_test(3), + max: 10, + }; + assert_eq!(error.name(), "InvalidAuthorityIndex"); + } + + { + let error = ConsensusError::InsufficientParentStakes { + parent_stakes: 5, + quorum: 20, + }; + assert_eq!(error.name(), "InsufficientParentStakes"); + } + } +} diff --git a/consensus/core/src/leader_schedule.rs b/consensus/core/src/leader_schedule.rs index 756cc9361d86b..21995bc977c1b 100644 --- a/consensus/core/src/leader_schedule.rs +++ b/consensus/core/src/leader_schedule.rs @@ -508,11 +508,10 @@ impl Debug for LeaderSwapTable { #[cfg(test)] mod tests { - use std::cmp::max; use super::*; use crate::{ - block::{BlockAPI as _, BlockDigest, BlockRef, BlockTimestampMs, TestBlock, VerifiedBlock}, + block::{BlockDigest, BlockRef, BlockTimestampMs, TestBlock, VerifiedBlock}, commit::{CommitDigest, CommitInfo, CommitRef, CommittedSubDag, TrustedCommit}, storage::{mem_store::MemStore, Store, WriteBatch}, test_dag_builder::DagBuilder, @@ -566,116 +565,6 @@ mod tests { ); } - #[tokio::test] - async fn test_leader_schedule_from_store_with_no_scores() { - telemetry_subscribers::init_for_testing(); - let mut context = Context::new_for_test(4).0; - context - .protocol_config - .set_mysticeti_leader_scoring_and_schedule_for_testing(false); - let context = Arc::new(context); - let store = Arc::new(MemStore::new()); - - let leader_timestamp = context.clock.timestamp_utc_ms(); - let blocks = vec![ - VerifiedBlock::new_for_test( - TestBlock::new(10, 2) - .set_timestamp_ms(leader_timestamp) - .build(), - ), - VerifiedBlock::new_for_test(TestBlock::new(9, 0).build()), - VerifiedBlock::new_for_test(TestBlock::new(9, 2).build()), - VerifiedBlock::new_for_test(TestBlock::new(9, 3).build()), - ]; - - let leader = blocks[0].clone(); - let leader_ref = leader.reference(); - let last_commit_index = 10; - let last_commit = TrustedCommit::new_for_test( - last_commit_index, - CommitDigest::MIN, - leader_timestamp, - leader_ref, - blocks - .iter() - .map(|block| block.reference()) - .collect::>(), - ); - - // The CommitInfo for the first 10 commits are written to store. This is the - // info that LeaderSchedule will be recovered from - let committed_rounds = vec![9, 9, 10, 9]; - let commit_ref = CommitRef::new(10, CommitDigest::MIN); - let commit_info = CommitInfo { - reputation_scores: ReputationScores::default(), - committed_rounds, - }; - - store - .write( - WriteBatch::default() - .commit_info(vec![(commit_ref, commit_info)]) - .blocks(blocks) - .commits(vec![last_commit]), - ) - .unwrap(); - - // CommitIndex '11' will be written to store. This should result in the cached - // last_committed_rounds & unscored subdags in DagState to be updated with the - // latest commit information on recovery. - let leader_timestamp = context.clock.timestamp_utc_ms(); - let blocks = vec![ - VerifiedBlock::new_for_test( - TestBlock::new(11, 3) - .set_timestamp_ms(leader_timestamp) - .build(), - ), - VerifiedBlock::new_for_test(TestBlock::new(10, 0).build()), - VerifiedBlock::new_for_test(TestBlock::new(10, 1).build()), - VerifiedBlock::new_for_test(TestBlock::new(10, 3).build()), - ]; - - let leader = blocks[0].clone(); - let leader_ref = leader.reference(); - let last_commit_index = 11; - let expected_last_committed_rounds = vec![10, 10, 10, 11]; - let last_commit = TrustedCommit::new_for_test( - last_commit_index, - CommitDigest::MIN, - leader_timestamp, - leader_ref, - blocks - .iter() - .map(|block| block.reference()) - .collect::>(), - ); - store - .write( - WriteBatch::default() - .blocks(blocks) - .commits(vec![last_commit]), - ) - .unwrap(); - - let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store))); - - // Check that DagState recovery from stored CommitInfo worked correctly - assert_eq!( - expected_last_committed_rounds, - dag_state.read().last_committed_rounds() - ); - - // Leader Scoring & Schedule Change is disabled, unscored subdags should not be accumulated. - assert_eq!(0, dag_state.read().scoring_subdags_count()); - - let leader_schedule = LeaderSchedule::from_store(context.clone(), dag_state.clone()); - - // Check that LeaderSchedule recovery from stored CommitInfo worked correctly - let leader_swap_table = leader_schedule.leader_swap_table.read(); - assert_eq!(leader_swap_table.good_nodes.len(), 0); - assert_eq!(leader_swap_table.bad_nodes.len(), 0); - } - #[tokio::test] async fn test_leader_schedule_from_store() { telemetry_subscribers::init_for_testing(); @@ -691,27 +580,12 @@ mod tests { dag_builder.layers(1..=11).build(); let mut subdags = vec![]; let mut expected_commits = vec![]; - let leaders = dag_builder - .leader_blocks(1..=11) - .into_iter() - .flatten() - .collect::>(); let mut blocks_to_write = vec![]; - let mut last_committed_rounds = vec![0; 4]; - for (idx, leader) in leaders.into_iter().enumerate() { - let commit_index = idx as u32 + 1; - let (sub_dag, commit) = dag_builder.get_sub_dag_and_commit( - leader.clone(), - last_committed_rounds.clone(), - commit_index, - ); + for (sub_dag, commit) in dag_builder.get_sub_dag_and_commits(1..=11) { for block in sub_dag.blocks.iter() { blocks_to_write.push(block.clone()); - last_committed_rounds[block.author().value()] = - max(block.round(), last_committed_rounds[block.author().value()]); } - expected_commits.push(commit); subdags.push(sub_dag); } @@ -743,7 +617,7 @@ mod tests { // Check that DagState recovery from stored CommitInfo worked correctly assert_eq!( - last_committed_rounds, + dag_builder.last_committed_rounds.clone(), dag_state.read().last_committed_rounds() ); assert_eq!(1, dag_state.read().scoring_subdags_count()); @@ -815,28 +689,14 @@ mod tests { let mut expected_scored_subdags = vec![]; let mut expected_commits = vec![]; - let leaders = dag_builder - .leader_blocks(1..=2) - .into_iter() - .flatten() - .collect::>(); let mut blocks_to_write = vec![]; - let mut last_committed_rounds = vec![0; 4]; - for (idx, leader) in leaders.into_iter().enumerate() { - let commit_index = idx as u32 + 1; - let (subdag, commit) = dag_builder.get_sub_dag_and_commit( - leader.clone(), - last_committed_rounds.clone(), - commit_index, - ); - for block in subdag.blocks.iter() { + for (sub_dag, commit) in dag_builder.get_sub_dag_and_commits(1..=2) { + for block in sub_dag.blocks.iter() { blocks_to_write.push(block.clone()); - last_committed_rounds[block.author().value()] = - max(block.round(), last_committed_rounds[block.author().value()]); } expected_commits.push(commit); - expected_scored_subdags.push(subdag); + expected_scored_subdags.push(sub_dag); } // The CommitInfo for the first 2 commits are written to store. 10 commits @@ -856,7 +716,7 @@ mod tests { // Check that DagState recovery from stored CommitInfo worked correctly assert_eq!( - last_committed_rounds, + dag_builder.last_committed_rounds.clone(), dag_state.read().last_committed_rounds() ); assert_eq!( @@ -888,6 +748,7 @@ mod tests { let unscored_subdags = vec![CommittedSubDag::new( BlockRef::new(1, AuthorityIndex::ZERO, BlockDigest::MIN), vec![], + vec![], context.clock.timestamp_utc_ms(), CommitRef::new(1, CommitDigest::MIN), vec![], @@ -969,6 +830,7 @@ mod tests { let leader_block = leader.unwrap(); let leader_ref = leader_block.reference(); let commit_index = 1; + let rejected_transactions = vec![vec![]; blocks.len()]; let last_commit = TrustedCommit::new_for_test( commit_index, @@ -984,6 +846,7 @@ mod tests { let unscored_subdags = vec![CommittedSubDag::new( leader_ref, blocks, + rejected_transactions, context.clock.timestamp_utc_ms(), last_commit.reference(), vec![], @@ -1195,119 +1058,6 @@ mod tests { } // TODO: Remove all tests below this when DistributedVoteScoring is enabled. - #[tokio::test] - async fn test_leader_schedule_from_store_with_no_scores_with_vote_scoring() { - telemetry_subscribers::init_for_testing(); - let mut context = Context::new_for_test(4).0; - context - .protocol_config - .set_consensus_distributed_vote_scoring_strategy_for_testing(false); - context - .protocol_config - .set_mysticeti_leader_scoring_and_schedule_for_testing(false); - let context = Arc::new(context); - let store = Arc::new(MemStore::new()); - - let leader_timestamp = context.clock.timestamp_utc_ms(); - let blocks = vec![ - VerifiedBlock::new_for_test( - TestBlock::new(10, 2) - .set_timestamp_ms(leader_timestamp) - .build(), - ), - VerifiedBlock::new_for_test(TestBlock::new(9, 0).build()), - VerifiedBlock::new_for_test(TestBlock::new(9, 2).build()), - VerifiedBlock::new_for_test(TestBlock::new(9, 3).build()), - ]; - - let leader = blocks[0].clone(); - let leader_ref = leader.reference(); - let last_commit_index = 10; - let last_commit = TrustedCommit::new_for_test( - last_commit_index, - CommitDigest::MIN, - leader_timestamp, - leader_ref, - blocks - .iter() - .map(|block| block.reference()) - .collect::>(), - ); - - // The CommitInfo for the first 10 commits are written to store. This is the - // info that LeaderSchedule will be recovered from - let committed_rounds = vec![9, 9, 10, 9]; - let commit_ref = CommitRef::new(10, CommitDigest::MIN); - let commit_info = CommitInfo { - reputation_scores: ReputationScores::default(), - committed_rounds, - }; - - store - .write( - WriteBatch::default() - .commit_info(vec![(commit_ref, commit_info)]) - .blocks(blocks) - .commits(vec![last_commit]), - ) - .unwrap(); - - // CommitIndex '11' will be written to store. This should result in the cached - // last_committed_rounds & unscored subdags in DagState to be updated with the - // latest commit information on recovery. - let leader_timestamp = context.clock.timestamp_utc_ms(); - let blocks = vec![ - VerifiedBlock::new_for_test( - TestBlock::new(11, 3) - .set_timestamp_ms(leader_timestamp) - .build(), - ), - VerifiedBlock::new_for_test(TestBlock::new(10, 0).build()), - VerifiedBlock::new_for_test(TestBlock::new(10, 1).build()), - VerifiedBlock::new_for_test(TestBlock::new(10, 3).build()), - ]; - - let leader = blocks[0].clone(); - let leader_ref = leader.reference(); - let last_commit_index = 11; - let expected_last_committed_rounds = vec![10, 10, 10, 11]; - let last_commit = TrustedCommit::new_for_test( - last_commit_index, - CommitDigest::MIN, - leader_timestamp, - leader_ref, - blocks - .iter() - .map(|block| block.reference()) - .collect::>(), - ); - store - .write( - WriteBatch::default() - .blocks(blocks) - .commits(vec![last_commit]), - ) - .unwrap(); - - let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store))); - - // Check that DagState recovery from stored CommitInfo worked correctly - assert_eq!( - expected_last_committed_rounds, - dag_state.read().last_committed_rounds() - ); - - // Leader Scoring & Schedule Change is disabled, unscored subdags should not be accumulated. - assert_eq!(0, dag_state.read().unscored_committed_subdags_count()); - - let leader_schedule = LeaderSchedule::from_store(context.clone(), dag_state.clone()); - - // Check that LeaderSchedule recovery from stored CommitInfo worked correctly - let leader_swap_table = leader_schedule.leader_swap_table.read(); - assert_eq!(leader_swap_table.good_nodes.len(), 0); - assert_eq!(leader_swap_table.bad_nodes.len(), 0); - } - #[tokio::test] async fn test_leader_schedule_from_store_with_vote_scoring() { telemetry_subscribers::init_for_testing(); @@ -1326,27 +1076,12 @@ mod tests { dag_builder.layers(1..=11).build(); let mut subdags = vec![]; let mut expected_commits = vec![]; - let leaders = dag_builder - .leader_blocks(1..=11) - .into_iter() - .flatten() - .collect::>(); let mut blocks_to_write = vec![]; - let mut last_committed_rounds = vec![0; 4]; - for (idx, leader) in leaders.into_iter().enumerate() { - let commit_index = idx as u32 + 1; - let (sub_dag, commit) = dag_builder.get_sub_dag_and_commit( - leader.clone(), - last_committed_rounds.clone(), - commit_index, - ); + for (sub_dag, commit) in dag_builder.get_sub_dag_and_commits(1..=11) { for block in sub_dag.blocks.iter() { blocks_to_write.push(block.clone()); - last_committed_rounds[block.author().value()] = - max(block.round(), last_committed_rounds[block.author().value()]); } - expected_commits.push(commit); subdags.push(sub_dag); } @@ -1378,7 +1113,7 @@ mod tests { // Check that DagState recovery from stored CommitInfo worked correctly assert_eq!( - last_committed_rounds, + dag_builder.last_committed_rounds.clone(), dag_state.read().last_committed_rounds() ); let actual_unscored_subdags = dag_state.read().unscored_committed_subdags(); @@ -1456,28 +1191,14 @@ mod tests { let mut expected_unscored_subdags = vec![]; let mut expected_commits = vec![]; - let leaders = dag_builder - .leader_blocks(1..=2) - .into_iter() - .flatten() - .collect::>(); let mut blocks_to_write = vec![]; - let mut last_committed_rounds = vec![0; 4]; - for (idx, leader) in leaders.into_iter().enumerate() { - let commit_index = idx as u32 + 1; - let (subdag, commit) = dag_builder.get_sub_dag_and_commit( - leader.clone(), - last_committed_rounds.clone(), - commit_index, - ); - for block in subdag.blocks.iter() { + for (sub_dag, commit) in dag_builder.get_sub_dag_and_commits(1..=2) { + for block in sub_dag.blocks.iter() { blocks_to_write.push(block.clone()); - last_committed_rounds[block.author().value()] = - max(block.round(), last_committed_rounds[block.author().value()]); } expected_commits.push(commit); - expected_unscored_subdags.push(subdag); + expected_unscored_subdags.push(sub_dag); } // The CommitInfo for the first 2 commits are written to store. 10 commits @@ -1497,7 +1218,7 @@ mod tests { // Check that DagState recovery from stored CommitInfo worked correctly assert_eq!( - last_committed_rounds, + dag_builder.last_committed_rounds.clone(), dag_state.read().last_committed_rounds() ); let actual_unscored_subdags = dag_state.read().unscored_committed_subdags(); @@ -1535,6 +1256,7 @@ mod tests { let unscored_subdags = vec![CommittedSubDag::new( BlockRef::new(1, AuthorityIndex::ZERO, BlockDigest::MIN), vec![], + vec![], context.clock.timestamp_utc_ms(), CommitRef::new(1, CommitDigest::MIN), vec![], @@ -1622,6 +1344,7 @@ mod tests { let leader_block = leader.unwrap(); let leader_ref = leader_block.reference(); let commit_index = 1; + let rejected_transactions = vec![vec![]; blocks.len()]; let last_commit = TrustedCommit::new_for_test( commit_index, @@ -1637,6 +1360,7 @@ mod tests { let unscored_subdags = vec![CommittedSubDag::new( leader_ref, blocks, + rejected_transactions, context.clock.timestamp_utc_ms(), last_commit.reference(), vec![], diff --git a/consensus/core/src/leader_scoring.rs b/consensus/core/src/leader_scoring.rs index c7b151607cd01..7136daf7c5e30 100644 --- a/consensus/core/src/leader_scoring.rs +++ b/consensus/core/src/leader_scoring.rs @@ -475,8 +475,6 @@ impl UnscoredSubdag { #[cfg(test)] mod tests { - use std::cmp::max; - use super::*; use crate::{test_dag_builder::DagBuilder, CommitDigest, CommitRef}; @@ -551,26 +549,10 @@ mod tests { .skip_block() .build(); - let leaders = dag_builder - .leader_blocks(1..=4) - .into_iter() - .flatten() - .collect::>(); - let mut scoring_subdag = ScoringSubdag::new(context.clone()); - let mut last_committed_rounds = vec![0; 4]; - for (idx, leader) in leaders.into_iter().enumerate() { - let commit_index = idx as u32 + 1; - let (subdag, _commit) = dag_builder.get_sub_dag_and_commit( - leader, - last_committed_rounds.clone(), - commit_index, - ); - for block in subdag.blocks.iter() { - last_committed_rounds[block.author().value()] = - max(block.round(), last_committed_rounds[block.author().value()]); - } - scoring_subdag.add_subdags(vec![subdag]); + + for (sub_dag, _commit) in dag_builder.get_sub_dag_and_commits(1..=4) { + scoring_subdag.add_subdags(vec![sub_dag]); } let scores = scoring_subdag.calculate_scores(); @@ -597,26 +579,10 @@ mod tests { .skip_block() .build(); - let leaders = dag_builder - .leader_blocks(1..=4) - .into_iter() - .flatten() - .collect::>(); - let mut scoring_subdag = ScoringSubdag::new(context.clone()); - let mut last_committed_rounds = vec![0; 4]; - for (idx, leader) in leaders.into_iter().enumerate() { - let commit_index = idx as u32 + 1; - let (subdag, _commit) = dag_builder.get_sub_dag_and_commit( - leader, - last_committed_rounds.clone(), - commit_index, - ); - for block in subdag.blocks.iter() { - last_committed_rounds[block.author().value()] = - max(block.round(), last_committed_rounds[block.author().value()]); - } - scoring_subdag.add_subdags(vec![subdag]); + + for (sub_dag, _commit) in dag_builder.get_sub_dag_and_commits(1..=4) { + scoring_subdag.add_subdags(vec![sub_dag]); } let scores_per_authority = scoring_subdag.score_certified_votes(); @@ -644,27 +610,11 @@ mod tests { .skip_block() .build(); - let leaders = dag_builder - .leader_blocks(1..=4) - .into_iter() - .flatten() - .collect::>(); - let mut unscored_subdags = vec![]; - let mut last_committed_rounds = vec![0; 4]; - for (idx, leader) in leaders.into_iter().enumerate() { - let commit_index = idx as u32 + 1; - let (subdag, _commit) = dag_builder.get_sub_dag_and_commit( - leader, - last_committed_rounds.clone(), - commit_index, - ); - for block in subdag.blocks.iter() { - last_committed_rounds[block.author().value()] = - max(block.round(), last_committed_rounds[block.author().value()]); - } - unscored_subdags.push(subdag); + for (sub_dag, _commit) in dag_builder.get_sub_dag_and_commits(1..=4) { + unscored_subdags.push(sub_dag); } + let mut calculator = ReputationScoreCalculator::new(context.clone(), &unscored_subdags); let scores = calculator.calculate(); assert_eq!(scores.scores_per_authority, vec![3, 2, 2, 2]); @@ -692,6 +642,7 @@ mod tests { let unscored_subdags = vec![CommittedSubDag::new( BlockRef::new(1, AuthorityIndex::ZERO, BlockDigest::MIN), blocks, + vec![], context.clock.timestamp_utc_ms(), CommitRef::new(1, CommitDigest::MIN), vec![], @@ -726,27 +677,9 @@ mod tests { .skip_block() .build(); - let leaders = dag_builder - .leader_blocks(1..=4) - .into_iter() - .flatten() - .collect::>(); - let mut unscored_subdags = vec![]; - let mut last_committed_rounds = vec![0; 4]; - for (idx, leader) in leaders.into_iter().enumerate() { - let commit_index = idx as u32 + 1; - let (subdag, _commit) = dag_builder.get_sub_dag_and_commit( - leader, - last_committed_rounds.clone(), - commit_index, - ); - tracing::info!("{subdag:?}"); - for block in subdag.blocks.iter() { - last_committed_rounds[block.author().value()] = - max(block.round(), last_committed_rounds[block.author().value()]); - } - unscored_subdags.push(subdag); + for (sub_dag, _commit) in dag_builder.get_sub_dag_and_commits(1..=4) { + unscored_subdags.push(sub_dag); } let mut calculator = ReputationScoreCalculator::new(context.clone(), &unscored_subdags); diff --git a/consensus/core/src/leader_timeout.rs b/consensus/core/src/leader_timeout.rs index 4c2c15b1bbc55..1e28852edc3e3 100644 --- a/consensus/core/src/leader_timeout.rs +++ b/consensus/core/src/leader_timeout.rs @@ -133,6 +133,7 @@ mod tests { use crate::core::CoreSignals; use crate::core_thread::{CoreError, CoreThreadDispatcher}; use crate::leader_timeout::LeaderTimeoutTask; + use crate::round_prober::QuorumRound; #[derive(Clone, Default)] struct MockCoreThreadDispatcher { @@ -171,7 +172,11 @@ mod tests { todo!() } - fn set_propagation_delay(&self, _delay: Round) -> Result<(), CoreError> { + fn set_propagation_delay_and_quorum_rounds( + &self, + _delay: Round, + _quorum_rounds: Vec, + ) -> Result<(), CoreError> { todo!() } diff --git a/consensus/core/src/lib.rs b/consensus/core/src/lib.rs index b7597853dbd2a..36b980311f5d7 100644 --- a/consensus/core/src/lib.rs +++ b/consensus/core/src/lib.rs @@ -46,9 +46,12 @@ mod test_dag_parser; /// Exported consensus API. pub use authority_node::ConsensusAuthority; pub use block::{BlockAPI, Round, TransactionIndex}; +/// Exported API for testing. +pub use block::{TestBlock, Transaction, VerifiedBlock}; pub use commit::{CommitDigest, CommitIndex, CommitRef, CommittedSubDag}; pub use commit_consumer::{CommitConsumer, CommitConsumerMonitor}; +pub use network::{ + connection_monitor::{AnemoConnectionMonitor, ConnectionMonitorHandle, ConnectionStatus}, + metrics::{MetricsMakeCallbackHandler, NetworkRouteMetrics, QuinnConnectionMetrics}, +}; pub use transaction::{ClientError, TransactionClient, TransactionVerifier, ValidationError}; - -/// Exported API for testing. -pub use block::{TestBlock, Transaction, VerifiedBlock}; diff --git a/consensus/core/src/linearizer.rs b/consensus/core/src/linearizer.rs index f9fffd6e659f5..dba030d05271e 100644 --- a/consensus/core/src/linearizer.rs +++ b/consensus/core/src/linearizer.rs @@ -7,12 +7,39 @@ use consensus_config::AuthorityIndex; use parking_lot::RwLock; use crate::{ - block::{BlockAPI, VerifiedBlock}, + block::{BlockAPI, BlockRef, VerifiedBlock}, commit::{sort_sub_dag_blocks, Commit, CommittedSubDag, TrustedCommit}, dag_state::DagState, leader_schedule::LeaderSchedule, + Round, TransactionIndex, }; +/// The `StorageAPI` trait provides an interface for the block store and has been +/// mostly introduced for allowing to inject the test store in `DagBuilder`. +pub(crate) trait BlockStoreAPI { + fn get_blocks(&self, refs: &[BlockRef]) -> Vec>; + + fn gc_round(&self) -> Round; + + fn gc_enabled(&self) -> bool; +} + +impl BlockStoreAPI + for parking_lot::lock_api::RwLockReadGuard<'_, parking_lot::RawRwLock, DagState> +{ + fn get_blocks(&self, refs: &[BlockRef]) -> Vec> { + DagState::get_blocks(self, refs) + } + + fn gc_round(&self) -> Round { + DagState::gc_round(self) + } + + fn gc_enabled(&self) -> bool { + DagState::gc_enabled(self) + } +} + /// Expand a committed sequence of leader into a sequence of sub-dags. #[derive(Clone)] pub(crate) struct Linearizer { @@ -45,13 +72,56 @@ impl Linearizer { let last_commit_digest = dag_state.last_commit_digest(); let last_commit_timestamp_ms = dag_state.last_commit_timestamp_ms(); let last_committed_rounds = dag_state.last_committed_rounds(); + let timestamp_ms = leader_block.timestamp_ms().max(last_commit_timestamp_ms); - let mut to_commit = Vec::new(); - let mut committed = HashSet::new(); + // Now linearize the sub-dag starting from the leader block + let (to_commit, rejected_transactions) = + Self::linearize_sub_dag(leader_block.clone(), last_committed_rounds, dag_state); - let timestamp_ms = leader_block.timestamp_ms().max(last_commit_timestamp_ms); + // Create the Commit. + let commit = Commit::new( + last_commit_index + 1, + last_commit_digest, + timestamp_ms, + leader_block.reference(), + to_commit + .iter() + .map(|block| block.reference()) + .collect::>(), + ); + let serialized = commit + .serialize() + .unwrap_or_else(|e| panic!("Failed to serialize commit: {}", e)); + let commit = TrustedCommit::new_trusted(commit, serialized); + + // Create the corresponding committed sub dag + let sub_dag = CommittedSubDag::new( + leader_block.reference(), + to_commit, + rejected_transactions, + timestamp_ms, + commit.reference(), + reputation_scores_desc, + ); + + (sub_dag, commit) + } + + pub(crate) fn linearize_sub_dag( + leader_block: VerifiedBlock, + last_committed_rounds: Vec, + dag_state: impl BlockStoreAPI, + ) -> (Vec, Vec>) { + let gc_enabled = dag_state.gc_enabled(); + // The GC round here is calculated based on the last committed round of the leader block. The algorithm will attempt to + // commit blocks up to this GC round. Once this commit has been processed and written to DagState, then gc round will update + // and on the processing of the next commit we'll have it already updated, so no need to do any gc_round recalculations here. + // We just use whatever is currently in DagState. + let gc_round: Round = dag_state.gc_round(); let leader_block_ref = leader_block.reference(); let mut buffer = vec![leader_block]; + let mut committed = HashSet::new(); + let mut to_commit = Vec::new(); assert!(committed.insert(leader_block_ref)); while let Some(x) = buffer.pop() { @@ -65,9 +135,16 @@ impl Linearizer { .filter(|ancestor| { // We skip the block if we already committed it or we reached a // round that we already committed. + // TODO: for Fast Path we need to ammend the recursion rule here and allow us to commit blocks all the way up to the `gc_round`. + // Some additional work will be needed to make sure that we keep the uncommitted blocks up to the `gc_round` across commits. !committed.contains(ancestor) && last_committed_rounds[ancestor.author] < ancestor.round }) + .filter(|ancestor| { + // Keep the block if GC is not enabled or it is enabled and the block is above the gc_round. We do this + // to stop the recursion early and avoid going to deep when it's unnecessary. + !gc_enabled || ancestor.round > gc_round + }) .collect::>(), ) .into_iter() @@ -82,37 +159,20 @@ impl Linearizer { } } - drop(dag_state); + // The above code should have not yielded any blocks that are <= gc_round, but just to make sure that we'll never + // commit anything that should be garbage collected we attempt to prune here as well. + if gc_enabled { + assert!(to_commit.iter().all(|block| block.round() > gc_round), "No blocks <= {gc_round} should be committed. Leader round {}, blocks {to_commit:?}.", leader_block_ref); + } // Sort the blocks of the sub-dag blocks sort_sub_dag_blocks(&mut to_commit); - // Create the Commit. - let commit = Commit::new( - last_commit_index + 1, - last_commit_digest, - timestamp_ms, - leader_block_ref, - to_commit - .iter() - .map(|block| block.reference()) - .collect::>(), - ); - let serialized = commit - .serialize() - .unwrap_or_else(|e| panic!("Failed to serialize commit: {}", e)); - let commit = TrustedCommit::new_trusted(commit, serialized); + // TODO(fastpath): determine rejected transactions from voting. + // Get rejected transactions. + let rejected_transactions = vec![vec![]; to_commit.len()]; - // Create the corresponding committed sub dag - let sub_dag = CommittedSubDag::new( - leader_block_ref, - to_commit, - timestamp_ms, - commit.reference(), - reputation_scores_desc, - ); - - (sub_dag, commit) + (to_commit, rejected_transactions) } // This function should be called whenever a new commit is observed. This will @@ -168,6 +228,8 @@ impl Linearizer { #[cfg(test)] mod tests { + use rstest::rstest; + use super::*; use crate::{ commit::{CommitAPI as _, CommitDigest, DEFAULT_WAVE_LENGTH}, @@ -175,6 +237,7 @@ mod tests { leader_schedule::{LeaderSchedule, LeaderSwapTable}, storage::mem_store::MemStore, test_dag_builder::DagBuilder, + test_dag_parser::parse_dag, CommitIndex, }; @@ -467,4 +530,114 @@ mod tests { assert!(block.round() <= expected_second_commit.leader().round); } } + + /// This test will run the linearizer with GC disabled (gc_depth = 0) and gc enabled (gc_depth = 3) and make + /// sure that for the exact same DAG the linearizer will commit different blocks according to the rules. + #[rstest] + #[tokio::test] + async fn test_handle_commit_with_gc_simple(#[values(0, 3)] gc_depth: u32) { + telemetry_subscribers::init_for_testing(); + + let num_authorities = 4; + let (mut context, _keys) = Context::new_for_test(num_authorities); + context.protocol_config.set_gc_depth_for_testing(gc_depth); + let context = Arc::new(context); + let dag_state = Arc::new(RwLock::new(DagState::new( + context.clone(), + Arc::new(MemStore::new()), + ))); + let leader_schedule = Arc::new(LeaderSchedule::new( + context.clone(), + LeaderSwapTable::default(), + )); + let mut linearizer = Linearizer::new(dag_state.clone(), leader_schedule); + + // Authorities of index 0->2 will always creates blocks that see each other, but until round 5 they won't see the blocks of authority 3. + // For authority 3 we create blocks that connect to all the other authorities. + // On round 5 we finally make the other authorities see the blocks of authority 3. + // Practically we "simulate" here a long chain created by authority 3 that is visible in round 5, but due to GC blocks of only round >=2 will + // be committed, when GC is enabled. When GC is disabled all blocks will be committed for rounds >= 1. + let dag_str = "DAG { + Round 0 : { 4 }, + Round 1 : { * }, + Round 2 : { + A -> [-D1], + B -> [-D1], + C -> [-D1], + D -> [*], + }, + Round 3 : { + A -> [-D2], + B -> [-D2], + C -> [-D2], + }, + Round 4 : { + A -> [-D3], + B -> [-D3], + C -> [-D3], + D -> [A3, B3, C3, D2], + }, + Round 5 : { * }, + }"; + + let (_, dag_builder) = parse_dag(dag_str).expect("Invalid dag"); + dag_builder.print(); + dag_builder.persist_all_blocks(dag_state.clone()); + + let leaders = dag_builder + .leader_blocks(1..=6) + .into_iter() + .flatten() + .collect::>(); + + let commits = linearizer.handle_commit(leaders.clone()); + for (idx, subdag) in commits.into_iter().enumerate() { + tracing::info!("{subdag:?}"); + assert_eq!(subdag.leader, leaders[idx].reference()); + assert_eq!(subdag.timestamp_ms, leaders[idx].timestamp_ms()); + if idx == 0 { + // First subdag includes the leader block only + assert_eq!(subdag.blocks.len(), 1); + } else if idx == 1 { + assert_eq!(subdag.blocks.len(), 3); + } else if idx == 2 { + // We commit: + // * 1 block on round 4, the leader block + // * 3 blocks on round 3, as no commit happened on round 3 since the leader was missing + // * 2 blocks on round 2, again as no commit happened on round 3, we commit the "sub dag" of leader of round 3, which will be another 2 blocks + assert_eq!(subdag.blocks.len(), 6); + } else { + // GC is enabled, so we expect to see only blocks of round >= 2 + if gc_depth > 0 { + // Now it's going to be the first time that a leader will see the blocks of authority 3 and will attempt to commit + // the long chain. However, due to GC it will only commit blocks of round > 1. That's because it will commit blocks + // up to previous leader's round (round = 4) minus the gc_depth = 3, so that will be gc_round = 4 - 3 = 1. So we expect + // to see on the sub dag committed blocks of round >= 2. + assert_eq!(subdag.blocks.len(), 5); + + assert!( + subdag.blocks.iter().all(|block| block.round() >= 2), + "Found blocks that are of round < 2." + ); + + // Also ensure that gc_round has advanced with the latest committed leader + assert_eq!(dag_state.read().gc_round(), subdag.leader.round - gc_depth); + } else { + // GC is disabled, so we expect to see all blocks of round >= 1 + assert_eq!(subdag.blocks.len(), 6); + assert!( + subdag.blocks.iter().all(|block| block.round() >= 1), + "Found blocks that are of round < 1." + ); + + // GC round should never have moved + assert_eq!(dag_state.read().gc_round(), 0); + } + } + for block in subdag.blocks.iter() { + assert!(block.round() <= leaders[idx].round()); + } + assert_eq!(subdag.commit_ref.index, idx as CommitIndex + 1); + } + } } diff --git a/consensus/core/src/metrics.rs b/consensus/core/src/metrics.rs index 4915b6601c0e7..a9f0ad175e848 100644 --- a/consensus/core/src/metrics.rs +++ b/consensus/core/src/metrics.rs @@ -155,6 +155,8 @@ pub(crate) struct NodeMetrics { pub(crate) block_manager_missing_blocks: IntGauge, pub(crate) block_manager_missing_blocks_by_authority: IntCounterVec, pub(crate) block_manager_missing_ancestors_by_authority: IntCounterVec, + pub(crate) block_manager_gc_unsuspended_blocks: IntCounterVec, + pub(crate) block_manager_skipped_blocks: IntCounterVec, pub(crate) threshold_clock_round: IntGauge, pub(crate) subscriber_connection_attempts: IntCounterVec, pub(crate) subscribed_to: IntGaugeVec, @@ -173,6 +175,7 @@ pub(crate) struct NodeMetrics { pub(crate) commit_sync_fetch_once_latency: Histogram, pub(crate) commit_sync_fetch_once_errors: IntCounterVec, pub(crate) round_prober_quorum_round_gaps: IntGaugeVec, + pub(crate) round_prober_low_quorum_round: IntGaugeVec, pub(crate) round_prober_current_round_gaps: IntGaugeVec, pub(crate) round_prober_propagation_delays: Histogram, pub(crate) round_prober_last_propagation_delay: IntGauge, @@ -365,7 +368,7 @@ impl NodeMetrics { invalid_blocks: register_int_counter_vec_with_registry!( "invalid_blocks", "Number of invalid blocks per peer authority", - &["authority", "source"], + &["authority", "source", "error"], registry, ).unwrap(), rejected_blocks: register_int_counter_vec_with_registry!( @@ -514,6 +517,18 @@ impl NodeMetrics { &["authority"], registry, ).unwrap(), + block_manager_gc_unsuspended_blocks: register_int_counter_vec_with_registry!( + "block_manager_gc_unsuspended_blocks", + "The number of blocks unsuspended because their missing ancestors are garbage collected by the block manager, counted by block's source authority", + &["authority"], + registry, + ).unwrap(), + block_manager_skipped_blocks: register_int_counter_vec_with_registry!( + "block_manager_skipped_blocks", + "The number of blocks skipped by the block manager due to block round being <= gc_round", + &["authority"], + registry, + ).unwrap(), threshold_clock_round: register_int_gauge_with_registry!( "threshold_clock_round", "The current threshold clock round. We only advance to a new round when a quorum of parents have been synced.", @@ -602,7 +617,7 @@ impl NodeMetrics { commit_sync_fetch_once_errors: register_int_counter_vec_with_registry!( "commit_sync_fetch_once_errors", "Number of errors when attempting to fetch commits and blocks from single authority during commit sync.", - &["error"], + &["authority", "error"], registry ).unwrap(), round_prober_quorum_round_gaps: register_int_gauge_vec_with_registry!( @@ -611,6 +626,12 @@ impl NodeMetrics { &["authority"], registry ).unwrap(), + round_prober_low_quorum_round: register_int_gauge_vec_with_registry!( + "round_prober_low_quorum_round", + "Low quorum round among peers for blocks proposed from each authority", + &["authority"], + registry + ).unwrap(), round_prober_current_round_gaps: register_int_gauge_vec_with_registry!( "round_prober_current_round_gaps", "Round gaps from local last proposed round to the low quorum round of each peer. Can be negative.", diff --git a/consensus/core/src/network/connection_monitor.rs b/consensus/core/src/network/connection_monitor.rs index 928fc8b41a040..c07fd49996a43 100644 --- a/consensus/core/src/network/connection_monitor.rs +++ b/consensus/core/src/network/connection_monitor.rs @@ -17,11 +17,9 @@ use super::metrics::QuinnConnectionMetrics; const CONNECTION_STAT_COLLECTION_INTERVAL: Duration = Duration::from_secs(60); -pub(crate) struct ConnectionMonitorHandle { +pub struct ConnectionMonitorHandle { handle: JoinHandle<()>, stop: Sender<()>, - // TODO: Sui will use this component eventually instead of the NW version - #[allow(unused)] connection_statuses: Arc>, } @@ -30,6 +28,10 @@ impl ConnectionMonitorHandle { self.stop.send(()).ok(); self.handle.await.ok(); } + + pub fn connection_statuses(&self) -> Arc> { + self.connection_statuses.clone() + } } #[derive(Eq, PartialEq, Clone, Debug)] @@ -89,10 +91,10 @@ impl AnemoConnectionMonitor { // we report first all the known peers as disconnected - so we can see // their labels in the metrics reporting tool - for (peer_id, hostname) in &self.known_peers { + for (peer_id, peer_label) in &self.known_peers { self.connection_metrics .network_peer_connected - .with_label_values(&[&format!("{peer_id}"), hostname]) + .with_label_values(&[&format!("{peer_id}"), peer_label]) .set(0) } @@ -114,10 +116,10 @@ impl AnemoConnectionMonitor { self.connection_metrics.socket_send_buffer_size.set( network.socket_send_buf_size() as i64 ); - for (peer_id, hostname) in &self.known_peers { + for (peer_id, peer_label) in &self.known_peers { if let Some(connection) = network.peer(*peer_id) { let stats = connection.connection_stats(); - self.update_quinn_metrics_for_peer(&format!("{peer_id}"), hostname, &stats); + self.update_quinn_metrics_for_peer(&format!("{peer_id}"), peer_label, &stats); } } } else { @@ -153,17 +155,17 @@ impl AnemoConnectionMonitor { // Only report peer IDs for known peers to prevent unlimited cardinality. if self.known_peers.contains_key(&peer_id) { let peer_id_str = format!("{peer_id}"); - let hostname = self.known_peers.get(&peer_id).unwrap(); + let peer_label = self.known_peers.get(&peer_id).unwrap(); self.connection_metrics .network_peer_connected - .with_label_values(&[&peer_id_str, hostname]) + .with_label_values(&[&peer_id_str, peer_label]) .set(int_status); if let PeerEvent::LostPeer(_, reason) = peer_event { self.connection_metrics .network_peer_disconnects - .with_label_values(&[&peer_id_str, hostname, &format!("{reason:?}")]) + .with_label_values(&[&peer_id_str, peer_label, &format!("{reason:?}")]) .inc(); } } @@ -173,85 +175,85 @@ impl AnemoConnectionMonitor { fn update_quinn_metrics_for_peer( &self, peer_id: &str, - hostname: &str, + peer_label: &str, stats: &ConnectionStats, ) { // Update PathStats self.connection_metrics .network_peer_rtt - .with_label_values(&[peer_id, hostname]) + .with_label_values(&[peer_id, peer_label]) .set(stats.path.rtt.as_millis() as i64); self.connection_metrics .network_peer_lost_packets - .with_label_values(&[peer_id, hostname]) + .with_label_values(&[peer_id, peer_label]) .set(stats.path.lost_packets as i64); self.connection_metrics .network_peer_lost_bytes - .with_label_values(&[peer_id, hostname]) + .with_label_values(&[peer_id, peer_label]) .set(stats.path.lost_bytes as i64); self.connection_metrics .network_peer_sent_packets - .with_label_values(&[peer_id, hostname]) + .with_label_values(&[peer_id, peer_label]) .set(stats.path.sent_packets as i64); self.connection_metrics .network_peer_congestion_events - .with_label_values(&[peer_id, hostname]) + .with_label_values(&[peer_id, peer_label]) .set(stats.path.congestion_events as i64); self.connection_metrics .network_peer_congestion_window - .with_label_values(&[peer_id, hostname]) + .with_label_values(&[peer_id, peer_label]) .set(stats.path.cwnd as i64); // Update FrameStats self.connection_metrics .network_peer_max_data - .with_label_values(&[peer_id, hostname, "transmitted"]) + .with_label_values(&[peer_id, peer_label, "transmitted"]) .set(stats.frame_tx.max_data as i64); self.connection_metrics .network_peer_max_data - .with_label_values(&[peer_id, hostname, "received"]) + .with_label_values(&[peer_id, peer_label, "received"]) .set(stats.frame_rx.max_data as i64); self.connection_metrics .network_peer_closed_connections - .with_label_values(&[peer_id, hostname, "transmitted"]) + .with_label_values(&[peer_id, peer_label, "transmitted"]) .set(stats.frame_tx.connection_close as i64); self.connection_metrics .network_peer_closed_connections - .with_label_values(&[peer_id, hostname, "received"]) + .with_label_values(&[peer_id, peer_label, "received"]) .set(stats.frame_rx.connection_close as i64); self.connection_metrics .network_peer_data_blocked - .with_label_values(&[peer_id, hostname, "transmitted"]) + .with_label_values(&[peer_id, peer_label, "transmitted"]) .set(stats.frame_tx.data_blocked as i64); self.connection_metrics .network_peer_data_blocked - .with_label_values(&[peer_id, hostname, "received"]) + .with_label_values(&[peer_id, peer_label, "received"]) .set(stats.frame_rx.data_blocked as i64); // Update UDPStats self.connection_metrics .network_peer_udp_datagrams - .with_label_values(&[peer_id, hostname, "transmitted"]) + .with_label_values(&[peer_id, peer_label, "transmitted"]) .set(stats.udp_tx.datagrams as i64); self.connection_metrics .network_peer_udp_datagrams - .with_label_values(&[peer_id, hostname, "received"]) + .with_label_values(&[peer_id, peer_label, "received"]) .set(stats.udp_rx.datagrams as i64); self.connection_metrics .network_peer_udp_bytes - .with_label_values(&[peer_id, hostname, "transmitted"]) + .with_label_values(&[peer_id, peer_label, "transmitted"]) .set(stats.udp_tx.bytes as i64); self.connection_metrics .network_peer_udp_bytes - .with_label_values(&[peer_id, hostname, "received"]) + .with_label_values(&[peer_id, peer_label, "received"]) .set(stats.udp_rx.bytes as i64); self.connection_metrics .network_peer_udp_transmits - .with_label_values(&[peer_id, hostname, "transmitted"]) + .with_label_values(&[peer_id, peer_label, "transmitted"]) .set(stats.udp_tx.ios as i64); self.connection_metrics .network_peer_udp_transmits - .with_label_values(&[peer_id, hostname, "received"]) + .with_label_values(&[peer_id, peer_label, "received"]) .set(stats.udp_rx.ios as i64); } } @@ -276,7 +278,7 @@ mod tests { let network_3 = build_network().unwrap(); let registry = Registry::new(); - let metrics = Arc::new(QuinnConnectionMetrics::new(®istry)); + let metrics = Arc::new(QuinnConnectionMetrics::new("consensus", ®istry)); // AND we connect to peer 2 let peer_2 = network_1.connect(network_2.local_addr()).await.unwrap(); @@ -288,6 +290,7 @@ mod tests { // WHEN bring up the monitor let handle = AnemoConnectionMonitor::spawn(network_1.downgrade(), metrics.clone(), known_peers); + let connection_statuses = handle.connection_statuses(); // THEN peer 2 should be already connected assert_network_peers(&metrics, 1).await; @@ -296,7 +299,7 @@ mod tests { let mut labels = HashMap::new(); let peer_2_str = format!("{peer_2}"); labels.insert("peer_id", peer_2_str.as_str()); - labels.insert("hostname", "peer_2"); + labels.insert("peer_label", "peer_2"); assert_ne!( metrics .network_peer_rtt @@ -306,7 +309,7 @@ mod tests { 0 ); assert_eq!( - *handle.connection_statuses.get(&peer_2).unwrap().value(), + *connection_statuses.get(&peer_2).unwrap().value(), ConnectionStatus::Connected ); @@ -316,7 +319,7 @@ mod tests { // THEN assert_network_peers(&metrics, 2).await; assert_eq!( - *handle.connection_statuses.get(&peer_3).unwrap().value(), + *connection_statuses.get(&peer_3).unwrap().value(), ConnectionStatus::Connected ); @@ -326,7 +329,7 @@ mod tests { // THEN assert_network_peers(&metrics, 1).await; assert_eq!( - *handle.connection_statuses.get(&peer_2).unwrap().value(), + *connection_statuses.get(&peer_2).unwrap().value(), ConnectionStatus::Disconnected ); @@ -336,7 +339,7 @@ mod tests { // THEN assert_network_peers(&metrics, 0).await; assert_eq!( - *handle.connection_statuses.get(&peer_3).unwrap().value(), + *connection_statuses.get(&peer_3).unwrap().value(), ConnectionStatus::Disconnected ); } diff --git a/consensus/core/src/network/metrics.rs b/consensus/core/src/network/metrics.rs index 841eb3906f348..dd9c5e63171a2 100644 --- a/consensus/core/src/network/metrics.rs +++ b/consensus/core/src/network/metrics.rs @@ -3,11 +3,13 @@ use std::sync::Arc; +use anemo_tower::callback::{MakeCallbackHandler, ResponseHandler}; use prometheus::{ register_histogram_vec_with_registry, register_int_counter_vec_with_registry, - register_int_gauge_vec_with_registry, register_int_gauge_with_registry, HistogramVec, - IntCounterVec, IntGauge, IntGaugeVec, Registry, + register_int_gauge_vec_with_registry, register_int_gauge_with_registry, HistogramTimer, + HistogramVec, IntCounterVec, IntGauge, IntGaugeVec, Registry, }; +use tracing::warn; // Fields for network-agnostic metrics can be added here pub(crate) struct NetworkMetrics { @@ -29,10 +31,10 @@ impl NetworkMetrics { registry ) .unwrap(), - inbound: Arc::new(NetworkRouteMetrics::new("inbound", registry)), - outbound: Arc::new(NetworkRouteMetrics::new("outbound", registry)), + inbound: Arc::new(NetworkRouteMetrics::new("", "inbound", registry)), + outbound: Arc::new(NetworkRouteMetrics::new("", "outbound", registry)), tcp_connection_metrics: Arc::new(TcpConnectionMetrics::new(registry)), - quinn_connection_metrics: Arc::new(QuinnConnectionMetrics::new(registry)), + quinn_connection_metrics: Arc::new(QuinnConnectionMetrics::new("", registry)), } } } @@ -80,7 +82,7 @@ impl TcpConnectionMetrics { } } -pub(crate) struct QuinnConnectionMetrics { +pub struct QuinnConnectionMetrics { /// The connection status of known peers. 0 if not connected, 1 if connected. pub network_peer_connected: IntGaugeVec, /// The number of connected peers @@ -124,36 +126,36 @@ pub(crate) struct QuinnConnectionMetrics { } impl QuinnConnectionMetrics { - pub fn new(registry: &Registry) -> Self { + pub fn new(node: &'static str, registry: &Registry) -> Self { Self { network_peer_connected: register_int_gauge_vec_with_registry!( - "quinn_network_peer_connected", + format!("{node}_quinn_network_peer_connected"), "The connection status of a peer. 0 if not connected, 1 if connected", - &["peer_id", "hostname"], + &["peer_id", "peer_label"], registry ) .unwrap(), network_peers: register_int_gauge_with_registry!( - "quinn_network_peers", + format!("{node}_quinn_network_peers"), "The number of connected peers.", registry ) .unwrap(), network_peer_disconnects: register_int_counter_vec_with_registry!( - "quinn_network_peer_disconnects", + format!("{node}_quinn_network_peer_disconnects"), "Number of disconnect events per peer.", - &["peer_id", "hostname", "reason"], + &["peer_id", "peer_label", "reason"], registry ) .unwrap(), socket_receive_buffer_size: register_int_gauge_with_registry!( - "quinn_socket_receive_buffer_size", + format!("{node}_quinn_socket_receive_buffer_size"), "Receive buffer size of Anemo socket.", registry ) .unwrap(), socket_send_buffer_size: register_int_gauge_with_registry!( - "quinn_socket_send_buffer_size", + format!("{node}_quinn_socket_send_buffer_size"), "Send buffer size of Anemo socket.", registry ) @@ -161,90 +163,90 @@ impl QuinnConnectionMetrics { // PathStats network_peer_rtt: register_int_gauge_vec_with_registry!( - "quinn_network_peer_rtt", + format!("{node}_quinn_network_peer_rtt"), "The rtt for a peer connection in ms.", - &["peer_id", "hostname"], + &["peer_id", "peer_label"], registry ) .unwrap(), network_peer_lost_packets: register_int_gauge_vec_with_registry!( - "quinn_network_peer_lost_packets", + format!("{node}_quinn_network_peer_lost_packets"), "The total number of lost packets for a peer connection.", - &["peer_id", "hostname"], + &["peer_id", "peer_label"], registry ) .unwrap(), network_peer_lost_bytes: register_int_gauge_vec_with_registry!( - "quinn_network_peer_lost_bytes", + format!("{node}_quinn_network_peer_lost_bytes"), "The total number of lost bytes for a peer connection.", - &["peer_id", "hostname"], + &["peer_id", "peer_label"], registry ) .unwrap(), network_peer_sent_packets: register_int_gauge_vec_with_registry!( - "quinn_network_peer_sent_packets", + format!("{node}_quinn_network_peer_sent_packets"), "The total number of sent packets for a peer connection.", - &["peer_id", "hostname"], + &["peer_id", "peer_label"], registry ) .unwrap(), network_peer_congestion_events: register_int_gauge_vec_with_registry!( - "quinn_network_peer_congestion_events", + format!("{node}_quinn_network_peer_congestion_events"), "The total number of congestion events for a peer connection.", - &["peer_id", "hostname"], + &["peer_id", "peer_label"], registry ) .unwrap(), network_peer_congestion_window: register_int_gauge_vec_with_registry!( - "quinn_network_peer_congestion_window", + format!("{node}_quinn_network_peer_congestion_window"), "The congestion window for a peer connection.", - &["peer_id", "hostname"], + &["peer_id", "peer_label"], registry ) .unwrap(), // FrameStats network_peer_closed_connections: register_int_gauge_vec_with_registry!( - "quinn_network_peer_closed_connections", + format!("{node}_quinn_network_peer_closed_connections"), "The number of closed connections for a peer connection.", - &["peer_id", "hostname", "direction"], + &["peer_id", "peer_label", "direction"], registry ) .unwrap(), network_peer_max_data: register_int_gauge_vec_with_registry!( - "quinn_network_peer_max_data", + format!("{node}_quinn_network_peer_max_data"), "The number of max data frames for a peer connection.", - &["peer_id", "hostname", "direction"], + &["peer_id", "peer_label", "direction"], registry ) .unwrap(), network_peer_data_blocked: register_int_gauge_vec_with_registry!( - "quinn_network_peer_data_blocked", + format!("{node}_quinn_network_peer_data_blocked"), "The number of data blocked frames for a peer connection.", - &["peer_id", "hostname", "direction"], + &["peer_id", "peer_label", "direction"], registry ) .unwrap(), // UDPStats network_peer_udp_datagrams: register_int_gauge_vec_with_registry!( - "quinn_network_peer_udp_datagrams", + format!("{node}_quinn_network_peer_udp_datagrams"), "The total number datagrams observed by the UDP peer connection.", - &["peer_id", "hostname", "direction"], + &["peer_id", "peer_label", "direction"], registry ) .unwrap(), network_peer_udp_bytes: register_int_gauge_vec_with_registry!( - "quinn_network_peer_udp_bytes", + format!("{node}_quinn_network_peer_udp_bytes"), "The total number bytes observed by the UDP peer connection.", - &["peer_id", "hostname", "direction"], + &["peer_id", "peer_label", "direction"], registry ) .unwrap(), network_peer_udp_transmits: register_int_gauge_vec_with_registry!( - "quinn_network_peer_udp_transmits", + format!("{node}_quinn_network_peer_udp_transmits"), "The total number transmits observed by the UDP peer connection.", - &["peer_id", "hostname", "direction"], + &["peer_id", "peer_label", "direction"], registry ) .unwrap(), @@ -253,7 +255,7 @@ impl QuinnConnectionMetrics { } #[derive(Clone)] -pub(crate) struct NetworkRouteMetrics { +pub struct NetworkRouteMetrics { /// Counter of requests by route pub requests: IntCounterVec, /// Request latency by route @@ -287,9 +289,9 @@ const SIZE_BYTE_BUCKETS: &[f64] = &[ ]; impl NetworkRouteMetrics { - pub fn new(direction: &'static str, registry: &Registry) -> Self { + pub fn new(node: &'static str, direction: &'static str, registry: &Registry) -> Self { let requests = register_int_counter_vec_with_registry!( - format!("{direction}_requests"), + format!("{node}_{direction}_requests"), "The number of requests made on the network", &["route"], registry @@ -297,7 +299,7 @@ impl NetworkRouteMetrics { .unwrap(); let request_latency = register_histogram_vec_with_registry!( - format!("{direction}_request_latency"), + format!("{node}_{direction}_request_latency"), "Latency of a request by route", &["route"], LATENCY_SEC_BUCKETS.to_vec(), @@ -306,7 +308,7 @@ impl NetworkRouteMetrics { .unwrap(); let request_size = register_histogram_vec_with_registry!( - format!("{direction}_request_size"), + format!("{node}_{direction}_request_size"), "Size of a request by route", &["route"], SIZE_BYTE_BUCKETS.to_vec(), @@ -315,7 +317,7 @@ impl NetworkRouteMetrics { .unwrap(); let response_size = register_histogram_vec_with_registry!( - format!("{direction}_response_size"), + format!("{node}_{direction}_response_size"), "Size of a response by route", &["route"], SIZE_BYTE_BUCKETS.to_vec(), @@ -324,7 +326,7 @@ impl NetworkRouteMetrics { .unwrap(); let excessive_size_requests = register_int_counter_vec_with_registry!( - format!("{direction}_excessive_size_requests"), + format!("{node}_{direction}_excessive_size_requests"), "The number of excessively large request messages sent", &["route"], registry @@ -332,7 +334,7 @@ impl NetworkRouteMetrics { .unwrap(); let excessive_size_responses = register_int_counter_vec_with_registry!( - format!("{direction}_excessive_size_responses"), + format!("{node}_{direction}_excessive_size_responses"), "The number of excessively large response messages seen", &["route"], registry @@ -340,7 +342,7 @@ impl NetworkRouteMetrics { .unwrap(); let inflight_requests = register_int_gauge_vec_with_registry!( - format!("{direction}_inflight_requests"), + format!("{node}_{direction}_inflight_requests"), "The number of inflight network requests", &["route"], registry @@ -348,7 +350,7 @@ impl NetworkRouteMetrics { .unwrap(); let errors = register_int_counter_vec_with_registry!( - format!("{direction}_request_errors"), + format!("{node}_{direction}_request_errors"), "Number of errors by route", &["route", "status"], registry, @@ -367,3 +369,115 @@ impl NetworkRouteMetrics { } } } + +#[derive(Clone)] +pub struct MetricsMakeCallbackHandler { + metrics: Arc, + /// Size in bytes above which a request or response message is considered excessively large + excessive_message_size: usize, +} + +impl MetricsMakeCallbackHandler { + pub fn new(metrics: Arc, excessive_message_size: usize) -> Self { + Self { + metrics, + excessive_message_size, + } + } +} + +impl MakeCallbackHandler for MetricsMakeCallbackHandler { + type Handler = MetricsResponseHandler; + + fn make_handler(&self, request: &anemo::Request) -> Self::Handler { + let route = request.route().to_owned(); + + self.metrics.requests.with_label_values(&[&route]).inc(); + self.metrics + .inflight_requests + .with_label_values(&[&route]) + .inc(); + let body_len = request.body().len(); + self.metrics + .request_size + .with_label_values(&[&route]) + .observe(body_len as f64); + if body_len > self.excessive_message_size { + warn!( + "Saw excessively large request with size {body_len} for {route} with peer {:?}", + request.peer_id() + ); + self.metrics + .excessive_size_requests + .with_label_values(&[&route]) + .inc(); + } + + let timer = self + .metrics + .request_latency + .with_label_values(&[&route]) + .start_timer(); + + MetricsResponseHandler { + metrics: self.metrics.clone(), + timer, + route, + excessive_message_size: self.excessive_message_size, + } + } +} + +pub struct MetricsResponseHandler { + metrics: Arc, + // The timer is held on to and "observed" once dropped + #[allow(unused)] + timer: HistogramTimer, + route: String, + excessive_message_size: usize, +} + +impl ResponseHandler for MetricsResponseHandler { + fn on_response(self, response: &anemo::Response) { + let body_len = response.body().len(); + self.metrics + .response_size + .with_label_values(&[&self.route]) + .observe(body_len as f64); + if body_len > self.excessive_message_size { + warn!( + "Saw excessively large response with size {body_len} for {} with peer {:?}", + self.route, + response.peer_id() + ); + self.metrics + .excessive_size_responses + .with_label_values(&[&self.route]) + .inc(); + } + + if !response.status().is_success() { + let status = response.status().to_u16().to_string(); + self.metrics + .errors + .with_label_values(&[&self.route, &status]) + .inc(); + } + } + + fn on_error(self, _error: &E) { + self.metrics + .errors + .with_label_values(&[&self.route, "unknown"]) + .inc(); + } +} + +impl Drop for MetricsResponseHandler { + fn drop(&mut self) { + self.metrics + .inflight_requests + .with_label_values(&[&self.route]) + .dec(); + } +} diff --git a/consensus/core/src/network/mod.rs b/consensus/core/src/network/mod.rs index a533222353204..86477d972c22c 100644 --- a/consensus/core/src/network/mod.rs +++ b/consensus/core/src/network/mod.rs @@ -41,8 +41,9 @@ mod tonic_gen { include!(concat!(env!("OUT_DIR"), "/consensus.ConsensusService.rs")); } +pub mod connection_monitor; + pub(crate) mod anemo_network; -pub(crate) mod connection_monitor; pub(crate) mod epoch_filter; pub(crate) mod metrics; mod metrics_layer; diff --git a/consensus/core/src/network/tonic_network.rs b/consensus/core/src/network/tonic_network.rs index 701f93ef33e83..185f59786b163 100644 --- a/consensus/core/src/network/tonic_network.rs +++ b/consensus/core/src/network/tonic_network.rs @@ -14,7 +14,7 @@ use bytes::Bytes; use cfg_if::cfg_if; use consensus_config::{AuthorityIndex, NetworkKeyPair, NetworkPublicKey}; use futures::{stream, Stream, StreamExt as _}; -use hyper_util::rt::tokio::TokioIo; +use hyper_util::rt::{tokio::TokioIo, TokioTimer}; use hyper_util::service::TowerToHyperService; use mysten_common::sync::notify_once::NotifyOnce; use mysten_metrics::monitored_future; @@ -31,7 +31,7 @@ use tokio::{ }; use tokio_rustls::TlsAcceptor; use tokio_stream::{iter, Iter}; -use tonic::{transport::Server, Request, Response, Streaming}; +use tonic::{Request, Response, Streaming}; use tower_http::{ trace::{DefaultMakeSpan, DefaultOnFailure, TraceLayer}, ServiceBuilderExt, @@ -710,31 +710,30 @@ impl NetworkManager for TonicManager { let service = TonicServiceProxy::new(self.context.clone(), service); let config = &self.context.parameters.tonic; - let consensus_service = Server::builder() - .layer( - TraceLayer::new_for_grpc() - .make_span_with(DefaultMakeSpan::new().level(tracing::Level::TRACE)) - .on_failure(DefaultOnFailure::new().level(tracing::Level::DEBUG)), - ) - .initial_connection_window_size(64 << 20) - .initial_stream_window_size(32 << 20) - .http2_keepalive_interval(Some(config.keepalive_interval)) - .http2_keepalive_timeout(Some(config.keepalive_interval)) - // tcp keepalive is unsupported by msim - .add_service( - ConsensusServiceServer::new(service) - .max_encoding_message_size(config.message_size_limit) - .max_decoding_message_size(config.message_size_limit), - ) - .into_router(); + let consensus_service = tonic::service::Routes::new( + ConsensusServiceServer::new(service) + .max_encoding_message_size(config.message_size_limit) + .max_decoding_message_size(config.message_size_limit), + ) + .into_axum_router(); let inbound_metrics = self.context.metrics.network_metrics.inbound.clone(); let excessive_message_size = self.context.parameters.tonic.excessive_message_size; - let http = - hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new()) - .http2_only(); - let http = Arc::new(http); + let http = { + let mut builder = + hyper_util::server::conn::auto::Builder::new(hyper_util::rt::TokioExecutor::new()) + .http2_only(); + builder + .http2() + .timer(TokioTimer::new()) + .initial_connection_window_size(64 << 20) + .initial_stream_window_size(32 << 20) + .keep_alive_interval(Some(config.keepalive_interval)) + .keep_alive_timeout(config.keepalive_interval); + + Arc::new(builder) + }; let tls_server_config = create_rustls_server_config(&self.context, self.network_keypair.clone()); @@ -830,7 +829,7 @@ impl NetworkManager for TonicManager { match result { Ok(Ok(())) => {}, Ok(Err(e)) => { - warn!("Error serving connection: {e:?}"); + debug!("Error serving connection: {e:?}"); }, Err(e) => { debug!("Connection task error, likely shutting down: {e:?}"); @@ -907,7 +906,12 @@ impl NetworkManager for TonicManager { inbound_metrics, excessive_message_size, ))) - .service(consensus_service.clone()); + .layer( + TraceLayer::new_for_grpc() + .make_span_with(DefaultMakeSpan::new().level(tracing::Level::TRACE)) + .on_failure(DefaultOnFailure::new().level(tracing::Level::DEBUG)), + ) + .service(consensus_service); pin! { let connection = http.serve_connection(TokioIo::new(tls_stream), TowerToHyperService::new(svc)); diff --git a/consensus/core/src/network/tonic_tls.rs b/consensus/core/src/network/tonic_tls.rs index 88a40f88c4103..13377934e3b18 100644 --- a/consensus/core/src/network/tonic_tls.rs +++ b/consensus/core/src/network/tonic_tls.rs @@ -1,19 +1,22 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::collections::BTreeSet; - +use crate::context::Context; use consensus_config::{AuthorityIndex, NetworkKeyPair}; -use fastcrypto::ed25519::Ed25519PublicKey; +use sui_tls::AllowPublicKeys; use tokio_rustls::rustls::{ClientConfig, ServerConfig}; -use crate::context::Context; - pub(crate) fn create_rustls_server_config( context: &Context, network_keypair: NetworkKeyPair, ) -> ServerConfig { - let allower = AllowedPublicKeys::new(context); + let allower = AllowPublicKeys::new( + context + .committee + .authorities() + .map(|(_i, a)| a.network_key.clone().into_inner()) + .collect(), + ); let verifier = sui_tls::ClientCertVerifier::new(allower, certificate_server_name(context)); // TODO: refactor to use key bytes let self_signed_cert = sui_tls::SelfSignedCertificate::new( @@ -56,30 +59,6 @@ pub(crate) fn create_rustls_client_config( tls_config } -// Checks if the public key from a TLS certificate belongs to one of the validators. -#[derive(Debug)] -struct AllowedPublicKeys { - // TODO: refactor to use key bytes - keys: BTreeSet, -} - -impl AllowedPublicKeys { - fn new(context: &Context) -> Self { - let keys = context - .committee - .authorities() - .map(|(_i, a)| a.network_key.clone().into_inner()) - .collect(); - Self { keys } - } -} - -impl sui_tls::Allower for AllowedPublicKeys { - fn allowed(&self, key: &Ed25519PublicKey) -> bool { - self.keys.contains(key) - } -} - fn certificate_server_name(context: &Context) -> String { format!("consensus_epoch_{}", context.committee.epoch()) } diff --git a/consensus/core/src/round_prober.rs b/consensus/core/src/round_prober.rs index ceaf46979196e..a80ffe148f1f9 100644 --- a/consensus/core/src/round_prober.rs +++ b/consensus/core/src/round_prober.rs @@ -207,6 +207,10 @@ impl RoundProber { .round_prober_quorum_round_gaps .with_label_values(&[&authority.hostname]) .set((high - low) as i64); + node_metrics + .round_prober_low_quorum_round + .with_label_values(&[&authority.hostname]) + .set(*low as i64); // The gap can be negative if this validator is lagging behind the network. node_metrics .round_prober_current_round_gaps @@ -231,7 +235,7 @@ impl RoundProber { .set(propagation_delay as i64); if let Err(e) = self .core_thread_dispatcher - .set_propagation_delay(propagation_delay) + .set_propagation_delay_and_quorum_rounds(propagation_delay, quorum_rounds.clone()) { tracing::warn!( "Failed to set propagation delay {propagation_delay} on Core: {:?}", @@ -293,6 +297,7 @@ mod test { use consensus_config::AuthorityIndex; use parking_lot::{Mutex, RwLock}; + use super::QuorumRound; use crate::{ block::BlockRef, commit::CommitRange, @@ -309,6 +314,7 @@ mod test { struct FakeThreadDispatcher { highest_received_rounds: Vec, propagation_delay: Mutex, + quorum_rounds: Mutex>, } impl FakeThreadDispatcher { @@ -316,12 +322,17 @@ mod test { Self { highest_received_rounds, propagation_delay: Mutex::new(0), + quorum_rounds: Mutex::new(Vec::new()), } } fn propagation_delay(&self) -> Round { *self.propagation_delay.lock() } + + fn quorum_rounds(&self) -> Vec { + self.quorum_rounds.lock().clone() + } } #[async_trait] @@ -345,7 +356,13 @@ mod test { unimplemented!() } - fn set_propagation_delay(&self, delay: Round) -> Result<(), CoreError> { + fn set_propagation_delay_and_quorum_rounds( + &self, + delay: Round, + quorum_rounds: Vec, + ) -> Result<(), CoreError> { + let mut quorum_round_per_authority = self.quorum_rounds.lock(); + *quorum_round_per_authority = quorum_rounds; let mut propagation_delay = self.propagation_delay.lock(); *propagation_delay = delay; Ok(()) @@ -492,6 +509,18 @@ mod test { ] ); + assert_eq!( + core_thread_dispatcher.quorum_rounds(), + vec![ + (100, 105), + (0, 115), + (103, 130), + (0, 0), + (105, 150), + (106, 160), + (107, 170) + ] + ); // 110 - 100 = 10 assert_eq!(propagation_delay, 10); assert_eq!(core_thread_dispatcher.propagation_delay(), 10); diff --git a/consensus/core/src/storage/rocksdb_store.rs b/consensus/core/src/storage/rocksdb_store.rs index edee2b3bb9779..956383b5002a9 100644 --- a/consensus/core/src/storage/rocksdb_store.rs +++ b/consensus/core/src/storage/rocksdb_store.rs @@ -54,10 +54,9 @@ impl RocksDBStore { ( Self::BLOCKS_CF, default_db_options() - .optimize_for_write_throughput() - // Blocks can get large and they don't need to be compacted. - // So keep them in rocksdb blobstore. - .optimize_for_large_values_no_scan(1 << 10) + .optimize_for_write_throughput_no_deletion() + // Using larger block is ok since there is not much point reads on the cf. + .set_block_options(512, 128 << 10) .options, ), (Self::DIGESTS_BY_AUTHORITIES_CF, cf_options.clone()), diff --git a/consensus/core/src/subscriber.rs b/consensus/core/src/subscriber.rs index 08a1da7d43845..19035ebfc347d 100644 --- a/consensus/core/src/subscriber.rs +++ b/consensus/core/src/subscriber.rs @@ -59,11 +59,24 @@ impl Subscriber { let context = self.context.clone(); let network_client = self.network_client.clone(); let authority_service = self.authority_service.clone(); - let last_received = self - .dag_state - .read() - .get_last_block_for_authority(peer) - .round(); + let (mut last_received, gc_round, gc_enabled) = { + let dag_state = self.dag_state.read(); + ( + dag_state.get_last_block_for_authority(peer).round(), + dag_state.gc_round(), + dag_state.gc_enabled(), + ) + }; + + // If the latest block we have accepted by an authority is older than the current gc round, + // then do not attempt to fetch any blocks from that point as they will simply be skipped. Instead + // do attempt to fetch from the gc round. + if gc_enabled && last_received < gc_round { + info!( + "Last received block for peer {peer} is older than GC round, {last_received} < {gc_round}, fetching from GC round" + ); + last_received = gc_round; + } let mut subscriptions = self.subscriptions.lock(); self.unsubscribe_locked(peer, &mut subscriptions[peer.value()]); diff --git a/consensus/core/src/synchronizer.rs b/consensus/core/src/synchronizer.rs index cf97dc8b40559..dbcf447be9b4e 100644 --- a/consensus/core/src/synchronizer.rs +++ b/consensus/core/src/synchronizer.rs @@ -41,12 +41,23 @@ use crate::{ /// The number of concurrent fetch blocks requests per authority const FETCH_BLOCKS_CONCURRENCY: usize = 5; +/// Timeouts when fetching blocks. const FETCH_REQUEST_TIMEOUT: Duration = Duration::from_millis(2_000); - const FETCH_FROM_PEERS_TIMEOUT: Duration = Duration::from_millis(4_000); +/// Max number of blocks to fetch per request. +/// This value should be chosen so even with blocks at max size, the requests +/// can finish on hosts with good network using the timeouts above. +const MAX_BLOCKS_PER_FETCH: usize = 32; + const MAX_AUTHORITIES_TO_FETCH_PER_BLOCK: usize = 2; +/// The number of rounds above the highest accepted round that still willing to fetch missing blocks via the periodic +/// synchronizer. Any missing blocks of higher rounds are considered too far in the future to fetch. This property is taken into +/// account only when it's detected that the node has fallen behind on its commit compared to the rest of the network, otherwise +/// scheduler will attempt to fetch any missing block. +const SYNC_MISSING_BLOCK_ROUND_THRESHOLD: u32 = 50; + struct BlocksGuard { map: Arc, block_refs: BTreeSet, @@ -328,7 +339,7 @@ impl Synchronizer Synchronizer Synchronizer Synchronizer ConsensusResult<()> { - let (commit_lagging, last_commit_index, quorum_commit_index) = self.is_commit_lagging(); - if commit_lagging { - trace!("Scheduled synchronizer temporarily disabled as local commit is falling behind from quorum {last_commit_index} << {quorum_commit_index}"); - self.context - .metrics - .node_metrics - .fetch_blocks_scheduler_skipped - .with_label_values(&["commit_lagging"]) - .inc(); - return Ok(()); - } - - let missing_blocks = self + let mut missing_blocks = self .core_dispatcher .get_missing_blocks() .await @@ -851,6 +853,31 @@ impl Synchronizer>(); + + // If no missing blocks are within the acceptable thresholds to sync while we commit lag, then we disable the scheduler completely for this run. + if missing_blocks.is_empty() { + trace!("Scheduled synchronizer temporarily disabled as local commit is falling behind from quorum {last_commit_index} << {quorum_commit_index} and missing blocks are too far in the future."); + self.context + .metrics + .node_metrics + .fetch_blocks_scheduler_skipped + .with_label_values(&["commit_lagging"]) + .inc(); + return Ok(()); + } + } + self.fetch_blocks_scheduler_task .spawn(monitored_future!(async move { let _scope = monitored_scope("FetchMissingBlocksScheduler"); @@ -910,7 +937,7 @@ impl Synchronizer>(); let mut missing_blocks_per_authority = vec![0; context.committee.size()]; for block in &missing_blocks { @@ -946,7 +973,7 @@ impl Synchronizer, AuthorityIndex); type FetchRequestResponse = (Vec, Option); @@ -1436,7 +1470,8 @@ mod tests { } #[tokio::test(flavor = "current_thread", start_paused = true)] - async fn synchronizer_periodic_task_skip_when_commit_lagging() { + async fn synchronizer_periodic_task_when_commit_lagging_with_missing_blocks_in_acceptable_thresholds( + ) { // GIVEN let (context, _) = Context::new_for_test(4); let context = Arc::new(context); @@ -1447,8 +1482,96 @@ mod tests { let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store))); let commit_vote_monitor = Arc::new(CommitVoteMonitor::new(context.clone())); - // AND stub some missing blocks - let expected_blocks = (0..10) + // AND stub some missing blocks. The highest accepted round is 0. Create some blocks that are below and above the threshold sync. + let expected_blocks = (0..SYNC_MISSING_BLOCK_ROUND_THRESHOLD * 2) + .map(|round| VerifiedBlock::new_for_test(TestBlock::new(round, 0).build())) + .collect::>(); + + let missing_blocks = expected_blocks + .iter() + .map(|block| block.reference()) + .collect::>(); + core_dispatcher.stub_missing_blocks(missing_blocks).await; + + // AND stub the requests for authority 1 & 2 + // Make the first authority timeout, so the second will be called. "We" are authority = 0, so + // we are skipped anyways. + let mut expected_blocks = expected_blocks + .into_iter() + .filter(|block| block.round() <= SYNC_MISSING_BLOCK_ROUND_THRESHOLD) + .collect::>(); + + for chunk in expected_blocks.chunks(MAX_BLOCKS_PER_FETCH) { + network_client + .stub_fetch_blocks( + chunk.to_vec(), + AuthorityIndex::new_for_test(1), + Some(FETCH_REQUEST_TIMEOUT), + ) + .await; + + network_client + .stub_fetch_blocks(chunk.to_vec(), AuthorityIndex::new_for_test(2), None) + .await; + } + + // Now create some blocks to simulate a commit lag + let round = context.parameters.commit_sync_batch_size * COMMIT_LAG_MULTIPLIER * 2; + let commit_index: CommitIndex = round - 1; + let blocks = (0..4) + .map(|authority| { + let commit_votes = vec![CommitVote::new(commit_index, CommitDigest::MIN)]; + let block = TestBlock::new(round, authority) + .set_commit_votes(commit_votes) + .build(); + + VerifiedBlock::new_for_test(block) + }) + .collect::>(); + + // Pass them through the commit vote monitor - so now there will be a big commit lag to prevent + // the scheduled synchronizer from running + for block in blocks { + commit_vote_monitor.observe_block(&block); + } + + // WHEN start the synchronizer and wait for a couple of seconds where normally the synchronizer should have kicked in. + let _handle = Synchronizer::start( + network_client.clone(), + context.clone(), + core_dispatcher.clone(), + commit_vote_monitor.clone(), + block_verifier.clone(), + dag_state.clone(), + false, + ); + + sleep(4 * FETCH_REQUEST_TIMEOUT).await; + + // We should be in commit lag mode, but since there are missing blocks within the acceptable round thresholds those ones should be fetched. Nothing above. + let mut added_blocks = core_dispatcher.get_add_blocks().await; + + added_blocks.sort_by_key(|block| block.reference()); + expected_blocks.sort_by_key(|block| block.reference()); + + assert_eq!(added_blocks, expected_blocks); + } + + #[tokio::test(flavor = "current_thread", start_paused = true)] + async fn synchronizer_periodic_task_when_commit_lagging_gets_disabled() { + // GIVEN + let (context, _) = Context::new_for_test(4); + let context = Arc::new(context); + let block_verifier = Arc::new(NoopBlockVerifier {}); + let core_dispatcher = Arc::new(MockCoreThreadDispatcher::default()); + let network_client = Arc::new(MockNetworkClient::default()); + let store = Arc::new(MemStore::new()); + let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store))); + let commit_vote_monitor = Arc::new(CommitVoteMonitor::new(context.clone())); + + // AND stub some missing blocks. The highest accepted round is 0. Create blocks that are above the threshold sync. + let mut expected_blocks = (SYNC_MISSING_BLOCK_ROUND_THRESHOLD * 2 + ..SYNC_MISSING_BLOCK_ROUND_THRESHOLD * 3) .map(|round| VerifiedBlock::new_for_test(TestBlock::new(round, 0).build())) .collect::>(); let missing_blocks = expected_blocks @@ -1462,20 +1585,18 @@ mod tests { // AND stub the requests for authority 1 & 2 // Make the first authority timeout, so the second will be called. "We" are authority = 0, so // we are skipped anyways. - network_client - .stub_fetch_blocks( - expected_blocks.clone(), - AuthorityIndex::new_for_test(1), - Some(FETCH_REQUEST_TIMEOUT), - ) - .await; - network_client - .stub_fetch_blocks( - expected_blocks.clone(), - AuthorityIndex::new_for_test(2), - None, - ) - .await; + for chunk in expected_blocks.chunks(MAX_BLOCKS_PER_FETCH) { + network_client + .stub_fetch_blocks( + chunk.to_vec(), + AuthorityIndex::new_for_test(1), + Some(FETCH_REQUEST_TIMEOUT), + ) + .await; + network_client + .stub_fetch_blocks(chunk.to_vec(), AuthorityIndex::new_for_test(2), None) + .await; + } // Now create some blocks to simulate a commit lag let round = context.parameters.commit_sync_batch_size * COMMIT_LAG_MULTIPLIER * 2; @@ -1532,10 +1653,19 @@ mod tests { ); } + // Now stub again the missing blocks to fetch the exact same ones. + core_dispatcher + .stub_missing_blocks(missing_blocks.clone()) + .await; + sleep(2 * FETCH_REQUEST_TIMEOUT).await; // THEN the missing blocks should now be fetched and added to core - let added_blocks = core_dispatcher.get_add_blocks().await; + let mut added_blocks = core_dispatcher.get_add_blocks().await; + + added_blocks.sort_by_key(|block| block.reference()); + expected_blocks.sort_by_key(|block| block.reference()); + assert_eq!(added_blocks, expected_blocks); } diff --git a/consensus/core/src/test_dag_builder.rs b/consensus/core/src/test_dag_builder.rs index d669ed50d42f3..f6816ab1befdf 100644 --- a/consensus/core/src/test_dag_builder.rs +++ b/consensus/core/src/test_dag_builder.rs @@ -16,10 +16,11 @@ use crate::{ genesis_blocks, BlockAPI, BlockDigest, BlockRef, BlockTimestampMs, Round, Slot, TestBlock, VerifiedBlock, }, - commit::{sort_sub_dag_blocks, CommitDigest, TrustedCommit, DEFAULT_WAVE_LENGTH}, + commit::{CommitDigest, TrustedCommit, DEFAULT_WAVE_LENGTH}, context::Context, dag_state::DagState, leader_schedule::{LeaderSchedule, LeaderSwapTable}, + linearizer::{BlockStoreAPI, Linearizer}, CommittedSubDag, }; @@ -83,6 +84,9 @@ pub(crate) struct DagBuilder { // All blocks created by dag builder. Will be used to pretty print or to be // retrieved for testing/persiting to dag state. pub(crate) blocks: BTreeMap, + // All the committed sub dags created by the dag builder. + pub(crate) committed_sub_dags: Vec<(CommittedSubDag, TrustedCommit)>, + pub(crate) last_committed_rounds: Vec, wave_length: Round, number_of_leaders: u32, @@ -100,6 +104,7 @@ impl DagBuilder { .collect(); let last_ancestors = genesis.keys().cloned().collect(); Self { + last_committed_rounds: vec![0; context.committee.size()], context, leader_schedule, wave_length: DEFAULT_WAVE_LENGTH, @@ -108,6 +113,7 @@ impl DagBuilder { genesis, last_ancestors, blocks: BTreeMap::new(), + committed_sub_dags: vec![], } } @@ -123,64 +129,108 @@ impl DagBuilder { .collect::>() } - // TODO: reuse logic from Linearizer. - pub(crate) fn get_sub_dag_and_commit( - &self, - leader_block: VerifiedBlock, - last_committed_rounds: Vec, - commit_index: u32, - ) -> (CommittedSubDag, TrustedCommit) { - let mut to_commit = Vec::new(); - let mut committed = HashSet::new(); - - let timestamp_ms = leader_block.timestamp_ms(); - let leader_block_ref = leader_block.reference(); - let mut buffer = vec![leader_block]; - assert!(committed.insert(leader_block_ref)); - while let Some(x) = buffer.pop() { - to_commit.push(x.clone()); - - let ancestors = self.get_blocks( - &x.ancestors() - .iter() - .copied() - .filter(|ancestor| { - // We skip the block if we already committed it or we reached a - // round that we already committed. - !committed.contains(ancestor) - && last_committed_rounds[ancestor.author] < ancestor.round - }) - .collect::>(), + pub(crate) fn all_blocks(&self) -> Vec { + assert!( + !self.blocks.is_empty(), + "No blocks have been created, please make sure that you have called build method" + ); + self.blocks.values().cloned().collect() + } + + pub(crate) fn get_sub_dag_and_commits( + &mut self, + leader_rounds: RangeInclusive, + ) -> Vec<(CommittedSubDag, TrustedCommit)> { + let (last_leader_round, mut last_commit_index, mut last_timestamp_ms) = + if let Some((sub_dag, _)) = self.committed_sub_dags.last() { + ( + sub_dag.leader.round, + sub_dag.commit_ref.index, + sub_dag.timestamp_ms, + ) + } else { + (0, 0, 0) + }; + + // Create any remaining committed sub dags + for leader_block in self + .leader_blocks(last_leader_round + 1..=*leader_rounds.end()) + .into_iter() + .flatten() + { + let leader_block_ref = leader_block.reference(); + last_commit_index += 1; + last_timestamp_ms = leader_block.timestamp_ms().max(last_timestamp_ms); + + struct FooStorage { + gc_round: Round, + context: Arc, + blocks: BTreeMap, + } + impl BlockStoreAPI for FooStorage { + fn get_blocks(&self, refs: &[BlockRef]) -> Vec> { + refs.iter() + .map(|block_ref| self.blocks.get(block_ref).cloned()) + .collect() + } + + fn gc_round(&self) -> Round { + self.gc_round + } + + fn gc_enabled(&self) -> bool { + self.context.protocol_config.gc_depth() > 0 + } + } + let storage = FooStorage { + context: self.context.clone(), + blocks: self.blocks.clone(), + gc_round: leader_block + .round() + .saturating_sub(1) + .saturating_sub(self.context.protocol_config.gc_depth()), + }; + + let (to_commit, rejected_transactions) = Linearizer::linearize_sub_dag( + leader_block, + self.last_committed_rounds.clone(), + storage, ); - for ancestor in ancestors { - buffer.push(ancestor.clone()); - assert!(committed.insert(ancestor.reference())); + // Update the last committed rounds + for block in &to_commit { + self.last_committed_rounds[block.author()] = + self.last_committed_rounds[block.author()].max(block.round()); } - } - sort_sub_dag_blocks(&mut to_commit); - - let commit = TrustedCommit::new_for_test( - commit_index, - CommitDigest::MIN, - timestamp_ms, - leader_block_ref, - to_commit - .iter() - .map(|block| block.reference()) - .collect::>(), - ); + let commit = TrustedCommit::new_for_test( + last_commit_index, + CommitDigest::MIN, + last_timestamp_ms, + leader_block_ref, + to_commit + .iter() + .map(|block| block.reference()) + .collect::>(), + ); - let sub_dag = CommittedSubDag::new( - leader_block_ref, - to_commit, - timestamp_ms, - commit.reference(), - vec![], - ); + let sub_dag = CommittedSubDag::new( + leader_block_ref, + to_commit, + rejected_transactions, + last_timestamp_ms, + commit.reference(), + vec![], + ); - (sub_dag, commit) + self.committed_sub_dags.push((sub_dag, commit)); + } + + self.committed_sub_dags + .clone() + .into_iter() + .filter(|(sub_dag, _)| leader_rounds.contains(&sub_dag.leader.round)) + .collect() } pub(crate) fn leader_blocks( diff --git a/crates/mysten-common/src/lib.rs b/crates/mysten-common/src/lib.rs index 7ed00de71fbc4..b3921c9f4b77b 100644 --- a/crates/mysten-common/src/lib.rs +++ b/crates/mysten-common/src/lib.rs @@ -1,5 +1,6 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +pub mod logging; pub mod metrics; pub mod sync; diff --git a/crates/mysten-common/src/logging.rs b/crates/mysten-common/src/logging.rs new file mode 100644 index 0000000000000..8ba327026953b --- /dev/null +++ b/crates/mysten-common/src/logging.rs @@ -0,0 +1,47 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[macro_export] +macro_rules! fatal { + ($($arg:tt)*) => {{ + tracing::error!(fatal = true, $($arg)*); + panic!($($arg)*); + }}; +} + +#[macro_export] +macro_rules! debug_fatal { + ($($arg:tt)*) => {{ + if cfg!(debug_assertions) { + $crate::fatal!($($arg)*); + } else { + // TODO: Export invariant metric for alerting + tracing::error!(debug_fatal = true, $($arg)*); + } + }}; +} + +mod tests { + #[test] + #[should_panic] + fn test_fatal() { + fatal!("This is a fatal error"); + } + + #[test] + #[should_panic] + fn test_debug_fatal() { + if cfg!(debug_assertions) { + debug_fatal!("This is a debug fatal error"); + } else { + // pass in release mode as well + fatal!("This is a fatal error"); + } + } + + #[cfg(not(debug_assertions))] + #[test] + fn test_debug_fatal_release_mode() { + debug_fatal!("This is a debug fatal error"); + } +} diff --git a/crates/mysten-common/src/metrics.rs b/crates/mysten-common/src/metrics.rs index d4f74ef20967f..66ef04586b6df 100644 --- a/crates/mysten-common/src/metrics.rs +++ b/crates/mysten-common/src/metrics.rs @@ -6,7 +6,7 @@ use prometheus::Encoder; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tracing::{debug, error, info}; -const DEFAULT_METRICS_PUSH_TIMEOUT: Duration = Duration::from_secs(30); +const METRICS_PUSH_TIMEOUT: Duration = Duration::from_secs(45); pub struct MetricsPushClient { certificate: std::sync::Arc, @@ -77,7 +77,7 @@ pub async fn push_metrics( .header(reqwest::header::CONTENT_ENCODING, "snappy") .header(reqwest::header::CONTENT_TYPE, prometheus::PROTOBUF_FORMAT) .body(compressed) - .timeout(DEFAULT_METRICS_PUSH_TIMEOUT) + .timeout(METRICS_PUSH_TIMEOUT) .send() .await?; diff --git a/crates/mysten-metrics/src/metered_channel.rs b/crates/mysten-metrics/src/metered_channel.rs index b91403cd789be..3a784bb552d63 100644 --- a/crates/mysten-metrics/src/metered_channel.rs +++ b/crates/mysten-metrics/src/metered_channel.rs @@ -99,22 +99,20 @@ impl Receiver { /// Attempts to receive the next value for this receiver. /// Decrements the gauge in case of a successful `try_recv`. pub fn try_recv(&mut self) -> Result { - self.inner.try_recv().map(|val| { + self.inner.try_recv().inspect(|_| { self.gauge.dec(); if let Some(total_gauge) = &self.total { total_gauge.inc(); } - val }) } pub fn blocking_recv(&mut self) -> Option { - self.inner.blocking_recv().map(|val| { + self.inner.blocking_recv().inspect(|_| { self.gauge.dec(); if let Some(total_gauge) = &self.total { total_gauge.inc(); } - val }) } @@ -194,9 +192,8 @@ impl Sender { self.inner .try_send(message) // remove this unsightly hack once https://github.com/rust-lang/rust/issues/91345 is resolved - .map(|val| { + .inspect(|_| { self.gauge.inc(); - val }) } diff --git a/crates/mysten-metrics/src/monitored_mpsc.rs b/crates/mysten-metrics/src/monitored_mpsc.rs index a30a68f52af7e..188b5cde93286 100644 --- a/crates/mysten-metrics/src/monitored_mpsc.rs +++ b/crates/mysten-metrics/src/monitored_mpsc.rs @@ -252,26 +252,24 @@ impl Receiver { /// Attempts to receive the next value for this receiver. /// Decrements the gauge in case of a successful `try_recv`. pub fn try_recv(&mut self) -> Result { - self.inner.try_recv().map(|val| { + self.inner.try_recv().inspect(|_| { if let Some(inflight) = &self.inflight { inflight.dec(); } if let Some(received) = &self.received { received.inc(); } - val }) } pub fn blocking_recv(&mut self) -> Option { - self.inner.blocking_recv().map(|val| { + self.inner.blocking_recv().inspect(|_| { if let Some(inflight) = &self.inflight { inflight.dec(); } if let Some(received) = &self.received { received.inc(); } - val }) } @@ -451,26 +449,24 @@ impl UnboundedReceiver { /// Attempts to receive the next value for this receiver. /// Decrements the gauge in case of a successful `try_recv`. pub fn try_recv(&mut self) -> Result { - self.inner.try_recv().map(|val| { + self.inner.try_recv().inspect(|_| { if let Some(inflight) = &self.inflight { inflight.dec(); } if let Some(received) = &self.received { received.inc(); } - val }) } pub fn blocking_recv(&mut self) -> Option { - self.inner.blocking_recv().map(|val| { + self.inner.blocking_recv().inspect(|_| { if let Some(inflight) = &self.inflight { inflight.dec(); } if let Some(received) = &self.received { received.inc(); } - val }) } diff --git a/crates/shared-crypto/src/intent.rs b/crates/shared-crypto/src/intent.rs index 959b7d9aa16ae..afc871047d012 100644 --- a/crates/shared-crypto/src/intent.rs +++ b/crates/shared-crypto/src/intent.rs @@ -66,7 +66,8 @@ pub enum IntentScope { ProofOfPossession = 5, // Used as a signature representing an authority's proof of possession of its authority protocol key. HeaderDigest = 6, // Used for narwhal authority signature on header digest. BridgeEventUnused = 7, // for bridge purposes but it's currently not included in messages. - ConsensusBlock = 8, // Used for consensus authority signature on block's digest + ConsensusBlock = 8, // Used for consensus authority signature on block's digest. + DiscoveryPeers = 9, // Used for reporting peer addresses in discovery. } impl TryFrom for IntentScope { diff --git a/crates/sui-analytics-indexer/src/analytics_processor.rs b/crates/sui-analytics-indexer/src/analytics_processor.rs index 04089590ce5aa..1e55981dabafd 100644 --- a/crates/sui-analytics-indexer/src/analytics_processor.rs +++ b/crates/sui-analytics-indexer/src/analytics_processor.rs @@ -53,6 +53,7 @@ const CHECK_FILE_SIZE_ITERATION_CYCLE: u64 = 50; #[async_trait::async_trait] impl Worker for AnalyticsProcessor { + type Result = (); async fn process_checkpoint(&self, checkpoint_data: &CheckpointData) -> Result<()> { // get epoch id, checkpoint sequence number and timestamp, those are important // indexes when operating on data diff --git a/crates/sui-analytics-indexer/src/handlers/checkpoint_handler.rs b/crates/sui-analytics-indexer/src/handlers/checkpoint_handler.rs index 563c12ce8236e..b3dd2042637d9 100644 --- a/crates/sui-analytics-indexer/src/handlers/checkpoint_handler.rs +++ b/crates/sui-analytics-indexer/src/handlers/checkpoint_handler.rs @@ -25,6 +25,8 @@ struct State { #[async_trait::async_trait] impl Worker for CheckpointHandler { + type Result = (); + async fn process_checkpoint(&self, checkpoint_data: &CheckpointData) -> Result<()> { let CheckpointData { checkpoint_summary, diff --git a/crates/sui-analytics-indexer/src/handlers/df_handler.rs b/crates/sui-analytics-indexer/src/handlers/df_handler.rs index 0c7746261c815..f8dfdea85a039 100644 --- a/crates/sui-analytics-indexer/src/handlers/df_handler.rs +++ b/crates/sui-analytics-indexer/src/handlers/df_handler.rs @@ -7,7 +7,8 @@ use std::collections::HashMap; use std::path::Path; use sui_data_ingestion_core::Worker; use sui_indexer::errors::IndexerError; -use sui_types::SYSTEM_PACKAGE_ADDRESSES; +use sui_types::object::bounded_visitor::BoundedVisitor; +use sui_types::{TypeTag, SYSTEM_PACKAGE_ADDRESSES}; use tap::tap::TapFallible; use tokio::sync::Mutex; use tracing::warn; @@ -17,10 +18,11 @@ use sui_json_rpc_types::SuiMoveValue; use sui_package_resolver::Resolver; use sui_rest_api::{CheckpointData, CheckpointTransaction}; use sui_types::base_types::ObjectID; -use sui_types::dynamic_field::{DynamicFieldInfo, DynamicFieldName, DynamicFieldType}; +use sui_types::dynamic_field::visitor as DFV; +use sui_types::dynamic_field::{DynamicFieldName, DynamicFieldType}; use sui_types::object::Object; -use crate::handlers::{get_move_struct, AnalyticsHandler}; +use crate::handlers::AnalyticsHandler; use crate::package_store::{LocalDBPackageStore, PackageCache}; use crate::tables::DynamicFieldEntry; use crate::FileType; @@ -37,6 +39,8 @@ struct State { #[async_trait::async_trait] impl Worker for DynamicFieldHandler { + type Result = (); + async fn process_checkpoint(&self, checkpoint_data: &CheckpointData) -> Result<()> { let CheckpointData { checkpoint_summary, @@ -114,28 +118,23 @@ impl DynamicFieldHandler { if !move_object.type_().is_dynamic_field() { return Ok(()); } - let move_struct = if let Some((tag, contents)) = object - .struct_tag() - .and_then(|tag| object.data.try_as_move().map(|mo| (tag, mo.contents()))) - { - let move_struct = get_move_struct(&tag, contents, &state.resolver).await?; - Some(move_struct) - } else { - None - }; - let Some(move_struct) = move_struct else { - return Ok(()); - }; - let (name_value, type_, object_id) = - DynamicFieldInfo::parse_move_object(&move_struct).tap_err(|e| warn!("{e}"))?; - let name_type = move_object.type_().try_extract_field_name(&type_)?; - - let bcs_name = bcs::to_bytes(&name_value.clone().undecorate()).map_err(|e| { - IndexerError::SerdeError(format!( - "Failed to serialize dynamic field name {:?}: {e}", - name_value - )) - })?; + + let layout = state + .resolver + .type_layout(move_object.type_().clone().into()) + .await?; + let object_id = object.id(); + + let field = DFV::FieldVisitor::deserialize(move_object.contents(), &layout)?; + + let type_ = field.kind; + let name_type: TypeTag = field.name_layout.into(); + let bcs_name = field.name_bytes.to_owned(); + + let name_value = BoundedVisitor::deserialize_value(field.name_bytes, field.name_layout) + .tap_err(|e| { + warn!("{e}"); + })?; let name = DynamicFieldName { type_: name_type, value: SuiMoveValue::from(name_value).to_json_value(), diff --git a/crates/sui-analytics-indexer/src/handlers/event_handler.rs b/crates/sui-analytics-indexer/src/handlers/event_handler.rs index 4e78f99693d31..bd2cdf6776350 100644 --- a/crates/sui-analytics-indexer/src/handlers/event_handler.rs +++ b/crates/sui-analytics-indexer/src/handlers/event_handler.rs @@ -33,6 +33,8 @@ struct State { #[async_trait::async_trait] impl Worker for EventHandler { + type Result = (); + async fn process_checkpoint(&self, checkpoint_data: &CheckpointData) -> Result<()> { let CheckpointData { checkpoint_summary, diff --git a/crates/sui-analytics-indexer/src/handlers/mod.rs b/crates/sui-analytics-indexer/src/handlers/mod.rs index 420335695f29c..ac4026dcac4ee 100644 --- a/crates/sui-analytics-indexer/src/handlers/mod.rs +++ b/crates/sui-analytics-indexer/src/handlers/mod.rs @@ -38,7 +38,7 @@ const WRAPPED_INDEXING_DISALLOW_LIST: [&str; 4] = [ ]; #[async_trait::async_trait] -pub trait AnalyticsHandler: Worker { +pub trait AnalyticsHandler: Worker { /// Read back rows which are ready to be persisted. This function /// will be invoked by the analytics processor after every call to /// process_checkpoint diff --git a/crates/sui-analytics-indexer/src/handlers/move_call_handler.rs b/crates/sui-analytics-indexer/src/handlers/move_call_handler.rs index d1c2cba3e85e3..93c018164fcb3 100644 --- a/crates/sui-analytics-indexer/src/handlers/move_call_handler.rs +++ b/crates/sui-analytics-indexer/src/handlers/move_call_handler.rs @@ -23,6 +23,8 @@ struct State { #[async_trait::async_trait] impl Worker for MoveCallHandler { + type Result = (); + async fn process_checkpoint(&self, checkpoint_data: &CheckpointData) -> Result<()> { let CheckpointData { checkpoint_summary, diff --git a/crates/sui-analytics-indexer/src/handlers/object_handler.rs b/crates/sui-analytics-indexer/src/handlers/object_handler.rs index d03a9f20acd89..f8b4f26f3023b 100644 --- a/crates/sui-analytics-indexer/src/handlers/object_handler.rs +++ b/crates/sui-analytics-indexer/src/handlers/object_handler.rs @@ -35,6 +35,8 @@ struct State { #[async_trait::async_trait] impl Worker for ObjectHandler { + type Result = (); + async fn process_checkpoint(&self, checkpoint_data: &CheckpointData) -> Result<()> { let CheckpointData { checkpoint_summary, diff --git a/crates/sui-analytics-indexer/src/handlers/package_handler.rs b/crates/sui-analytics-indexer/src/handlers/package_handler.rs index 6f0fa0f491b71..bfb6bcfdccd88 100644 --- a/crates/sui-analytics-indexer/src/handlers/package_handler.rs +++ b/crates/sui-analytics-indexer/src/handlers/package_handler.rs @@ -23,6 +23,8 @@ struct State { #[async_trait::async_trait] impl Worker for PackageHandler { + type Result = (); + async fn process_checkpoint(&self, checkpoint_data: &CheckpointData) -> Result<()> { let CheckpointData { checkpoint_summary, diff --git a/crates/sui-analytics-indexer/src/handlers/transaction_handler.rs b/crates/sui-analytics-indexer/src/handlers/transaction_handler.rs index 0da0f8b21f270..2ade6f8f1c8db 100644 --- a/crates/sui-analytics-indexer/src/handlers/transaction_handler.rs +++ b/crates/sui-analytics-indexer/src/handlers/transaction_handler.rs @@ -28,6 +28,8 @@ pub(crate) struct State { #[async_trait::async_trait] impl Worker for TransactionHandler { + type Result = (); + async fn process_checkpoint(&self, checkpoint_data: &CheckpointData) -> Result<()> { let CheckpointData { checkpoint_summary, diff --git a/crates/sui-analytics-indexer/src/handlers/transaction_objects_handler.rs b/crates/sui-analytics-indexer/src/handlers/transaction_objects_handler.rs index ee6004f7052c1..e9fbc38542fdb 100644 --- a/crates/sui-analytics-indexer/src/handlers/transaction_objects_handler.rs +++ b/crates/sui-analytics-indexer/src/handlers/transaction_objects_handler.rs @@ -24,6 +24,8 @@ struct State { #[async_trait::async_trait] impl Worker for TransactionObjectsHandler { + type Result = (); + async fn process_checkpoint(&self, checkpoint_data: &CheckpointData) -> Result<()> { let CheckpointData { checkpoint_summary, diff --git a/crates/sui-analytics-indexer/src/handlers/wrapped_object_handler.rs b/crates/sui-analytics-indexer/src/handlers/wrapped_object_handler.rs index 9734b10f1ffad..8887df15ffc0c 100644 --- a/crates/sui-analytics-indexer/src/handlers/wrapped_object_handler.rs +++ b/crates/sui-analytics-indexer/src/handlers/wrapped_object_handler.rs @@ -30,6 +30,8 @@ struct State { #[async_trait::async_trait] impl Worker for WrappedObjectHandler { + type Result = (); + async fn process_checkpoint(&self, checkpoint_data: &CheckpointData) -> Result<()> { let CheckpointData { checkpoint_summary, diff --git a/crates/sui-analytics-indexer/src/lib.rs b/crates/sui-analytics-indexer/src/lib.rs index 86b3bc1e24004..579a057d5f9eb 100644 --- a/crates/sui-analytics-indexer/src/lib.rs +++ b/crates/sui-analytics-indexer/src/lib.rs @@ -487,12 +487,14 @@ impl FileMetadata { } pub struct Processor { - pub processor: Box, + pub processor: Box>, pub starting_checkpoint_seq_num: CheckpointSequenceNumber, } #[async_trait::async_trait] impl Worker for Processor { + type Result = (); + #[inline] async fn process_checkpoint(&self, checkpoint_data: &CheckpointData) -> Result<()> { self.processor.process_checkpoint(checkpoint_data).await diff --git a/crates/sui-authority-aggregation/src/lib.rs b/crates/sui-authority-aggregation/src/lib.rs index d1b05f5163e0a..15aae583d5487 100644 --- a/crates/sui-authority-aggregation/src/lib.rs +++ b/crates/sui-authority-aggregation/src/lib.rs @@ -7,7 +7,7 @@ use mysten_metrics::monitored_future; use std::collections::{BTreeMap, BTreeSet}; use std::sync::Arc; -use std::time::Duration; +use std::time::{Duration, Instant}; use sui_types::base_types::ConciseableName; use sui_types::committee::{CommitteeTrait, StakeUnit}; @@ -15,13 +15,25 @@ use tokio::time::timeout; pub type AsyncResult<'a, T, E> = BoxFuture<'a, Result>; +pub struct SigRequestPrefs { + pub ordering_pref: BTreeSet, + pub prefetch_timeout: Duration, +} + pub enum ReduceOutput { Continue(S), - ContinueWithTimeout(S, Duration), Failed(S), Success(R), } +/// This function takes an initial state, than executes an asynchronous function (FMap) for each +/// authority, and folds the results as they become available into the state using an async function (FReduce). +/// +/// prefetch_timeout: the minimum amount of time to spend trying to gather results from all authorities +/// before falling back to arrival order. +/// +/// total_timeout: the maximum amount of total time to wait for results from all authorities, including +/// time spent prefetching. pub async fn quorum_map_then_reduce_with_timeout_and_prefs< 'a, C, @@ -36,11 +48,11 @@ pub async fn quorum_map_then_reduce_with_timeout_and_prefs< >( committee: Arc, authority_clients: Arc>>, - authority_preferences: Option<&BTreeSet>, + authority_preferences: Option>, initial_state: S, map_each_authority: FMap, reduce_result: FReduce, - initial_timeout: Duration, + total_timeout: Duration, ) -> Result< ( R, @@ -54,10 +66,22 @@ where FMap: FnOnce(K, Arc) -> AsyncResult<'a, V, E> + Clone + 'a, FReduce: Fn(S, K, StakeUnit, Result) -> BoxFuture<'a, ReduceOutput>, { - let authorities_shuffled = committee.shuffle_by_stake(authority_preferences, None); + let (preference, prefetch_timeout) = if let Some(SigRequestPrefs { + ordering_pref, + prefetch_timeout, + }) = authority_preferences + { + (Some(ordering_pref), Some(prefetch_timeout)) + } else { + (None, None) + }; + let authorities_shuffled = committee.shuffle_by_stake(preference.as_ref(), None); + let mut accumulated_state = initial_state; + let mut total_timeout = total_timeout; // First, execute in parallel for each authority FMap. let mut responses: futures::stream::FuturesUnordered<_> = authorities_shuffled + .clone() .into_iter() .map(|name| { let client = authority_clients[&name].clone(); @@ -65,22 +89,66 @@ where monitored_future!(async move { (name.clone(), execute(name, client).await,) }) }) .collect(); + if let Some(prefetch_timeout) = prefetch_timeout { + let elapsed = Instant::now(); + let prefetch_sleep = tokio::time::sleep(prefetch_timeout); + let mut authority_to_result: BTreeMap> = BTreeMap::new(); + tokio::pin!(prefetch_sleep); + // get all the sigs we can within prefetch_timeout + loop { + tokio::select! { + resp = responses.next() => { + match resp { + Some((authority_name, result)) => { + authority_to_result.insert(authority_name, result); + } + None => { + // we have processed responses from the full committee so can stop early + break; + } + } + } + _ = &mut prefetch_sleep => { + break; + } + } + } + // process what we have up to this point + for authority_name in authorities_shuffled { + let authority_weight = committee.weight(&authority_name); + if let Some(result) = authority_to_result.remove(&authority_name) { + accumulated_state = match reduce_result( + accumulated_state, + authority_name, + authority_weight, + result, + ) + .await + { + // In the first two cases we are told to continue the iteration. + ReduceOutput::Continue(state) => state, + ReduceOutput::Failed(state) => { + return Err(state); + } + ReduceOutput::Success(result) => { + // The reducer tells us that we have the result needed. Just return it. + return Ok((result, responses)); + } + }; + } + } + // if we got here, fallback through the if statement to continue in arrival order on + // the remaining validators + total_timeout = total_timeout.saturating_sub(elapsed.elapsed()); + } - let mut current_timeout = initial_timeout; - let mut accumulated_state = initial_state; - // Then, as results become available fold them into the state using FReduce. - while let Ok(Some((authority_name, result))) = timeout(current_timeout, responses.next()).await - { + // As results become available fold them into the state using FReduce. + while let Ok(Some((authority_name, result))) = timeout(total_timeout, responses.next()).await { let authority_weight = committee.weight(&authority_name); accumulated_state = match reduce_result(accumulated_state, authority_name, authority_weight, result).await { // In the first two cases we are told to continue the iteration. ReduceOutput::Continue(state) => state, - ReduceOutput::ContinueWithTimeout(state, duration) => { - // Adjust the waiting timeout. - current_timeout = duration; - state - } ReduceOutput::Failed(state) => { return Err(state); } @@ -104,8 +172,7 @@ where /// /// FReduce returns a result to a ReduceOutput. If the result is Err the function /// shortcuts and the Err is returned. An Ok ReduceOutput result can be used to shortcut and return -/// the resulting state (ReduceOutput::End), continue the folding as new states arrive (ReduceOutput::Continue), -/// or continue with a timeout maximum waiting time (ReduceOutput::ContinueWithTimeout). +/// the resulting state (ReduceOutput::End), continue the folding as new states arrive (ReduceOutput::Continue). /// /// This function provides a flexible way to communicate with a quorum of authorities, processing and /// processing their results into a safe overall result, and also safely allowing operations to continue diff --git a/crates/sui-benchmark/tests/simtest.rs b/crates/sui-benchmark/tests/simtest.rs index b7d2e189da3a2..fcb22e160de14 100644 --- a/crates/sui-benchmark/tests/simtest.rs +++ b/crates/sui-benchmark/tests/simtest.rs @@ -461,12 +461,17 @@ mod test { let checkpoint_budget_factor; // The checkpoint congestion control budget in respect to transaction budget. let txn_count_limit; // When using transaction count as congestion control mode, the limit of transactions per object per commit. let max_deferral_rounds; + let cap_factor_denominator; { let mut rng = thread_rng(); - mode = if rng.gen_bool(0.5) { + mode = if rng.gen_bool(0.33) { PerObjectCongestionControlMode::TotalGasBudget } else { - PerObjectCongestionControlMode::TotalTxCount + if rng.gen_bool(0.5) { + PerObjectCongestionControlMode::TotalTxCount + } else { + PerObjectCongestionControlMode::TotalGasBudgetWithCap + } }; checkpoint_budget_factor = rng.gen_range(1..20); txn_count_limit = rng.gen_range(1..=10); @@ -474,7 +479,9 @@ mod test { rng.gen_range(0..20) // Short deferral round (testing cancellation) } else { rng.gen_range(1000..10000) // Large deferral round (testing liveness) - } + }; + + cap_factor_denominator = rng.gen_range(1..100); } info!( @@ -504,6 +511,14 @@ mod test { txn_count_limit ); }, + PerObjectCongestionControlMode::TotalGasBudgetWithCap => { + let total_gas_limit = checkpoint_budget_factor + * DEFAULT_VALIDATOR_GAS_PRICE + * TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE; + config.set_max_accumulated_txn_cost_per_object_in_narwhal_commit_for_testing(total_gas_limit); + config.set_max_accumulated_txn_cost_per_object_in_mysticeti_commit_for_testing(total_gas_limit); + config.set_gas_budget_based_txn_cost_cap_factor_for_testing(total_gas_limit/cap_factor_denominator); + }, } config.set_max_deferral_rounds_for_congestion_control_for_testing(max_deferral_rounds); config diff --git a/crates/sui-bridge-cli/src/main.rs b/crates/sui-bridge-cli/src/main.rs index 0919dfe6838e8..25532f6bc5335 100644 --- a/crates/sui-bridge-cli/src/main.rs +++ b/crates/sui-bridge-cli/src/main.rs @@ -411,7 +411,8 @@ async fn main() -> anyhow::Result<()> { }; let url = url.to_string(); - let name = names.get(&sui_address).unwrap(); + let name = names.get(&sui_address).cloned().unwrap_or(url.clone()); + if ping { let client_clone = client.clone(); ping_tasks.push(client_clone.get(url.clone()).send()); diff --git a/crates/sui-bridge-indexer/Cargo.toml b/crates/sui-bridge-indexer/Cargo.toml index 46dd6ff414e84..4e41c2d6e0100 100644 --- a/crates/sui-bridge-indexer/Cargo.toml +++ b/crates/sui-bridge-indexer/Cargo.toml @@ -23,6 +23,8 @@ clap.workspace = true mysten-metrics.workspace = true prometheus.workspace = true serde_yaml.workspace = true +serde_json.workspace = true +strum_macros.workspace = true sui-bridge.workspace = true sui-sdk.workspace = true sui-json-rpc-types.workspace = true @@ -34,6 +36,7 @@ backoff.workspace = true sui-config.workspace = true tempfile.workspace = true sui-indexer-builder.workspace = true +sui-bridge-watchdog.workspace = true [dev-dependencies] sui-types = { workspace = true, features = ["test-utils"] } diff --git a/crates/sui-bridge-indexer/config.yaml b/crates/sui-bridge-indexer/config.yaml index 2e1a60036f88f..5263c915279a5 100644 --- a/crates/sui-bridge-indexer/config.yaml +++ b/crates/sui-bridge-indexer/config.yaml @@ -20,7 +20,3 @@ # metric_url: # Client metric port # metric_port: -# checkpoint size of each backfill worker, use 432000 for 1 worker per day, assume 5 checkpoint per second -# back_fill_lot_size: -# Optional starting checkpoint for realtime ingestion task -# resume_from_checkpoint: diff --git a/crates/sui-bridge-indexer/src/eth_bridge_indexer.rs b/crates/sui-bridge-indexer/src/eth_bridge_indexer.rs index 99b5e0ca2a56a..3ed0348db120f 100644 --- a/crates/sui-bridge-indexer/src/eth_bridge_indexer.rs +++ b/crates/sui-bridge-indexer/src/eth_bridge_indexer.rs @@ -9,19 +9,24 @@ use anyhow::Error; use async_trait::async_trait; use ethers::prelude::Transaction; use ethers::providers::{Http, Middleware, Provider, StreamExt, Ws}; -use ethers::types::{Address as EthAddress, Block, Filter, H256}; -use prometheus::{IntCounterVec, IntGaugeVec}; +use ethers::types::{Address as EthAddress, Block, Filter, Log, H256}; +use prometheus::{IntCounterVec, IntGauge, IntGaugeVec}; use sui_bridge::error::BridgeError; use sui_bridge::eth_client::EthClient; use sui_bridge::eth_syncer::EthSyncer; use sui_bridge::metered_eth_provider::MeteredEthHttpProvier; use sui_bridge::retry_with_max_elapsed_time; use sui_indexer_builder::Task; +use tap::tap::TapFallible; +use tokio::select; use tokio::task::JoinHandle; -use tracing::info; +use tracing::{info, warn}; use mysten_metrics::spawn_monitored_task; -use sui_bridge::abi::{EthBridgeEvent, EthSuiBridgeEvents}; +use sui_bridge::abi::{ + EthBridgeCommitteeEvents, EthBridgeConfigEvents, EthBridgeEvent, EthBridgeLimiterEvents, + EthSuiBridgeEvents, +}; use crate::metrics::BridgeIndexerMetrics; use sui_bridge::metrics::BridgeMetrics; @@ -29,9 +34,11 @@ use sui_bridge::types::{EthEvent, RawEthLog}; use sui_indexer_builder::indexer_builder::{DataMapper, DataSender, Datasource}; use crate::{ - BridgeDataSource, ProcessedTxnData, TokenTransfer, TokenTransferData, TokenTransferStatus, + BridgeDataSource, GovernanceAction, GovernanceActionType, ProcessedTxnData, TokenTransfer, + TokenTransferData, TokenTransferStatus, }; +#[derive(Debug)] pub struct RawEthData { log: RawEthLog, block: Block, @@ -73,75 +80,54 @@ impl Datasource for EthSubscriptionDatasource { task: Task, data_sender: DataSender, ) -> Result>, Error> { + assert!( + task.is_live_task, + "EthSubscriptionDatasource only supports live tasks" + ); let filter = Filter::new() .address(self.addresses.clone()) .from_block(task.start_checkpoint) .to_block(task.target_checkpoint); let eth_ws_url = self.eth_ws_url.clone(); - + let task_name = task.task_name.clone(); + let task_name_clone = task_name.clone(); + let progress_metric = self + .indexer_metrics + .tasks_latest_retrieved_checkpoints + .with_label_values(&[task.name_prefix(), task.type_str()]); let handle = spawn_monitored_task!(async move { - let eth_ws_client = Provider::::connect(ð_ws_url).await?; - - // TODO: enable a shared cache for blocks that can be used by both the subscription and finalized sync - let mut cached_blocks: HashMap> = HashMap::new(); - - let mut stream = eth_ws_client.subscribe_logs(&filter).await?; - while let Some(log) = stream.next().await { - let raw_log = RawEthLog { - block_number: log - .block_number - .ok_or(BridgeError::ProviderError( - "Provider returns log without block_number".into(), - )) - .unwrap() - .as_u64(), - tx_hash: log - .transaction_hash - .ok_or(BridgeError::ProviderError( - "Provider returns log without transaction_hash".into(), - )) - .unwrap(), - log, - }; - - let block_number = raw_log.block_number(); - - let block = if let Some(cached_block) = cached_blocks.get(&block_number) { - cached_block.clone() - } else { - let Ok(Ok(Some(block))) = retry_with_max_elapsed_time!( - eth_ws_client.get_block(block_number), - Duration::from_secs(30000) - ) else { - panic!("Unable to get block from provider"); - }; - - cached_blocks.insert(block_number, block.clone()); - block - }; - - let Ok(Ok(Some(transaction))) = retry_with_max_elapsed_time!( - eth_ws_client.get_transaction(raw_log.tx_hash), - Duration::from_secs(30000) - ) else { - panic!("Unable to get transaction from provider"); - }; - - data_sender - .send(( - block_number, - vec![RawEthData { - log: raw_log, - block, - transaction, - is_finalized: false, - }], - )) - .await?; + let eth_ws_client = Provider::::connect(ð_ws_url).await.tap_err(|e| { + tracing::error!("Failed to connect to websocket: {:?}", e); + })?; + + let mut log_stream = eth_ws_client.subscribe_logs(&filter).await.tap_err(|e| { + tracing::error!("Failed to subscribe logs: {:?}", e); + })?; + // Check latest block height every 5 sec + let mut interval = tokio::time::interval(Duration::from_secs(5)); + interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + loop { + select! { + log = log_stream.next() => { + if let Some(log) = log { + Self::handle_log(&task_name_clone, log, ð_ws_client, &data_sender).await; + } else { + panic!("EthSubscriptionDatasource log stream ended unexpectedly"); + } + } + _ = interval.tick() => { + let Ok(Ok(block_num)) = retry_with_max_elapsed_time!( + eth_ws_client.get_block_number(), + Duration::from_secs(30000) + ) else { + tracing::error!("Failed to get block number"); + continue; + }; + progress_metric.set(block_num.as_u64() as i64); + } + } } - - Ok::<_, Error>(()) }); Ok(handle) } @@ -164,6 +150,87 @@ impl Datasource for EthSubscriptionDatasource { fn get_tasks_processed_checkpoints_metric(&self) -> &IntCounterVec { &self.indexer_metrics.tasks_processed_checkpoints } + + fn get_inflight_live_tasks_metrics(&self) -> &IntGaugeVec { + &self.indexer_metrics.inflight_live_tasks + } +} + +impl EthSubscriptionDatasource { + async fn handle_log( + task_name: &str, + log: Log, + eth_ws_client: &Provider, + data_sender: &DataSender, + ) { + tracing::info!( + task_name, + "EthSubscriptionDatasource retrieved log: {:?}", + log + ); + // TODO: enable a shared cache for blocks that can be used by both the subscription and finalized sync + let mut cached_blocks: HashMap> = HashMap::new(); + let raw_log = RawEthLog { + block_number: log + .block_number + .ok_or(BridgeError::ProviderError( + "Provider returns log without block_number".into(), + )) + .unwrap() + .as_u64(), + tx_hash: log + .transaction_hash + .ok_or(BridgeError::ProviderError( + "Provider returns log without transaction_hash".into(), + )) + .unwrap(), + log, + }; + + let block_number = raw_log.block_number(); + + let block = if let Some(cached_block) = cached_blocks.get(&block_number) { + cached_block.clone() + } else { + let Ok(Ok(Some(block))) = retry_with_max_elapsed_time!( + eth_ws_client.get_block(block_number), + Duration::from_secs(30000) + ) else { + panic!("Unable to get block from provider"); + }; + + cached_blocks.insert(block_number, block.clone()); + block + }; + + let Ok(Ok(Some(transaction))) = retry_with_max_elapsed_time!( + eth_ws_client.get_transaction(raw_log.tx_hash), + Duration::from_secs(30000) + ) else { + panic!("Unable to get transaction from provider"); + }; + tracing::info!( + task_name, + "Sending data from EthSubscriptionDatasource: {:?}", + (raw_log.tx_hash, block_number) + ); + let raw_eth_data = vec![RawEthData { + log: raw_log, + block, + transaction, + is_finalized: false, + }]; + data_sender + .send((block_number, raw_eth_data)) + .await + .unwrap_or_else(|e| { + tracing::error!( + task_name, + "Failed to send data from EthSubscriptionDatasource: {:?}", + e + ); + }); + } } pub struct EthFinalizedSyncDatasource { @@ -205,31 +272,34 @@ impl Datasource for EthFinalizedSyncDatasource { Provider::::try_from(&self.eth_http_url)? .interval(std::time::Duration::from_millis(2000)), ); - + let progress_metric = self + .indexer_metrics + .tasks_latest_retrieved_checkpoints + .with_label_values(&[task.name_prefix(), task.type_str()]); let bridge_addresses = self.bridge_addresses.clone(); let client = self.eth_client.clone(); let provider = provider.clone(); let bridge_metrics = self.bridge_metrics.clone(); - let handle = spawn_monitored_task!(async move { if task.is_live_task { - retrieve_and_process_live_finalized_logs( + loop_retrieve_and_process_live_finalized_logs( + task, client, provider, bridge_addresses, - task.start_checkpoint, data_sender, bridge_metrics, + progress_metric, ) .await?; } else { - retrieve_and_process_log_range( + loop_retrieve_and_process_log_range( + task, client, provider, bridge_addresses, - task.start_checkpoint, - task.target_checkpoint, data_sender, + progress_metric, ) .await?; } @@ -257,28 +327,35 @@ impl Datasource for EthFinalizedSyncDatasource { fn get_tasks_processed_checkpoints_metric(&self) -> &IntCounterVec { &self.indexer_metrics.tasks_processed_checkpoints } + + fn get_inflight_live_tasks_metrics(&self) -> &IntGaugeVec { + &self.indexer_metrics.inflight_live_tasks + } } -async fn retrieve_and_process_live_finalized_logs( +async fn loop_retrieve_and_process_live_finalized_logs( + task: Task, client: Arc>, provider: Arc>, addresses: Vec, - starting_checkpoint: u64, data_sender: DataSender, bridge_metrics: Arc, + progress_metric: IntGauge, ) -> Result<(), Error> { + let task_name = task.task_name.clone(); + let starting_checkpoint = task.start_checkpoint; let eth_contracts_to_watch = HashMap::from_iter( addresses .iter() .map(|address| (*address, starting_checkpoint)), ); - let (_, mut eth_events_rx, _) = EthSyncer::new(client.clone(), eth_contracts_to_watch) .run(bridge_metrics.clone()) .await .expect("Failed to start eth syncer"); - // forward received events to the data sender + // EthSyncer sends items even when there is no matching events. + // We leverge this to update the progress metric. while let Some((_, block, logs)) = eth_events_rx.recv().await { let raw_logs: Vec = logs .into_iter() @@ -289,22 +366,33 @@ async fn retrieve_and_process_live_finalized_logs( }) .collect(); - process_logs(raw_logs, provider.clone(), data_sender.clone(), block, true) - .await - .expect("Failed to process logs"); + process_logs( + &task_name, + raw_logs, + provider.clone(), + data_sender.clone(), + block, + true, + ) + .await + .expect("Failed to process logs"); + progress_metric.set(block as i64); } panic!("Eth finalized syncer live task stopped unexpectedly"); } -async fn retrieve_and_process_log_range( +async fn loop_retrieve_and_process_log_range( + task: Task, client: Arc>, provider: Arc>, addresses: Vec, - starting_checkpoint: u64, - target_checkpoint: u64, data_sender: DataSender, + progress_metric: IntGauge, ) -> Result<(), Error> { + let task_name = task.task_name.clone(); + let starting_checkpoint = task.start_checkpoint; + let target_checkpoint = task.target_checkpoint; let mut all_logs = Vec::new(); let mut current_start = starting_checkpoint; @@ -331,22 +419,31 @@ async fn retrieve_and_process_log_range( } process_logs( + &task_name, all_logs, provider.clone(), data_sender.clone(), target_checkpoint, true, ) - .await?; - + .await + .tap_ok(|_| { + tracing::info!(task_name, "Finished processing range"); + }) + .tap_err(|e| { + tracing::error!(task_name, "Failed to process logs: {:?}", e); + }) + .expect("Process logs should not fail"); + progress_metric.set(target_checkpoint as i64); Ok::<_, Error>(()) } async fn process_logs( + task_name: &str, logs: Vec, provider: Arc>, data_sender: DataSender, - target_checkpoint: u64, + block_height: u64, is_finalized: bool, ) -> Result<(), Error> { let mut data = Vec::new(); @@ -382,9 +479,19 @@ async fn process_logs( is_finalized, }); } - - data_sender.send((target_checkpoint, data)).await?; - + let tx_hashes = data + .iter() + .map(|data| (data.log.tx_hash, data.block.number.map(|n| n.as_u64()))) + .collect::)>>(); + tracing::info!( + task_name, + "Sending data from EthFinalizedSyncDatasource: {:?}", + tx_hashes + ); + data_sender + .send((block_height, data)) + .await + .expect("Failed to send data"); Ok::<_, Error>(()) } @@ -411,68 +518,315 @@ impl DataMapper for EthDataMapper { let bridge_event = eth_bridge_event.unwrap(); let timestamp_ms = block.timestamp.as_u64() * 1000; let gas = transaction.gas; + let mut processed_txn_data = Vec::new(); + let txn_sender = transaction.from.as_bytes().to_vec(); + let txn_hash = transaction.hash.as_bytes().to_vec(); - let transfer = match bridge_event { - EthBridgeEvent::EthSuiBridgeEvents(bridge_event) => match bridge_event { + match bridge_event { + EthBridgeEvent::EthSuiBridgeEvents(bridge_event) => match &bridge_event { EthSuiBridgeEvents::TokensDepositedFilter(bridge_event) => { - info!("Observed Eth Deposit at block: {}", log.block_number()); + info!( + "Observed Eth Deposit at block: {}, tx_hash: {}", + log.block_number(), + log.tx_hash + ); self.metrics.total_eth_token_deposited.inc(); - ProcessedTxnData::TokenTransfer(TokenTransfer { + processed_txn_data.push(ProcessedTxnData::TokenTransfer(TokenTransfer { chain_id: bridge_event.source_chain_id, nonce: bridge_event.nonce, block_height: log.block_number(), timestamp_ms, - txn_hash: transaction.hash.as_bytes().to_vec(), - txn_sender: bridge_event.sender_address.as_bytes().to_vec(), + txn_hash: txn_hash.clone(), + txn_sender: txn_sender.clone(), status: TokenTransferStatus::Deposited, gas_usage: gas.as_u64() as i64, data_source: BridgeDataSource::Eth, is_finalized, data: Some(TokenTransferData { - sender_address: bridge_event.sender_address.as_bytes().to_vec(), + sender_address: txn_sender.clone(), destination_chain: bridge_event.destination_chain_id, recipient_address: bridge_event.recipient_address.to_vec(), token_id: bridge_event.token_id, amount: bridge_event.sui_adjusted_amount, is_finalized, }), - }) + })); } EthSuiBridgeEvents::TokensClaimedFilter(bridge_event) => { - info!("Observed Eth Claim at block: {}", log.block_number()); + info!( + "Observed Eth Claim at block: {}, tx_hash: {}", + log.block_number(), + log.tx_hash + ); self.metrics.total_eth_token_transfer_claimed.inc(); - ProcessedTxnData::TokenTransfer(TokenTransfer { + processed_txn_data.push(ProcessedTxnData::TokenTransfer(TokenTransfer { chain_id: bridge_event.source_chain_id, nonce: bridge_event.nonce, block_height: log.block_number(), timestamp_ms, - txn_hash: transaction.hash.as_bytes().to_vec(), - txn_sender: bridge_event.sender_address.to_vec(), + txn_hash: txn_hash.clone(), + txn_sender: txn_sender.clone(), status: TokenTransferStatus::Claimed, gas_usage: gas.as_u64() as i64, data_source: BridgeDataSource::Eth, data: None, is_finalized, - }) + })); + } + EthSuiBridgeEvents::EmergencyOperationFilter(f) => { + info!( + "Observed Eth Emergency Operation at block: {}, tx_hash: {}", + log.block_number(), + log.tx_hash + ); + processed_txn_data.push(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: Some(f.nonce), + data_source: BridgeDataSource::Eth, + tx_digest: txn_hash.clone(), + sender: txn_sender.clone(), + timestamp_ms, + action: GovernanceActionType::EmergencyOperation, + data: serde_json::to_value(bridge_event)?, + })); + } + EthSuiBridgeEvents::ContractUpgradedFilter(f) => { + info!( + "Observed Eth SuiBridge Upgrade at block: {}, tx_hash: {}", + log.block_number(), + log.tx_hash + ); + + processed_txn_data.push(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: Some(f.nonce.as_u64()), + data_source: BridgeDataSource::Eth, + tx_digest: txn_hash.clone(), + sender: txn_sender.clone(), + timestamp_ms, + action: GovernanceActionType::UpgradeEVMContract, + data: serde_json::to_value(bridge_event)?, + })); } - EthSuiBridgeEvents::PausedFilter(_) + + EthSuiBridgeEvents::InitializedFilter(_) + | EthSuiBridgeEvents::PausedFilter(_) | EthSuiBridgeEvents::UnpausedFilter(_) - | EthSuiBridgeEvents::UpgradedFilter(_) - | EthSuiBridgeEvents::InitializedFilter(_) => { - // TODO: handle these events - self.metrics.total_eth_bridge_txn_other.inc(); - return Ok(vec![]); + | EthSuiBridgeEvents::UpgradedFilter(_) => { + warn!("Unexpected event {bridge_event:?}.") + } + }, + EthBridgeEvent::EthBridgeCommitteeEvents(bridge_event) => match &bridge_event { + EthBridgeCommitteeEvents::BlocklistUpdatedFilter(_) => { + info!( + "Observed Eth Blocklist Update at block: {}, tx_hash: {}", + log.block_number(), + log.tx_hash + ); + + processed_txn_data.push(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: None, + data_source: BridgeDataSource::Eth, + tx_digest: txn_hash.clone(), + sender: txn_sender.clone(), + timestamp_ms, + action: GovernanceActionType::UpdateCommitteeBlocklist, + data: serde_json::to_value(bridge_event)?, + })); + } + EthBridgeCommitteeEvents::BlocklistUpdatedV2Filter(f) => { + info!( + "Observed Eth Blocklist Update at block: {}, tx_hash: {}", + log.block_number(), + log.tx_hash + ); + + processed_txn_data.push(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: Some(f.nonce), + data_source: BridgeDataSource::Eth, + tx_digest: txn_hash.clone(), + sender: txn_sender.clone(), + timestamp_ms, + action: GovernanceActionType::UpdateCommitteeBlocklist, + data: serde_json::to_value(bridge_event)?, + })); + } + EthBridgeCommitteeEvents::ContractUpgradedFilter(f) => { + info!( + "Observed Eth BridgeCommittee Upgrade at block: {}, tx_hash: {}", + log.block_number(), + log.tx_hash + ); + + processed_txn_data.push(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: Some(f.nonce.as_u64()), + data_source: BridgeDataSource::Eth, + tx_digest: txn_hash.clone(), + sender: txn_sender.clone(), + timestamp_ms, + action: GovernanceActionType::UpgradeEVMContract, + data: serde_json::to_value(bridge_event)?, + })); + } + EthBridgeCommitteeEvents::InitializedFilter(_) + | EthBridgeCommitteeEvents::UpgradedFilter(_) => { + warn!("Unexpected event {bridge_event:?}.") + } + }, + EthBridgeEvent::EthBridgeLimiterEvents(bridge_event) => match &bridge_event { + EthBridgeLimiterEvents::LimitUpdatedFilter(_) => { + info!( + "Observed Eth BridgeLimiter Update at block: {}, tx_hash: {}", + log.block_number(), + log.tx_hash + ); + + processed_txn_data.push(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: None, + data_source: BridgeDataSource::Eth, + tx_digest: txn_hash.clone(), + sender: txn_sender.clone(), + timestamp_ms, + action: GovernanceActionType::UpdateBridgeLimit, + data: serde_json::to_value(bridge_event)?, + })); + } + EthBridgeLimiterEvents::LimitUpdatedV2Filter(f) => { + info!( + "Observed Eth BridgeLimiter Update at block: {}, tx_hash: {}", + log.block_number(), + log.tx_hash + ); + + processed_txn_data.push(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: Some(f.nonce), + data_source: BridgeDataSource::Eth, + tx_digest: txn_hash.clone(), + sender: txn_sender.clone(), + timestamp_ms, + action: GovernanceActionType::UpdateBridgeLimit, + data: serde_json::to_value(bridge_event)?, + })); + } + EthBridgeLimiterEvents::ContractUpgradedFilter(f) => { + info!( + "Observed Eth BridgeLimiter Upgrade at block: {}, tx_hash: {}", + log.block_number(), + log.tx_hash + ); + + processed_txn_data.push(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: Some(f.nonce.as_u64()), + data_source: BridgeDataSource::Eth, + tx_digest: txn_hash.clone(), + sender: txn_sender.clone(), + timestamp_ms, + action: GovernanceActionType::UpgradeEVMContract, + data: serde_json::to_value(bridge_event)?, + })); + } + + EthBridgeLimiterEvents::HourlyTransferAmountUpdatedFilter(_) + | EthBridgeLimiterEvents::InitializedFilter(_) + | EthBridgeLimiterEvents::OwnershipTransferredFilter(_) + | EthBridgeLimiterEvents::UpgradedFilter(_) => { + warn!("Unexpected event {bridge_event:?}.") + } + }, + EthBridgeEvent::EthBridgeConfigEvents(bridge_event) => match &bridge_event { + EthBridgeConfigEvents::TokenPriceUpdatedFilter(_) => { + info!( + "Observed Eth TokenPrices Update at block: {}, tx_hash: {}", + log.block_number(), + log.tx_hash + ); + + processed_txn_data.push(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: None, + data_source: BridgeDataSource::Eth, + tx_digest: txn_hash.clone(), + sender: txn_sender.clone(), + timestamp_ms, + action: GovernanceActionType::UpdateTokenPrices, + data: serde_json::to_value(bridge_event)?, + })); + } + EthBridgeConfigEvents::TokenPriceUpdatedV2Filter(f) => { + info!( + "Observed Eth TokenPrices Update at block: {}, tx_hash: {}", + log.block_number(), + log.tx_hash + ); + + processed_txn_data.push(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: Some(f.nonce), + data_source: BridgeDataSource::Eth, + tx_digest: txn_hash.clone(), + sender: txn_sender.clone(), + timestamp_ms, + action: GovernanceActionType::UpdateTokenPrices, + data: serde_json::to_value(bridge_event)?, + })); + } + EthBridgeConfigEvents::TokenAddedFilter(_) => { + info!( + "Observed Eth AddSuiTokens at block: {}, tx_hash: {}", + log.block_number(), + log.tx_hash + ); + + processed_txn_data.push(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: None, + data_source: BridgeDataSource::Eth, + tx_digest: txn_hash.clone(), + sender: txn_sender.clone(), + timestamp_ms, + action: GovernanceActionType::AddEVMTokens, + data: serde_json::to_value(bridge_event)?, + })); + } + EthBridgeConfigEvents::TokensAddedV2Filter(f) => { + info!( + "Observed Eth AddSuiTokens at block: {}, tx_hash: {}", + log.block_number(), + log.tx_hash + ); + + processed_txn_data.push(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: Some(f.nonce), + data_source: BridgeDataSource::Eth, + tx_digest: txn_hash.clone(), + sender: txn_sender.clone(), + timestamp_ms, + action: GovernanceActionType::AddEVMTokens, + data: serde_json::to_value(bridge_event)?, + })); + } + EthBridgeConfigEvents::ContractUpgradedFilter(f) => { + info!( + "Observed Eth BridgeConfig Upgrade at block: {}, tx_hash: {}", + log.block_number(), + log.tx_hash + ); + + processed_txn_data.push(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: Some(f.nonce.as_u64()), + data_source: BridgeDataSource::Eth, + tx_digest: txn_hash.clone(), + sender: txn_sender.clone(), + timestamp_ms, + action: GovernanceActionType::UpgradeEVMContract, + data: serde_json::to_value(bridge_event)?, + })); + } + + EthBridgeConfigEvents::InitializedFilter(_) + | EthBridgeConfigEvents::UpgradedFilter(_) => { + warn!("Unexpected event {bridge_event:?}.") } }, - EthBridgeEvent::EthBridgeCommitteeEvents(_) - | EthBridgeEvent::EthBridgeLimiterEvents(_) - | EthBridgeEvent::EthBridgeConfigEvents(_) - | EthBridgeEvent::EthCommitteeUpgradeableContractEvents(_) => { - // TODO: handle these events - self.metrics.total_eth_bridge_txn_other.inc(); - return Ok(vec![]); + EthBridgeEvent::EthCommitteeUpgradeableContractEvents(_) => { + warn!("Unexpected event {bridge_event:?}.") } }; - Ok(vec![transfer]) + Ok(processed_txn_data) } } diff --git a/crates/sui-bridge-indexer/src/lib.rs b/crates/sui-bridge-indexer/src/lib.rs index d74f194f653c2..b95802502eb93 100644 --- a/crates/sui-bridge-indexer/src/lib.rs +++ b/crates/sui-bridge-indexer/src/lib.rs @@ -2,9 +2,11 @@ // SPDX-License-Identifier: Apache-2.0 use std::fmt::{Display, Formatter}; +use strum_macros::Display; use sui_types::base_types::{SuiAddress, TransactionDigest}; +use crate::models::GovernanceAction as DBGovernanceAction; use crate::models::TokenTransferData as DBTokenTransferData; use crate::models::{SuiErrorTransactions, TokenTransfer as DBTokenTransfer}; @@ -25,6 +27,7 @@ pub mod sui_datasource; #[derive(Clone)] pub enum ProcessedTxnData { TokenTransfer(TokenTransfer), + GovernanceAction(GovernanceAction), Error(SuiTxnError), } @@ -52,6 +55,17 @@ pub struct TokenTransfer { is_finalized: bool, } +#[derive(Clone)] +pub struct GovernanceAction { + nonce: Option, + data_source: BridgeDataSource, + tx_digest: Vec, + sender: Vec, + timestamp_ms: u64, + action: GovernanceActionType, + data: serde_json::Value, +} + #[derive(Clone)] pub struct TokenTransferData { sender_address: Vec, @@ -107,6 +121,20 @@ impl SuiTxnError { } } +impl GovernanceAction { + fn to_db(&self) -> DBGovernanceAction { + DBGovernanceAction { + nonce: self.nonce.map(|nonce| nonce as i64), + data_source: self.data_source.to_string(), + txn_digest: self.tx_digest.clone(), + sender_address: self.sender.to_vec(), + timestamp_ms: self.timestamp_ms as i64, + action: self.action.to_string(), + data: self.data.clone(), + } + } +} + #[derive(Clone)] pub(crate) enum TokenTransferStatus { Deposited, @@ -125,6 +153,17 @@ impl Display for TokenTransferStatus { } } +#[derive(Clone, Display)] +pub(crate) enum GovernanceActionType { + UpdateCommitteeBlocklist, + EmergencyOperation, + UpdateBridgeLimit, + UpdateTokenPrices, + UpgradeEVMContract, + AddSuiTokens, + AddEVMTokens, +} + #[derive(Clone)] enum BridgeDataSource { Sui, diff --git a/crates/sui-bridge-indexer/src/main.rs b/crates/sui-bridge-indexer/src/main.rs index 56e6740e27050..8ba1d128ce1d4 100644 --- a/crates/sui-bridge-indexer/src/main.rs +++ b/crates/sui-bridge-indexer/src/main.rs @@ -3,7 +3,9 @@ use anyhow::Result; use clap::*; +use ethers::providers::{Http, Provider}; use ethers::types::Address as EthAddress; +use prometheus::Registry; use std::collections::HashSet; use std::env; use std::net::IpAddr; @@ -12,9 +14,12 @@ use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; use sui_bridge::eth_client::EthClient; -use sui_bridge::metered_eth_provider::MeteredEthHttpProvier; +use sui_bridge::metered_eth_provider::{new_metered_eth_provider, MeteredEthHttpProvier}; +use sui_bridge::sui_client::SuiBridgeClient; +use sui_bridge::utils::get_eth_contract_addresses; use sui_bridge_indexer::eth_bridge_indexer::EthFinalizedSyncDatasource; use sui_bridge_indexer::eth_bridge_indexer::EthSubscriptionDatasource; +use sui_config::Config; use tokio::task::JoinHandle; use tracing::info; @@ -32,7 +37,10 @@ use sui_bridge_indexer::sui_bridge_indexer::SuiBridgeDataMapper; use sui_bridge_indexer::sui_datasource::SuiCheckpointDatasource; use sui_bridge_indexer::sui_transaction_handler::handle_sui_transactions_loop; use sui_bridge_indexer::sui_transaction_queries::start_sui_tx_polling_task; -use sui_config::Config; +use sui_bridge_watchdog::{ + eth_bridge_status::EthBridgeStatus, eth_vault_balance::EthVaultBalance, + metrics::WatchdogMetrics, sui_bridge_status::SuiBridgeStatus, BridgeWatchDog, +}; use sui_data_ingestion_core::DataIngestionMetrics; use sui_indexer_builder::indexer_builder::{BackfillStrategy, IndexerBuilder}; use sui_indexer_builder::progress::{ @@ -83,14 +91,12 @@ async fn main() -> Result<()> { ProgressSavingPolicy::SaveAfterDuration(SaveAfterDurationPolicy::new( tokio::time::Duration::from_secs(30), )), - indexer_meterics.clone(), ); let datastore_with_out_of_order_source = PgBridgePersistent::new( get_connection_pool(db_url.clone()).await, ProgressSavingPolicy::OutOfOrderSaveAfterDuration(OutOfOrderSaveAfterDurationPolicy::new( tokio::time::Duration::from_secs(30), )), - indexer_meterics.clone(), ); let eth_client: Arc> = Arc::new( @@ -101,15 +107,25 @@ async fn main() -> Result<()> { ) .await?, ); - + let eth_bridge_proxy_address = EthAddress::from_str(&config.eth_sui_bridge_contract_address)?; let mut tasks = vec![]; if Some(true) == config.disable_eth { info!("Eth indexer is disabled"); } else { // Start the eth subscription indexer - let bridge_addresses = vec![EthAddress::from_str( - &config.eth_sui_bridge_contract_address, - )?]; + let bridge_address = EthAddress::from_str(&config.eth_sui_bridge_contract_address)?; + let provider = Arc::new( + Provider::::try_from(&config.eth_rpc_url)? + .interval(std::time::Duration::from_millis(2000)), + ); + let bridge_addresses = get_eth_contract_addresses(bridge_address, &provider).await?; + let bridge_addresses: Vec = vec![ + bridge_address, + bridge_addresses.0, + bridge_addresses.1, + bridge_addresses.2, + bridge_addresses.3, + ]; // Start the eth subscription indexer let eth_subscription_datasource = EthSubscriptionDatasource::new( @@ -164,11 +180,12 @@ async fn main() -> Result<()> { .await?, ); let sui_checkpoint_datasource = SuiCheckpointDatasource::new( - config.remote_store_url, + config.remote_store_url.clone(), sui_client, config.concurrency as usize, config .checkpoints_path + .clone() .map(|p| p.into()) .unwrap_or(tempfile::tempdir()?.into_path()), config.sui_bridge_genesis_checkpoint, @@ -186,17 +203,66 @@ async fn main() -> Result<()> { .build(); tasks.push(spawn_logged_monitored_task!(indexer.start())); + let sui_bridge_client = + Arc::new(SuiBridgeClient::new(&config.sui_rpc_url, bridge_metrics.clone()).await?); + start_watchdog( + config, + eth_bridge_proxy_address, + sui_bridge_client, + ®istry, + bridge_metrics.clone(), + ) + .await?; + // Wait for tasks in `tasks` to finish. Return when anyone of them returns an error. futures::future::try_join_all(tasks).await?; unreachable!("Indexer tasks finished unexpectedly"); } +async fn start_watchdog( + config: IndexerConfig, + eth_bridge_proxy_address: EthAddress, + sui_client: Arc, + registry: &Registry, + bridge_metrics: Arc, +) -> Result<()> { + let watchdog_metrics = WatchdogMetrics::new(registry); + let eth_provider = + Arc::new(new_metered_eth_provider(&config.eth_rpc_url, bridge_metrics.clone()).unwrap()); + let (_committee_address, _limiter_address, vault_address, _config_address, weth_address) = + get_eth_contract_addresses(eth_bridge_proxy_address, ð_provider).await?; + + let eth_vault_balance = EthVaultBalance::new( + eth_provider.clone(), + vault_address, + weth_address, + watchdog_metrics.eth_vault_balance.clone(), + ); + + let eth_bridge_status = EthBridgeStatus::new( + eth_provider, + eth_bridge_proxy_address, + watchdog_metrics.eth_bridge_paused.clone(), + ); + + let sui_bridge_status = + SuiBridgeStatus::new(sui_client, watchdog_metrics.sui_bridge_paused.clone()); + + BridgeWatchDog::new(vec![ + Arc::new(eth_vault_balance), + Arc::new(eth_bridge_status), + Arc::new(sui_bridge_status), + ]) + .run() + .await; + Ok(()) +} + #[allow(unused)] async fn start_processing_sui_checkpoints_by_querying_txns( sui_rpc_url: String, db_url: String, indexer_metrics: BridgeIndexerMetrics, - bridge_metrics: Arc, ) -> Result>> { let pg_pool = get_connection_pool(db_url.clone()).await; let (tx, rx) = channel( @@ -212,7 +278,7 @@ async fn start_processing_sui_checkpoints_by_querying_txns( .expect("Failed to read cursor from sui progress store"); let sui_client = SuiClientBuilder::default().build(sui_rpc_url).await?; handles.push(spawn_logged_monitored_task!( - start_sui_tx_polling_task(sui_client, cursor, tx, bridge_metrics), + start_sui_tx_polling_task(sui_client, cursor, tx), "start_sui_tx_polling_task" )); handles.push(spawn_logged_monitored_task!( diff --git a/crates/sui-bridge-indexer/src/metrics.rs b/crates/sui-bridge-indexer/src/metrics.rs index b24387ac0b828..5a6f778487fe8 100644 --- a/crates/sui-bridge-indexer/src/metrics.rs +++ b/crates/sui-bridge-indexer/src/metrics.rs @@ -17,11 +17,11 @@ pub struct BridgeIndexerMetrics { pub(crate) total_eth_bridge_transactions: IntCounter, pub(crate) total_eth_token_deposited: IntCounter, pub(crate) total_eth_token_transfer_claimed: IntCounter, - pub(crate) total_eth_bridge_txn_other: IntCounter, pub(crate) last_committed_sui_checkpoint: IntGauge, pub(crate) backfill_tasks_remaining_checkpoints: IntGaugeVec, pub(crate) tasks_processed_checkpoints: IntCounterVec, - pub(crate) tasks_current_checkpoints: IntGaugeVec, + pub(crate) tasks_latest_retrieved_checkpoints: IntGaugeVec, + pub(crate) inflight_live_tasks: IntGaugeVec, } impl BridgeIndexerMetrics { @@ -75,12 +75,6 @@ impl BridgeIndexerMetrics { registry, ) .unwrap(), - total_eth_bridge_txn_other: register_int_counter_with_registry!( - "bridge_indexer_total_eth_bridge_txn_other", - "Total number of other eth bridge transactions", - registry, - ) - .unwrap(), last_committed_sui_checkpoint: register_int_gauge_with_registry!( "bridge_indexer_last_committed_sui_checkpoint", "The latest sui checkpoint that indexer committed to DB", @@ -101,13 +95,20 @@ impl BridgeIndexerMetrics { registry, ) .unwrap(), - tasks_current_checkpoints: register_int_gauge_vec_with_registry!( - "bridge_indexer_tasks_current_checkpoints", - "Current checkpoint for each task", + tasks_latest_retrieved_checkpoints: register_int_gauge_vec_with_registry!( + "bridge_indexer_tasks_latest_retrieved_checkpoints", + "latest retrieved checkpoint for each task", &["task_name", "task_type"], registry, ) .unwrap(), + inflight_live_tasks: register_int_gauge_vec_with_registry!( + "bridge_indexer_inflight_live_tasks", + "Number of inflight live tasks", + &["task_name"], + registry, + ) + .unwrap(), } } diff --git a/crates/sui-bridge-indexer/src/migrations/2024-09-05-180103_governance_actions/down.sql b/crates/sui-bridge-indexer/src/migrations/2024-09-05-180103_governance_actions/down.sql new file mode 100644 index 0000000000000..2e98af9155588 --- /dev/null +++ b/crates/sui-bridge-indexer/src/migrations/2024-09-05-180103_governance_actions/down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS governance_actions; diff --git a/crates/sui-bridge-indexer/src/migrations/2024-09-05-180103_governance_actions/up.sql b/crates/sui-bridge-indexer/src/migrations/2024-09-05-180103_governance_actions/up.sql new file mode 100644 index 0000000000000..eb25fee31f20e --- /dev/null +++ b/crates/sui-bridge-indexer/src/migrations/2024-09-05-180103_governance_actions/up.sql @@ -0,0 +1,11 @@ +CREATE TABLE governance_actions +( + id BIGSERIAL PRIMARY KEY, + nonce BIGINT, + data_source TEXT NOT NULL, + txn_digest bytea NOT NULL, + sender_address bytea NOT NULL, + timestamp_ms BIGINT NOT NULL, + action text NOT NULL, + data JSONB NOT NULL +); \ No newline at end of file diff --git a/crates/sui-bridge-indexer/src/models.rs b/crates/sui-bridge-indexer/src/models.rs index 011dc238576ac..5e535e7a311da 100644 --- a/crates/sui-bridge-indexer/src/models.rs +++ b/crates/sui-bridge-indexer/src/models.rs @@ -7,7 +7,8 @@ use diesel::{Identifiable, Insertable, Queryable, Selectable}; use sui_indexer_builder::{Task, LIVE_TASK_TARGET_CHECKPOINT}; use crate::schema::{ - progress_store, sui_error_transactions, sui_progress_store, token_transfer, token_transfer_data, + governance_actions, progress_store, sui_error_transactions, sui_progress_store, token_transfer, + token_transfer_data, }; #[derive(Queryable, Selectable, Insertable, Identifiable, Debug)] @@ -79,3 +80,15 @@ pub struct SuiErrorTransactions { pub failure_status: String, pub cmd_idx: Option, } + +#[derive(Queryable, Selectable, Insertable, Identifiable, Debug)] +#[diesel(table_name = governance_actions, primary_key(txn_digest))] +pub struct GovernanceAction { + pub nonce: Option, + pub data_source: String, + pub txn_digest: Vec, + pub sender_address: Vec, + pub timestamp_ms: i64, + pub action: String, + pub data: serde_json::Value, +} diff --git a/crates/sui-bridge-indexer/src/postgres_manager.rs b/crates/sui-bridge-indexer/src/postgres_manager.rs index 1751bf8e9b15b..3b62af5747462 100644 --- a/crates/sui-bridge-indexer/src/postgres_manager.rs +++ b/crates/sui-bridge-indexer/src/postgres_manager.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::models::SuiProgressStore; +use crate::schema::governance_actions; use crate::schema::sui_progress_store::txn_digest; use crate::schema::{sui_error_transactions, token_transfer_data}; use crate::{schema, schema::token_transfer, ProcessedTxnData}; @@ -36,9 +37,9 @@ pub async fn write(pool: &PgPool, token_txns: Vec) -> Result<( if token_txns.is_empty() { return Ok(()); } - let (transfers, data, errors) = token_txns.iter().fold( - (vec![], vec![], vec![]), - |(mut transfers, mut data, mut errors), d| { + let (transfers, data, errors, gov_actions) = token_txns.iter().fold( + (vec![], vec![], vec![], vec![]), + |(mut transfers, mut data, mut errors, mut gov_actions), d| { match d { ProcessedTxnData::TokenTransfer(t) => { transfers.push(t.to_db()); @@ -47,8 +48,9 @@ pub async fn write(pool: &PgPool, token_txns: Vec) -> Result<( } } ProcessedTxnData::Error(e) => errors.push(e.to_db()), + ProcessedTxnData::GovernanceAction(a) => gov_actions.push(a.to_db()), } - (transfers, data, errors) + (transfers, data, errors, gov_actions) }, ); @@ -112,6 +114,11 @@ pub async fn write(pool: &PgPool, token_txns: Vec) -> Result<( .values(&errors) .on_conflict_do_nothing() .execute(conn) + .await?; + diesel::insert_into(governance_actions::table) + .values(&gov_actions) + .on_conflict_do_nothing() + .execute(conn) .await } .scope_boxed() diff --git a/crates/sui-bridge-indexer/src/schema.rs b/crates/sui-bridge-indexer/src/schema.rs index c0bb997a465b8..3efec3f7ee994 100644 --- a/crates/sui-bridge-indexer/src/schema.rs +++ b/crates/sui-bridge-indexer/src/schema.rs @@ -59,9 +59,23 @@ diesel::table! { } } +diesel::table! { + governance_actions (txn_digest) { + id -> Int8, + nonce -> Nullable, + data_source -> Text, + txn_digest -> Bytea, + sender_address -> Bytea, + timestamp_ms -> Int8, + action -> Text, + data -> Jsonb, + } +} + diesel::allow_tables_to_appear_in_same_query!( progress_store, sui_error_transactions, + governance_actions, sui_progress_store, token_transfer, token_transfer_data, diff --git a/crates/sui-bridge-indexer/src/storage.rs b/crates/sui-bridge-indexer/src/storage.rs index d37c4a32f6df2..5955ca8e1c37c 100644 --- a/crates/sui-bridge-indexer/src/storage.rs +++ b/crates/sui-bridge-indexer/src/storage.rs @@ -12,7 +12,6 @@ use diesel_async::scoped_futures::ScopedFutureExt; use diesel_async::AsyncConnection; use diesel_async::RunQueryDsl; -use crate::metrics::BridgeIndexerMetrics; use crate::postgres_manager::PgPool; use crate::schema::progress_store::{columns, dsl}; use crate::schema::{sui_error_transactions, token_transfer, token_transfer_data}; @@ -27,19 +26,13 @@ use sui_indexer_builder::{ pub struct PgBridgePersistent { pool: PgPool, save_progress_policy: ProgressSavingPolicy, - indexer_metrics: BridgeIndexerMetrics, } impl PgBridgePersistent { - pub fn new( - pool: PgPool, - save_progress_policy: ProgressSavingPolicy, - indexer_metrics: BridgeIndexerMetrics, - ) -> Self { + pub fn new(pool: PgPool, save_progress_policy: ProgressSavingPolicy) -> Self { Self { pool, save_progress_policy, - indexer_metrics, } } } @@ -128,6 +121,13 @@ impl Persistent for PgBridgePersistent { .await?; } } + ProcessedTxnData::GovernanceAction(a) => { + diesel::insert_into(schema::governance_actions::table) + .values(&a.to_db()) + .on_conflict_do_nothing() + .execute(conn) + .await?; + } ProcessedTxnData::Error(e) => { diesel::insert_into(sui_error_transactions::table) .values(&e.to_db()) @@ -169,8 +169,6 @@ impl IndexerProgressStore for PgBridgePersistent { return Ok(None); } let task_name = task.task_name.clone(); - let task_name_prefix = task.name_prefix(); - let task_type_label = task.type_str(); if let Some(checkpoint_to_save) = self .save_progress_policy .cache_progress(task, checkpoint_numbers) @@ -193,10 +191,6 @@ impl IndexerProgressStore for PgBridgePersistent { )) .execute(&mut conn) .await?; - self.indexer_metrics - .tasks_current_checkpoints - .with_label_values(&[task_name_prefix, task_type_label]) - .set(checkpoint_to_save as i64); return Ok(Some(checkpoint_to_save)); } Ok(None) diff --git a/crates/sui-bridge-indexer/src/sui_bridge_indexer.rs b/crates/sui-bridge-indexer/src/sui_bridge_indexer.rs index a6e50444f2124..d377d99ffd1ef 100644 --- a/crates/sui-bridge-indexer/src/sui_bridge_indexer.rs +++ b/crates/sui-bridge-indexer/src/sui_bridge_indexer.rs @@ -2,10 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use anyhow::Error; -use tracing::info; +use tracing::{info, warn}; use sui_bridge::events::{ - MoveTokenDepositedEvent, MoveTokenTransferApproved, MoveTokenTransferClaimed, + EmergencyOpEvent, MoveBlocklistValidatorEvent, MoveNewTokenEvent, MoveTokenDepositedEvent, + MoveTokenRegistrationEvent, MoveTokenTransferApproved, MoveTokenTransferClaimed, + UpdateRouteLimitEvent, UpdateTokenPriceEvent, }; use sui_indexer_builder::indexer_builder::DataMapper; use sui_types::effects::TransactionEffectsAPI; @@ -17,8 +19,8 @@ use sui_types::{BRIDGE_ADDRESS, SUI_BRIDGE_OBJECT_ID}; use crate::metrics::BridgeIndexerMetrics; use crate::sui_datasource::CheckpointTxnData; use crate::{ - BridgeDataSource, ProcessedTxnData, SuiTxnError, TokenTransfer, TokenTransferData, - TokenTransferStatus, + BridgeDataSource, GovernanceAction, GovernanceActionType, ProcessedTxnData, SuiTxnError, + TokenTransfer, TokenTransferData, TokenTransferStatus, }; /// Data mapper impl @@ -146,8 +148,93 @@ fn process_sui_event( is_finalized: true, })) } + "UpdateRouteLimitEvent" => { + info!("Observed Sui Route Limit Update {:?}", ev); + let event: UpdateRouteLimitEvent = bcs::from_bytes(&ev.contents)?; + + Some(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: None, + data_source: BridgeDataSource::Sui, + tx_digest: tx.transaction.digest().inner().to_vec(), + sender: ev.sender.to_vec(), + timestamp_ms, + action: GovernanceActionType::UpdateBridgeLimit, + data: serde_json::to_value(event)?, + })) + } + "EmergencyOpEvent" => { + info!("Observed Sui Emergency Op {:?}", ev); + let event: EmergencyOpEvent = bcs::from_bytes(&ev.contents)?; + + Some(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: None, + data_source: BridgeDataSource::Sui, + tx_digest: tx.transaction.digest().inner().to_vec(), + sender: ev.sender.to_vec(), + timestamp_ms, + action: GovernanceActionType::EmergencyOperation, + data: serde_json::to_value(event)?, + })) + } + "BlocklistValidatorEvent" => { + info!("Observed Sui Blocklist Validator {:?}", ev); + let event: MoveBlocklistValidatorEvent = bcs::from_bytes(&ev.contents)?; + + Some(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: None, + data_source: BridgeDataSource::Sui, + tx_digest: tx.transaction.digest().inner().to_vec(), + sender: ev.sender.to_vec(), + timestamp_ms, + action: GovernanceActionType::UpdateCommitteeBlocklist, + data: serde_json::to_value(event)?, + })) + } + "TokenRegistrationEvent" => { + info!("Observed Sui Token Registration {:?}", ev); + let event: MoveTokenRegistrationEvent = bcs::from_bytes(&ev.contents)?; + + Some(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: None, + data_source: BridgeDataSource::Sui, + tx_digest: tx.transaction.digest().inner().to_vec(), + sender: ev.sender.to_vec(), + timestamp_ms, + action: GovernanceActionType::AddSuiTokens, + data: serde_json::to_value(event)?, + })) + } + "UpdateTokenPriceEvent" => { + info!("Observed Sui Token Price Update {:?}", ev); + let event: UpdateTokenPriceEvent = bcs::from_bytes(&ev.contents)?; + + Some(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: None, + data_source: BridgeDataSource::Sui, + tx_digest: tx.transaction.digest().inner().to_vec(), + sender: ev.sender.to_vec(), + timestamp_ms, + action: GovernanceActionType::UpdateTokenPrices, + data: serde_json::to_value(event)?, + })) + } + "NewTokenEvent" => { + info!("Observed Sui New token event {:?}", ev); + let event: MoveNewTokenEvent = bcs::from_bytes(&ev.contents)?; + + Some(ProcessedTxnData::GovernanceAction(GovernanceAction { + nonce: None, + data_source: BridgeDataSource::Sui, + tx_digest: tx.transaction.digest().inner().to_vec(), + sender: ev.sender.to_vec(), + timestamp_ms, + action: GovernanceActionType::AddSuiTokens, + data: serde_json::to_value(event)?, + })) + } _ => { // todo: metrics.total_sui_bridge_txn_other.inc(); + warn!("Unexpected event {ev:?}."); None } } diff --git a/crates/sui-bridge-indexer/src/sui_datasource.rs b/crates/sui-bridge-indexer/src/sui_datasource.rs index 2c7ad6f0feab9..c292992298670 100644 --- a/crates/sui-bridge-indexer/src/sui_datasource.rs +++ b/crates/sui-bridge-indexer/src/sui_datasource.rs @@ -5,6 +5,7 @@ use anyhow::Error; use async_trait::async_trait; use mysten_metrics::{metered_channel, spawn_monitored_task}; use prometheus::IntCounterVec; +use prometheus::IntGauge; use prometheus::IntGaugeVec; use std::path::PathBuf; use std::sync::Arc; @@ -85,7 +86,11 @@ impl Datasource for SuiCheckpointDatasource { ingestion_reader_batch_size ); let mut executor = IndexerExecutor::new(progress_store, 1, self.metrics.clone()); - let worker = IndexerWorker::new(data_sender); + let progress_metric = self + .indexer_metrics + .tasks_latest_retrieved_checkpoints + .with_label_values(&[task.name_prefix(), task.type_str()]); + let worker = IndexerWorker::new(data_sender, progress_metric); let worker_pool = WorkerPool::new(worker, task.task_name.clone(), self.concurrency); executor.register(worker_pool).await?; let checkpoint_path = self.checkpoint_path.clone(); @@ -126,6 +131,10 @@ impl Datasource for SuiCheckpointDatasource { fn get_tasks_processed_checkpoints_metric(&self) -> &IntCounterVec { &self.indexer_metrics.tasks_processed_checkpoints } + + fn get_inflight_live_tasks_metrics(&self) -> &IntGaugeVec { + &self.indexer_metrics.inflight_live_tasks + } } struct PerTaskInMemProgressStore { @@ -168,11 +177,18 @@ impl ProgressStore for PerTaskInMemProgressStore { pub struct IndexerWorker { data_sender: metered_channel::Sender<(u64, Vec)>, + progress_metric: IntGauge, } impl IndexerWorker { - pub fn new(data_sender: metered_channel::Sender<(u64, Vec)>) -> Self { - Self { data_sender } + pub fn new( + data_sender: metered_channel::Sender<(u64, Vec)>, + progress_metric: IntGauge, + ) -> Self { + Self { + data_sender, + progress_metric, + } } } @@ -180,6 +196,8 @@ pub type CheckpointTxnData = (CheckpointTransaction, u64, u64); #[async_trait] impl Worker for IndexerWorker { + type Result = (); + async fn process_checkpoint(&self, checkpoint: &SuiCheckpointData) -> anyhow::Result<()> { tracing::trace!( "Received checkpoint [{}] {}: {}", @@ -196,9 +214,10 @@ impl Worker for IndexerWorker { .into_iter() .map(|tx| (tx, checkpoint_num, timestamp_ms)) .collect(); - Ok(self - .data_sender + self.data_sender .send((checkpoint_num, transactions)) - .await?) + .await?; + self.progress_metric.set(checkpoint_num as i64); + Ok(()) } } diff --git a/crates/sui-bridge-indexer/src/sui_transaction_queries.rs b/crates/sui-bridge-indexer/src/sui_transaction_queries.rs index beb28eaaa6709..38c8a523354fa 100644 --- a/crates/sui-bridge-indexer/src/sui_transaction_queries.rs +++ b/crates/sui-bridge-indexer/src/sui_transaction_queries.rs @@ -1,7 +1,6 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::sync::Arc; use std::time::Duration; use sui_json_rpc_types::SuiTransactionBlockResponseOptions; use sui_json_rpc_types::SuiTransactionBlockResponseQuery; @@ -10,7 +9,7 @@ use sui_sdk::SuiClient; use sui_types::digests::TransactionDigest; use sui_types::SUI_BRIDGE_OBJECT_ID; -use sui_bridge::{metrics::BridgeMetrics, retry_with_max_elapsed_time}; +use sui_bridge::retry_with_max_elapsed_time; use tracing::{error, info}; use crate::types::RetrievedTransaction; @@ -25,7 +24,6 @@ pub async fn start_sui_tx_polling_task( Vec, Option, )>, - metrics: Arc, ) { info!("Starting SUI transaction polling task from {:?}", cursor); loop { @@ -68,12 +66,9 @@ pub async fn start_sui_tx_polling_task( tokio::time::sleep(QUERY_DURATION).await; continue; } - // Unwrap: txes is not empty - let ckp = txes.last().unwrap().checkpoint; tx.send((txes, results.next_cursor)) .await .expect("Failed to send transaction block to process"); - metrics.last_synced_sui_checkpoint.set(ckp as i64); cursor = results.next_cursor; } } diff --git a/crates/sui-bridge-watchdog/Cargo.toml b/crates/sui-bridge-watchdog/Cargo.toml new file mode 100644 index 0000000000000..b6148e6bd6222 --- /dev/null +++ b/crates/sui-bridge-watchdog/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "sui-bridge-watchdog" +version = "0.1.0" +authors = ["Mysten Labs "] +license = "Apache-2.0" +publish = false +edition = "2021" + +[dependencies] +sui-bridge.workspace = true +mysten-metrics.workspace = true +prometheus.workspace = true +anyhow.workspace = true +futures.workspace = true +async-trait.workspace = true +ethers = { version = "2.0" } +tracing.workspace = true +tokio = { workspace = true, features = ["full"] } diff --git a/crates/sui-bridge-watchdog/src/eth_bridge_status.rs b/crates/sui-bridge-watchdog/src/eth_bridge_status.rs new file mode 100644 index 0000000000000..cdd795f2f71f9 --- /dev/null +++ b/crates/sui-bridge-watchdog/src/eth_bridge_status.rs @@ -0,0 +1,58 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! The EthBridgeStatus observable monitors whether the Eth Bridge is paused. + +use crate::Observable; +use async_trait::async_trait; +use ethers::providers::Provider; +use ethers::types::Address as EthAddress; +use prometheus::IntGauge; +use std::sync::Arc; +use sui_bridge::abi::EthSuiBridge; +use sui_bridge::metered_eth_provider::MeteredEthHttpProvier; +use tokio::time::Duration; +use tracing::{error, info}; + +pub struct EthBridgeStatus { + bridge_contract: EthSuiBridge>, + metric: IntGauge, +} + +impl EthBridgeStatus { + pub fn new( + provider: Arc>, + bridge_address: EthAddress, + metric: IntGauge, + ) -> Self { + let bridge_contract = EthSuiBridge::new(bridge_address, provider.clone()); + Self { + bridge_contract, + metric, + } + } +} + +#[async_trait] +impl Observable for EthBridgeStatus { + fn name(&self) -> &str { + "EthBridgeStatus" + } + + async fn observe_and_report(&self) { + let status = self.bridge_contract.paused().call().await; + match status { + Ok(status) => { + self.metric.set(status as i64); + info!("Eth Bridge Status: {:?}", status); + } + Err(e) => { + error!("Error getting eth bridge status: {:?}", e); + } + } + } + + fn interval(&self) -> Duration { + Duration::from_secs(10) + } +} diff --git a/crates/sui-bridge-watchdog/src/eth_vault_balance.rs b/crates/sui-bridge-watchdog/src/eth_vault_balance.rs new file mode 100644 index 0000000000000..dfc359e0cb393 --- /dev/null +++ b/crates/sui-bridge-watchdog/src/eth_vault_balance.rs @@ -0,0 +1,75 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::Observable; +use async_trait::async_trait; +use ethers::providers::Provider; +use ethers::types::{Address as EthAddress, U256}; +use prometheus::IntGauge; +use std::sync::Arc; +use sui_bridge::abi::EthERC20; +use sui_bridge::metered_eth_provider::MeteredEthHttpProvier; +use tokio::time::Duration; +use tracing::{error, info}; + +const TEN_ZEROS: u64 = 10_u64.pow(10); + +pub struct EthVaultBalance { + coin_contract: EthERC20>, + vault_address: EthAddress, + ten_zeros: U256, + metric: IntGauge, +} + +impl EthVaultBalance { + pub fn new( + provider: Arc>, + vault_address: EthAddress, + coin_address: EthAddress, // for now this only support one coin which is WETH + metric: IntGauge, + ) -> Self { + let ten_zeros = U256::from(TEN_ZEROS); + let coin_contract = EthERC20::new(coin_address, provider); + Self { + coin_contract, + vault_address, + ten_zeros, + metric, + } + } +} + +#[async_trait] +impl Observable for EthVaultBalance { + fn name(&self) -> &str { + "EthVaultBalance" + } + + async fn observe_and_report(&self) { + match self + .coin_contract + .balance_of(self.vault_address) + .call() + .await + { + Ok(balance) => { + // Why downcasting is safe: + // 1. On Ethereum we only take the first 8 decimals into account, + // meaning the trailing 10 digits can be ignored + // 2. i64::MAX is 9_223_372_036_854_775_807, with 8 decimal places is + // 92_233_720_368. We likely won't see any balance higher than this + // in the next 12 months. + let balance = (balance / self.ten_zeros).as_u64() as i64; + self.metric.set(balance); + info!("Eth Vault Balance: {:?}", balance); + } + Err(e) => { + error!("Error getting balance from vault: {:?}", e); + } + } + } + + fn interval(&self) -> Duration { + Duration::from_secs(10) + } +} diff --git a/crates/sui-bridge-watchdog/src/lib.rs b/crates/sui-bridge-watchdog/src/lib.rs new file mode 100644 index 0000000000000..b78e436fd696a --- /dev/null +++ b/crates/sui-bridge-watchdog/src/lib.rs @@ -0,0 +1,62 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! The BridgeWatchDog module is responsible for monitoring the health +//! of the bridge by periodically running various observables and +//! reporting the results. + +use anyhow::Result; +use async_trait::async_trait; +use mysten_metrics::spawn_logged_monitored_task; +use std::sync::Arc; +use tokio::time::Duration; +use tokio::time::MissedTickBehavior; +use tracing::{error_span, info, Instrument}; + +pub mod eth_bridge_status; +pub mod eth_vault_balance; +pub mod metrics; +pub mod sui_bridge_status; + +pub struct BridgeWatchDog { + observables: Vec>, +} + +impl BridgeWatchDog { + pub fn new(observables: Vec>) -> Self { + Self { observables } + } + + pub async fn run(self) { + let mut handles = vec![]; + for observable in self.observables.into_iter() { + let handle = spawn_logged_monitored_task!(Self::run_observable(observable)); + handles.push(handle); + } + // Return when any task returns an error or all tasks exit. + futures::future::try_join_all(handles).await.unwrap(); + unreachable!("watch dog tasks should not exit"); + } + + async fn run_observable(observable: Arc) -> Result<()> { + let mut interval = tokio::time::interval(observable.interval()); + interval.set_missed_tick_behavior(MissedTickBehavior::Skip); + let name = observable.name(); + let span = error_span!("observable", name); + loop { + info!("Running observable {}", name); + observable + .observe_and_report() + .instrument(span.clone()) + .await; + interval.tick().await; + } + } +} + +#[async_trait] +pub trait Observable { + fn name(&self) -> &str; + async fn observe_and_report(&self); + fn interval(&self) -> Duration; +} diff --git a/crates/sui-bridge-watchdog/src/metrics.rs b/crates/sui-bridge-watchdog/src/metrics.rs new file mode 100644 index 0000000000000..c33d2e4876e3b --- /dev/null +++ b/crates/sui-bridge-watchdog/src/metrics.rs @@ -0,0 +1,41 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use prometheus::{register_int_gauge_with_registry, IntGauge, Registry}; + +#[derive(Clone, Debug)] +pub struct WatchdogMetrics { + pub eth_vault_balance: IntGauge, + pub eth_bridge_paused: IntGauge, + pub sui_bridge_paused: IntGauge, +} + +impl WatchdogMetrics { + pub fn new(registry: &Registry) -> Self { + Self { + eth_vault_balance: register_int_gauge_with_registry!( + "bridge_eth_vault_balance", + "Current balance of eth vault", + registry, + ) + .unwrap(), + eth_bridge_paused: register_int_gauge_with_registry!( + "bridge_eth_bridge_paused", + "Whether the eth bridge is paused", + registry, + ) + .unwrap(), + sui_bridge_paused: register_int_gauge_with_registry!( + "bridge_sui_bridge_paused", + "Whether the sui bridge is paused", + registry, + ) + .unwrap(), + } + } + + pub fn new_for_testing() -> Self { + let registry = Registry::new(); + Self::new(®istry) + } +} diff --git a/crates/sui-bridge-watchdog/src/sui_bridge_status.rs b/crates/sui-bridge-watchdog/src/sui_bridge_status.rs new file mode 100644 index 0000000000000..09e5b5adf9cb3 --- /dev/null +++ b/crates/sui-bridge-watchdog/src/sui_bridge_status.rs @@ -0,0 +1,48 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//! The SuiBridgeStatus observable monitors whether the Sui Bridge is paused. + +use crate::Observable; +use async_trait::async_trait; +use prometheus::IntGauge; +use std::sync::Arc; +use sui_bridge::sui_client::SuiBridgeClient; + +use tokio::time::Duration; +use tracing::{error, info}; + +pub struct SuiBridgeStatus { + sui_client: Arc, + metric: IntGauge, +} + +impl SuiBridgeStatus { + pub fn new(sui_client: Arc, metric: IntGauge) -> Self { + Self { sui_client, metric } + } +} + +#[async_trait] +impl Observable for SuiBridgeStatus { + fn name(&self) -> &str { + "SuiBridgeStatus" + } + + async fn observe_and_report(&self) { + let status = self.sui_client.is_bridge_paused().await; + match status { + Ok(status) => { + self.metric.set(status as i64); + info!("Sui Bridge Status: {:?}", status); + } + Err(e) => { + error!("Error getting sui bridge status: {:?}", e); + } + } + } + + fn interval(&self) -> Duration { + Duration::from_secs(2) + } +} diff --git a/crates/sui-bridge/abi/bridge_committee.json b/crates/sui-bridge/abi/bridge_committee.json index 23ff17267c16d..107675bd3ae69 100644 --- a/crates/sui-bridge/abi/bridge_committee.json +++ b/crates/sui-bridge/abi/bridge_committee.json @@ -10,6 +10,75 @@ "name": "AddressEmptyCode", "type": "error" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address[]", + "name": "updatedMembers", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isBlocklisted", + "type": "bool" + } + ], + "name": "BlocklistUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "updatedMembers", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isBlocklisted", + "type": "bool" + } + ], + "name": "BlocklistUpdatedV2", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "ContractUpgraded", + "type": "event" + }, { "inputs": [ { @@ -31,6 +100,19 @@ "name": "FailedInnerCall", "type": "error" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, { "inputs": [], "name": "InvalidInitialization", @@ -46,6 +128,19 @@ "name": "ReentrancyGuardReentrantCall", "type": "error" }, + { + "inputs": [], + "name": "UPGRADE_INTERFACE_VERSION", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "UUPSUnauthorizedCallContext", @@ -62,38 +157,6 @@ "name": "UUPSUnsupportedProxiableUUID", "type": "error" }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address[]", - "name": "updatedMembers", - "type": "address[]" - }, - { - "indexed": false, - "internalType": "bool", - "name": "isBlocklisted", - "type": "bool" - } - ], - "name": "BlocklistUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint64", - "name": "version", - "type": "uint64" - } - ], - "name": "Initialized", - "type": "event" - }, { "anonymous": false, "inputs": [ @@ -107,19 +170,6 @@ "name": "Upgraded", "type": "event" }, - { - "inputs": [], - "name": "UPGRADE_INTERFACE_VERSION", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { diff --git a/crates/sui-bridge/abi/bridge_committee_upgradeable.json b/crates/sui-bridge/abi/bridge_committee_upgradeable.json index 9bd3d2624650d..7e55f6ca24f3a 100644 --- a/crates/sui-bridge/abi/bridge_committee_upgradeable.json +++ b/crates/sui-bridge/abi/bridge_committee_upgradeable.json @@ -1,212 +1,212 @@ [ { - "type": "function", - "name": "UPGRADE_INTERFACE_VERSION", + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "ERC1967InvalidImplementation", + "type": "error" + }, + { + "inputs": [], + "name": "ERC1967NonPayable", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { "inputs": [], + "name": "UPGRADE_INTERFACE_VERSION", "outputs": [ { + "internalType": "string", "name": "", - "type": "string", - "internalType": "string" + "type": "string" } ], - "stateMutability": "view" + "stateMutability": "view", + "type": "function" }, { - "type": "function", - "name": "committee", "inputs": [], + "name": "UUPSUnauthorizedCallContext", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "slot", + "type": "bytes32" + } + ], + "name": "UUPSUnsupportedProxiableUUID", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "inputs": [], + "name": "committee", "outputs": [ { + "internalType": "contract IBridgeCommittee", "name": "", - "type": "address", - "internalType": "contract IBridgeCommittee" + "type": "address" } ], - "stateMutability": "view" + "stateMutability": "view", + "type": "function" }, { - "type": "function", - "name": "nonces", "inputs": [ { + "internalType": "uint8", "name": "messageType", - "type": "uint8", - "internalType": "uint8" + "type": "uint8" } ], + "name": "nonces", "outputs": [ { + "internalType": "uint64", "name": "nonce", - "type": "uint64", - "internalType": "uint64" + "type": "uint64" } ], - "stateMutability": "view" + "stateMutability": "view", + "type": "function" }, { - "type": "function", - "name": "proxiableUUID", "inputs": [], + "name": "proxiableUUID", "outputs": [ { + "internalType": "bytes32", "name": "", - "type": "bytes32", - "internalType": "bytes32" + "type": "bytes32" } ], - "stateMutability": "view" + "stateMutability": "view", + "type": "function" }, { - "type": "function", - "name": "upgradeToAndCall", "inputs": [ { + "internalType": "address", "name": "newImplementation", - "type": "address", - "internalType": "address" + "type": "address" }, { + "internalType": "bytes", "name": "data", - "type": "bytes", - "internalType": "bytes" + "type": "bytes" } ], + "name": "upgradeToAndCall", "outputs": [], - "stateMutability": "payable" + "stateMutability": "payable", + "type": "function" }, { - "type": "function", - "name": "upgradeWithSignatures", "inputs": [ { + "internalType": "bytes[]", "name": "signatures", - "type": "bytes[]", - "internalType": "bytes[]" + "type": "bytes[]" }, { - "name": "message", - "type": "tuple", - "internalType": "struct BridgeUtils.Message", "components": [ { + "internalType": "uint8", "name": "messageType", - "type": "uint8", - "internalType": "uint8" + "type": "uint8" }, { + "internalType": "uint8", "name": "version", - "type": "uint8", - "internalType": "uint8" + "type": "uint8" }, { + "internalType": "uint64", "name": "nonce", - "type": "uint64", - "internalType": "uint64" + "type": "uint64" }, { + "internalType": "uint8", "name": "chainID", - "type": "uint8", - "internalType": "uint8" + "type": "uint8" }, { + "internalType": "bytes", "name": "payload", - "type": "bytes", - "internalType": "bytes" + "type": "bytes" } - ] + ], + "internalType": "struct BridgeUtils.Message", + "name": "message", + "type": "tuple" } ], + "name": "upgradeWithSignatures", "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "event", - "name": "Initialized", - "inputs": [ - { - "name": "version", - "type": "uint64", - "indexed": false, - "internalType": "uint64" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "Upgraded", - "inputs": [ - { - "name": "implementation", - "type": "address", - "indexed": true, - "internalType": "address" - } - ], - "anonymous": false - }, - { - "type": "error", - "name": "AddressEmptyCode", - "inputs": [ - { - "name": "target", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "ERC1967InvalidImplementation", - "inputs": [ - { - "name": "implementation", - "type": "address", - "internalType": "address" - } - ] - }, - { - "type": "error", - "name": "ERC1967NonPayable", - "inputs": [] - }, - { - "type": "error", - "name": "FailedInnerCall", - "inputs": [] - }, - { - "type": "error", - "name": "InvalidInitialization", - "inputs": [] - }, - { - "type": "error", - "name": "NotInitializing", - "inputs": [] - }, - { - "type": "error", - "name": "ReentrancyGuardReentrantCall", - "inputs": [] - }, - { - "type": "error", - "name": "UUPSUnauthorizedCallContext", - "inputs": [] - }, - { - "type": "error", - "name": "UUPSUnsupportedProxiableUUID", - "inputs": [ - { - "name": "slot", - "type": "bytes32", - "internalType": "bytes32" - } - ] + "stateMutability": "nonpayable", + "type": "function" } -] \ No newline at end of file +] diff --git a/crates/sui-bridge/abi/bridge_config.json b/crates/sui-bridge/abi/bridge_config.json index 48997486cd1c7..7d58f7ec92a10 100644 --- a/crates/sui-bridge/abi/bridge_config.json +++ b/crates/sui-bridge/abi/bridge_config.json @@ -10,6 +10,31 @@ "name": "AddressEmptyCode", "type": "error" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "ContractUpgraded", + "type": "event" + }, { "inputs": [ { @@ -31,6 +56,19 @@ "name": "FailedInnerCall", "type": "error" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, { "inputs": [], "name": "InvalidInitialization", @@ -46,35 +84,6 @@ "name": "ReentrancyGuardReentrantCall", "type": "error" }, - { - "inputs": [], - "name": "UUPSUnauthorizedCallContext", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "slot", - "type": "bytes32" - } - ], - "name": "UUPSUnsupportedProxiableUUID", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint64", - "name": "version", - "type": "uint64" - } - ], - "name": "Initialized", - "type": "event" - }, { "anonymous": false, "inputs": [ @@ -129,13 +138,62 @@ "anonymous": false, "inputs": [ { - "indexed": true, - "internalType": "address", - "name": "implementation", - "type": "address" + "indexed": false, + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "tokenID", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "tokenPrice", + "type": "uint64" } ], - "name": "Upgraded", + "name": "TokenPriceUpdatedV2", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint8[]", + "name": "tokenIDs", + "type": "uint8[]" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "tokenAddresses", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "uint8[]", + "name": "suiDecimals", + "type": "uint8[]" + }, + { + "indexed": false, + "internalType": "uint64[]", + "name": "tokenPrices", + "type": "uint64[]" + } + ], + "name": "TokensAddedV2", "type": "event" }, { @@ -151,6 +209,35 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "UUPSUnauthorizedCallContext", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "slot", + "type": "bytes32" + } + ], + "name": "UUPSUnsupportedProxiableUUID", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, { "inputs": [ { diff --git a/crates/sui-bridge/abi/bridge_limiter.json b/crates/sui-bridge/abi/bridge_limiter.json index fb0169a17e3a5..3097723e54344 100644 --- a/crates/sui-bridge/abi/bridge_limiter.json +++ b/crates/sui-bridge/abi/bridge_limiter.json @@ -11,77 +11,49 @@ "type": "error" }, { + "anonymous": false, "inputs": [ { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "indexed": false, "internalType": "address", - "name": "implementation", + "name": "proxy", "type": "address" - } - ], - "name": "ERC1967InvalidImplementation", - "type": "error" - }, - { - "inputs": [], - "name": "ERC1967NonPayable", - "type": "error" - }, - { - "inputs": [], - "name": "FailedInnerCall", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidInitialization", - "type": "error" - }, - { - "inputs": [], - "name": "NotInitializing", - "type": "error" - }, - { - "inputs": [ + }, { + "indexed": false, "internalType": "address", - "name": "owner", + "name": "implementation", "type": "address" } ], - "name": "OwnableInvalidOwner", - "type": "error" + "name": "ContractUpgraded", + "type": "event" }, { "inputs": [ { "internalType": "address", - "name": "account", + "name": "implementation", "type": "address" } ], - "name": "OwnableUnauthorizedAccount", + "name": "ERC1967InvalidImplementation", "type": "error" }, { "inputs": [], - "name": "ReentrancyGuardReentrantCall", + "name": "ERC1967NonPayable", "type": "error" }, { "inputs": [], - "name": "UUPSUnauthorizedCallContext", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "slot", - "type": "bytes32" - } - ], - "name": "UUPSUnsupportedProxiableUUID", + "name": "FailedInnerCall", "type": "error" }, { @@ -116,6 +88,11 @@ "name": "Initialized", "type": "event" }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, { "anonymous": false, "inputs": [ @@ -139,20 +116,53 @@ "anonymous": false, "inputs": [ { - "indexed": true, + "indexed": false, + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "sourceChainID", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "newLimit", + "type": "uint64" + } + ], + "name": "LimitUpdatedV2", + "type": "event" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", - "name": "previousOwner", + "name": "owner", "type": "address" - }, + } + ], + "name": "OwnableInvalidOwner", + "type": "error" + }, + { + "inputs": [ { - "indexed": true, "internalType": "address", - "name": "newOwner", + "name": "account", "type": "address" } ], - "name": "OwnershipTransferred", - "type": "event" + "name": "OwnableUnauthorizedAccount", + "type": "error" }, { "anonymous": false, @@ -160,13 +170,24 @@ { "indexed": true, "internalType": "address", - "name": "implementation", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", "type": "address" } ], - "name": "Upgraded", + "name": "OwnershipTransferred", "type": "event" }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, { "inputs": [], "name": "UPGRADE_INTERFACE_VERSION", @@ -180,6 +201,35 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "UUPSUnauthorizedCallContext", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "slot", + "type": "bytes32" + } + ], + "name": "UUPSUnsupportedProxiableUUID", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, { "inputs": [ { diff --git a/crates/sui-bridge/abi/bridge_vault.json b/crates/sui-bridge/abi/bridge_vault.json index 71a0893fd9272..8367d4c316f92 100644 --- a/crates/sui-bridge/abi/bridge_vault.json +++ b/crates/sui-bridge/abi/bridge_vault.json @@ -10,6 +10,37 @@ "stateMutability": "nonpayable", "type": "constructor" }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, { "inputs": [ { @@ -51,6 +82,22 @@ "name": "OwnershipTransferred", "type": "event" }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, { "inputs": [], "name": "owner", @@ -137,9 +184,5 @@ ], "stateMutability": "view", "type": "function" - }, - { - "stateMutability": "payable", - "type": "receive" } ] diff --git a/crates/sui-bridge/abi/sui_bridge.json b/crates/sui-bridge/abi/sui_bridge.json index 273877f530af8..b49e56ac8a8ea 100644 --- a/crates/sui-bridge/abi/sui_bridge.json +++ b/crates/sui-bridge/abi/sui_bridge.json @@ -14,62 +14,86 @@ "inputs": [ { "internalType": "address", - "name": "implementation", + "name": "account", "type": "address" } ], - "name": "ERC1967InvalidImplementation", - "type": "error" - }, - { - "inputs": [], - "name": "ERC1967NonPayable", + "name": "AddressInsufficientBalance", "type": "error" }, { - "inputs": [], - "name": "EnforcedPause", - "type": "error" + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "proxy", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "ContractUpgraded", + "type": "event" }, { - "inputs": [], - "name": "ExpectedPause", + "inputs": [ + { + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "ERC1967InvalidImplementation", "type": "error" }, { "inputs": [], - "name": "FailedInnerCall", + "name": "ERC1967NonPayable", "type": "error" }, { - "inputs": [], - "name": "InvalidInitialization", - "type": "error" + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "nonce", + "type": "uint64" + }, + { + "indexed": false, + "internalType": "bool", + "name": "paused", + "type": "bool" + } + ], + "name": "EmergencyOperation", + "type": "event" }, { "inputs": [], - "name": "NotInitializing", + "name": "EnforcedPause", "type": "error" }, { "inputs": [], - "name": "ReentrancyGuardReentrantCall", + "name": "ExpectedPause", "type": "error" }, { "inputs": [], - "name": "UUPSUnauthorizedCallContext", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "slot", - "type": "bytes32" - } - ], - "name": "UUPSUnsupportedProxiableUUID", + "name": "FailedInnerCall", "type": "error" }, { @@ -85,6 +109,16 @@ "name": "Initialized", "type": "event" }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, { "anonymous": false, "inputs": [ @@ -98,6 +132,22 @@ "name": "Paused", "type": "event" }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, { "anonymous": false, "inputs": [ @@ -196,6 +246,35 @@ "name": "TokensDeposited", "type": "event" }, + { + "inputs": [], + "name": "UPGRADE_INTERFACE_VERSION", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "UUPSUnauthorizedCallContext", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "slot", + "type": "bytes32" + } + ], + "name": "UUPSUnsupportedProxiableUUID", + "type": "error" + }, { "anonymous": false, "inputs": [ @@ -222,19 +301,6 @@ "name": "Upgraded", "type": "event" }, - { - "inputs": [], - "name": "UPGRADE_INTERFACE_VERSION", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, { "inputs": [ { @@ -355,11 +421,6 @@ "internalType": "address", "name": "_limiter", "type": "address" - }, - { - "internalType": "address", - "name": "_wETH", - "type": "address" } ], "name": "initialize", @@ -564,18 +625,5 @@ ], "stateMutability": "view", "type": "function" - }, - { - "inputs": [], - "name": "wETH", - "outputs": [ - { - "internalType": "contract IWETH9", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" } ] diff --git a/crates/sui-bridge/src/abi.rs b/crates/sui-bridge/src/abi.rs index d1b844402be70..892f864727335 100644 --- a/crates/sui-bridge/src/abi.rs +++ b/crates/sui-bridge/src/abi.rs @@ -134,12 +134,16 @@ impl EthBridgeEvent { EthSuiBridgeEvents::UnpausedFilter(_event) => None, EthSuiBridgeEvents::UpgradedFilter(_event) => None, EthSuiBridgeEvents::InitializedFilter(_event) => None, + EthSuiBridgeEvents::ContractUpgradedFilter(_event) => None, + EthSuiBridgeEvents::EmergencyOperationFilter(_event) => None, } } EthBridgeEvent::EthBridgeCommitteeEvents(event) => match event { EthBridgeCommitteeEvents::BlocklistUpdatedFilter(_event) => None, EthBridgeCommitteeEvents::InitializedFilter(_event) => None, EthBridgeCommitteeEvents::UpgradedFilter(_event) => None, + EthBridgeCommitteeEvents::BlocklistUpdatedV2Filter(_event) => None, + EthBridgeCommitteeEvents::ContractUpgradedFilter(_event) => None, }, EthBridgeEvent::EthBridgeLimiterEvents(event) => match event { EthBridgeLimiterEvents::LimitUpdatedFilter(_event) => None, @@ -147,12 +151,17 @@ impl EthBridgeEvent { EthBridgeLimiterEvents::UpgradedFilter(_event) => None, EthBridgeLimiterEvents::HourlyTransferAmountUpdatedFilter(_event) => None, EthBridgeLimiterEvents::OwnershipTransferredFilter(_event) => None, + EthBridgeLimiterEvents::ContractUpgradedFilter(_event) => None, + EthBridgeLimiterEvents::LimitUpdatedV2Filter(_event) => None, }, EthBridgeEvent::EthBridgeConfigEvents(event) => match event { EthBridgeConfigEvents::InitializedFilter(_event) => None, EthBridgeConfigEvents::UpgradedFilter(_event) => None, EthBridgeConfigEvents::TokenAddedFilter(_event) => None, EthBridgeConfigEvents::TokenPriceUpdatedFilter(_event) => None, + EthBridgeConfigEvents::ContractUpgradedFilter(_event) => None, + EthBridgeConfigEvents::TokenPriceUpdatedV2Filter(_event) => None, + EthBridgeConfigEvents::TokensAddedV2Filter(_event) => None, }, EthBridgeEvent::EthCommitteeUpgradeableContractEvents(event) => match event { EthCommitteeUpgradeableContractEvents::InitializedFilter(_event) => None, diff --git a/crates/sui-bridge/src/action_executor.rs b/crates/sui-bridge/src/action_executor.rs index 113838ab32df7..e8d54728e4011 100644 --- a/crates/sui-bridge/src/action_executor.rs +++ b/crates/sui-bridge/src/action_executor.rs @@ -378,6 +378,7 @@ where // TODO: spawn a task for this if attempt_times >= MAX_SIGNING_ATTEMPTS { + metrics.err_signature_aggregation_too_many_failures.inc(); error!("Manual intervention is required. Failed to collect sigs for bridge action after {MAX_SIGNING_ATTEMPTS} attempts: {:?}", e); return; } @@ -542,10 +543,10 @@ where let sender_clone = execution_queue_sender.clone(); spawn_logged_monitored_task!(async move { // If it fails for too many times, log and ask for manual intervention. - metrics_clone - .err_sui_transaction_submission_too_many_failures - .inc(); if attempt_times >= MAX_EXECUTION_ATTEMPTS { + metrics_clone + .err_sui_transaction_submission_too_many_failures + .inc(); error!("Manual intervention is required. Failed to collect execute transaction for bridge action after {MAX_EXECUTION_ATTEMPTS} attempts: {:?}", err); return; } @@ -1359,6 +1360,7 @@ mod tests { sui_tx_digest, sui_tx_event_index, Ok(signed_action.clone()), + None, ); signed_actions.insert(secret.public().into(), signed_action.into_sig().signature); } @@ -1375,6 +1377,7 @@ mod tests { sui_tx_digest, sui_tx_event_index, Err(BridgeError::RestAPIError("small issue".into())), + None, ); } } diff --git a/crates/sui-bridge/src/client/bridge_authority_aggregator.rs b/crates/sui-bridge/src/client/bridge_authority_aggregator.rs index 882e689f341f2..1de1c41635a7e 100644 --- a/crates/sui-bridge/src/client/bridge_authority_aggregator.rs +++ b/crates/sui-bridge/src/client/bridge_authority_aggregator.rs @@ -17,13 +17,16 @@ use std::collections::BTreeMap; use std::collections::BTreeSet; use std::sync::Arc; use std::time::Duration; -use sui_authority_aggregation::quorum_map_then_reduce_with_timeout_and_prefs; use sui_authority_aggregation::ReduceOutput; +use sui_authority_aggregation::{quorum_map_then_reduce_with_timeout_and_prefs, SigRequestPrefs}; use sui_types::base_types::ConciseableName; use sui_types::committee::StakeUnit; use sui_types::committee::TOTAL_VOTING_POWER; use tracing::{error, info, warn}; +const TOTAL_TIMEOUT_MS: u64 = 5000; +const PREFETCH_TIMEOUT_MS: u64 = 1500; + pub struct BridgeAuthorityAggregator { pub committee: Arc, pub clients: Arc>>, @@ -72,6 +75,7 @@ impl BridgeAuthorityAggregator { self.committee.clone(), self.clients.clone(), state, + Duration::from_secs(PREFETCH_TIMEOUT_MS), ) .await } @@ -167,6 +171,7 @@ async fn request_sign_bridge_action_into_certification( committee: Arc, clients: Arc>>, state: GetSigsState, + prefetch_timeout: Duration, ) -> BridgeResult { // `preferences` is used as a trick here to influence the order of validators to be requested. // * if `Some(_)`, then we will request validators in the order of the voting power. @@ -175,20 +180,26 @@ async fn request_sign_bridge_action_into_certification( // we pass in `Some` to make sure the validators with higher voting power are requested first // to save gas cost. let preference = match action { - BridgeAction::SuiToEthBridgeAction(_) => Some(BTreeSet::new()), + BridgeAction::SuiToEthBridgeAction(_) => Some(SigRequestPrefs { + ordering_pref: BTreeSet::new(), + prefetch_timeout, + }), BridgeAction::EthToSuiBridgeAction(_) => None, _ => { if action.chain_id().is_sui_chain() { None } else { - Some(BTreeSet::new()) + Some(SigRequestPrefs { + ordering_pref: BTreeSet::new(), + prefetch_timeout, + }) } } }; let (result, _) = quorum_map_then_reduce_with_timeout_and_prefs( committee, clients, - preference.as_ref(), + preference, state, |_name, client| { Box::pin(async move { client.request_sign_bridge_action(action.clone()).await }) @@ -234,8 +245,7 @@ async fn request_sign_bridge_action_into_certification( } }) }, - // A herustic timeout, we expect the signing to finish within 5 seconds - Duration::from_secs(5), + Duration::from_secs(TOTAL_TIMEOUT_MS), ) .await .map_err(|state| { @@ -362,21 +372,25 @@ mod tests { sui_tx_digest, sui_tx_event_index, Ok(sign_action_with_key(&action, &secrets[0])), + None, ); mock1.add_sui_event_response( sui_tx_digest, sui_tx_event_index, Ok(sign_action_with_key(&action, &secrets[1])), + None, ); mock2.add_sui_event_response( sui_tx_digest, sui_tx_event_index, Ok(sign_action_with_key(&action, &secrets[2])), + None, ); mock3.add_sui_event_response( sui_tx_digest, sui_tx_event_index, Ok(sign_action_with_key(&action, &secrets[3])), + None, ); agg.request_committee_signatures(action.clone()) .await @@ -387,6 +401,7 @@ mod tests { sui_tx_digest, sui_tx_event_index, Err(BridgeError::RestAPIError("".into())), + None, ); agg.request_committee_signatures(action.clone()) .await @@ -397,6 +412,7 @@ mod tests { sui_tx_digest, sui_tx_event_index, Err(BridgeError::RestAPIError("".into())), + None, ); agg.request_committee_signatures(action.clone()) .await @@ -407,6 +423,7 @@ mod tests { sui_tx_digest, sui_tx_event_index, Err(BridgeError::RestAPIError("".into())), + None, ); let err = agg .request_committee_signatures(action.clone()) @@ -418,6 +435,176 @@ mod tests { )); } + #[tokio::test] + async fn test_bridge_auth_agg_optimized() { + telemetry_subscribers::init_for_testing(); + + let mock0 = BridgeRequestMockHandler::new(); + let mock1 = BridgeRequestMockHandler::new(); + let mock2 = BridgeRequestMockHandler::new(); + let mock3 = BridgeRequestMockHandler::new(); + let mock4 = BridgeRequestMockHandler::new(); + let mock5 = BridgeRequestMockHandler::new(); + let mock6 = BridgeRequestMockHandler::new(); + let mock7 = BridgeRequestMockHandler::new(); + let mock8 = BridgeRequestMockHandler::new(); + + // start servers - there is only one permutation of size 2 (1112, 2222) that will achieve quorum + let (_handles, authorities, secrets) = get_test_authorities_and_run_mock_bridge_server( + vec![666, 1000, 900, 900, 900, 900, 900, 1612, 2222], + vec![ + mock0.clone(), + mock1.clone(), + mock2.clone(), + mock3.clone(), + mock4.clone(), + mock5.clone(), + mock6.clone(), + mock7.clone(), + mock8.clone(), + ], + ); + + let authorities_clone = authorities.clone(); + let committee = Arc::new(BridgeCommittee::new(authorities_clone).unwrap()); + + let agg = BridgeAuthorityAggregator::new(committee.clone()); + + let sui_tx_digest = TransactionDigest::random(); + let sui_tx_event_index = 0; + let nonce = 0; + let amount = 1000; + let action = get_test_sui_to_eth_bridge_action( + Some(sui_tx_digest), + Some(sui_tx_event_index), + Some(nonce), + Some(amount), + None, + None, + None, + ); + + // All authorities return signatures + mock0.add_sui_event_response( + sui_tx_digest, + sui_tx_event_index, + Ok(sign_action_with_key(&action, &secrets[0])), + Some(Duration::from_millis(200)), + ); + mock1.add_sui_event_response( + sui_tx_digest, + sui_tx_event_index, + Ok(sign_action_with_key(&action, &secrets[1])), + Some(Duration::from_millis(200)), + ); + mock2.add_sui_event_response( + sui_tx_digest, + sui_tx_event_index, + Ok(sign_action_with_key(&action, &secrets[2])), + Some(Duration::from_millis(700)), + ); + mock3.add_sui_event_response( + sui_tx_digest, + sui_tx_event_index, + Ok(sign_action_with_key(&action, &secrets[3])), + Some(Duration::from_millis(700)), + ); + mock4.add_sui_event_response( + sui_tx_digest, + sui_tx_event_index, + Ok(sign_action_with_key(&action, &secrets[4])), + Some(Duration::from_millis(700)), + ); + mock5.add_sui_event_response( + sui_tx_digest, + sui_tx_event_index, + Ok(sign_action_with_key(&action, &secrets[5])), + Some(Duration::from_millis(700)), + ); + mock6.add_sui_event_response( + sui_tx_digest, + sui_tx_event_index, + Ok(sign_action_with_key(&action, &secrets[6])), + Some(Duration::from_millis(700)), + ); + mock7.add_sui_event_response( + sui_tx_digest, + sui_tx_event_index, + Ok(sign_action_with_key(&action, &secrets[7])), + Some(Duration::from_millis(900)), + ); + mock8.add_sui_event_response( + sui_tx_digest, + sui_tx_event_index, + Ok(sign_action_with_key(&action, &secrets[8])), + Some(Duration::from_millis(1_500)), + ); + + // we should receive all signatures in time, but only aggregate 2 authorities + // to achieve quorum + let state = GetSigsState::new(action.approval_threshold(), committee.clone()); + let resp = request_sign_bridge_action_into_certification( + action.clone(), + agg.committee.clone(), + agg.clients.clone(), + state, + Duration::from_millis(2_000), + ) + .await + .unwrap(); + let sig_keys = resp.auth_sig().signatures.keys().collect::>(); + assert_eq!(sig_keys.len(), 2); + assert!(sig_keys.contains(&authorities[7].pubkey_bytes())); + assert!(sig_keys.contains(&authorities[8].pubkey_bytes())); + + // we should receive all but the highest stake signatures in time, but still be able to + // achieve quorum with 3 sigs + let state = GetSigsState::new(action.approval_threshold(), committee.clone()); + let resp = request_sign_bridge_action_into_certification( + action.clone(), + agg.committee.clone(), + agg.clients.clone(), + state, + Duration::from_millis(1_200), + ) + .await + .unwrap(); + let sig_keys = resp.auth_sig().signatures.keys().collect::>(); + assert_eq!(sig_keys.len(), 3); + assert!(sig_keys.contains(&authorities[7].pubkey_bytes())); + // this should not have come in time + assert!(!sig_keys.contains(&authorities[8].pubkey_bytes())); + + // we should have fallen back to arrival order given that we timeout before we reach quorum + let state = GetSigsState::new(action.approval_threshold(), committee.clone()); + let start = std::time::Instant::now(); + let resp = request_sign_bridge_action_into_certification( + action.clone(), + agg.committee.clone(), + agg.clients.clone(), + state, + Duration::from_millis(500), + ) + .await + .unwrap(); + let elapsed = start.elapsed(); + assert!( + elapsed >= Duration::from_millis(700), + "Expected to have to wait at least 700ms to fallback to arrival order and achieve quorum, but was {:?}", + elapsed + ); + let sig_keys = resp.auth_sig().signatures.keys().collect::>(); + assert_eq!(sig_keys.len(), 4); + // These two do not make it on time initially, and then we should be able + // to achieve quorum before these ultimately arrive + assert!(!sig_keys.contains(&authorities[7].pubkey_bytes())); + assert!(!sig_keys.contains(&authorities[8].pubkey_bytes())); + // These were the first two to respond, and should be immediately + // included once we fallback to arrival order + assert!(sig_keys.contains(&authorities[0].pubkey_bytes())); + assert!(sig_keys.contains(&authorities[1].pubkey_bytes())); + } + #[tokio::test] async fn test_bridge_auth_agg_more_cases() { telemetry_subscribers::init_for_testing(); @@ -460,11 +647,13 @@ mod tests { sui_tx_digest, sui_tx_event_index, Ok(sign_action_with_key(&action, &secrets[2])), + None, ); mock3.add_sui_event_response( sui_tx_digest, sui_tx_event_index, Ok(sign_action_with_key(&action, &secrets[3])), + None, ); let certified = agg .request_committee_signatures(action.clone()) @@ -489,6 +678,7 @@ mod tests { sui_tx_digest, sui_tx_event_index, Err(BridgeError::RestAPIError("".into())), + None, ); let err = agg .request_committee_signatures(action.clone()) @@ -504,6 +694,7 @@ mod tests { sui_tx_digest, sui_tx_event_index, Ok(sign_action_with_key(&action, &secrets[2])), + None, ); let err = agg .request_committee_signatures(action.clone()) diff --git a/crates/sui-bridge/src/client/bridge_client.rs b/crates/sui-bridge/src/client/bridge_client.rs index 08bbd50984672..83c11e73ba995 100644 --- a/crates/sui-bridge/src/client/bridge_client.rs +++ b/crates/sui-bridge/src/client/bridge_client.rs @@ -341,7 +341,7 @@ mod tests { ); let sig = BridgeAuthoritySignInfo::new(&action, &secret); let signed_event = SignedBridgeAction::new_from_data_and_sig(action.clone(), sig.clone()); - mock_handler.add_sui_event_response(tx_digest, event_idx, Ok(signed_event.clone())); + mock_handler.add_sui_event_response(tx_digest, event_idx, Ok(signed_event.clone()), None); // success client @@ -362,7 +362,7 @@ mod tests { let wrong_sig = BridgeAuthoritySignInfo::new(&action2, &secret); let wrong_signed_action = SignedBridgeAction::new_from_data_and_sig(action2.clone(), wrong_sig.clone()); - mock_handler.add_sui_event_response(tx_digest, event_idx, Ok(wrong_signed_action)); + mock_handler.add_sui_event_response(tx_digest, event_idx, Ok(wrong_signed_action), None); let err = client .request_sign_bridge_action(action.clone()) .await @@ -372,7 +372,7 @@ mod tests { // The action matches but the signature is wrong, fail let wrong_signed_action = SignedBridgeAction::new_from_data_and_sig(action.clone(), wrong_sig); - mock_handler.add_sui_event_response(tx_digest, event_idx, Ok(wrong_signed_action)); + mock_handler.add_sui_event_response(tx_digest, event_idx, Ok(wrong_signed_action), None); let err = client .request_sign_bridge_action(action.clone()) .await @@ -389,7 +389,7 @@ mod tests { BridgeCommittee::new(vec![authority_blocklisted.clone(), authority2.clone()]).unwrap(), ); client.update_committee(committee2); - mock_handler.add_sui_event_response(tx_digest, event_idx, Ok(signed_event)); + mock_handler.add_sui_event_response(tx_digest, event_idx, Ok(signed_event), None); let err = client .request_sign_bridge_action(action.clone()) @@ -404,7 +404,7 @@ mod tests { // signed by a different authority in committee would fail let sig2 = BridgeAuthoritySignInfo::new(&action, &secret2); let signed_event2 = SignedBridgeAction::new_from_data_and_sig(action.clone(), sig2.clone()); - mock_handler.add_sui_event_response(tx_digest, event_idx, Ok(signed_event2)); + mock_handler.add_sui_event_response(tx_digest, event_idx, Ok(signed_event2), None); let err = client .request_sign_bridge_action(action.clone()) .await @@ -416,7 +416,7 @@ mod tests { let secret3 = Arc::pin(kp3); let sig3 = BridgeAuthoritySignInfo::new(&action, &secret3); let signed_event3 = SignedBridgeAction::new_from_data_and_sig(action.clone(), sig3); - mock_handler.add_sui_event_response(tx_digest, event_idx, Ok(signed_event3)); + mock_handler.add_sui_event_response(tx_digest, event_idx, Ok(signed_event3), None); let err = client .request_sign_bridge_action(action.clone()) .await diff --git a/crates/sui-bridge/src/config.rs b/crates/sui-bridge/src/config.rs index 065b8c0aeb6c1..e59576417caac 100644 --- a/crates/sui-bridge/src/config.rs +++ b/crates/sui-bridge/src/config.rs @@ -170,7 +170,7 @@ impl BridgeNodeConfig { )); } - let (eth_client, eth_contracts) = self.prepare_for_eth(metrics).await?; + let (eth_client, eth_contracts) = self.prepare_for_eth(metrics.clone()).await?; let bridge_summary = sui_client .get_bridge_summary() .await @@ -208,7 +208,7 @@ impl BridgeNodeConfig { // If client is enabled, prepare client config let (bridge_client_key, client_sui_address, gas_object_ref) = - self.prepare_for_sui(sui_client.clone()).await?; + self.prepare_for_sui(sui_client.clone(), metrics).await?; let db_path = self .db_path @@ -249,7 +249,7 @@ impl BridgeNodeConfig { .interval(std::time::Duration::from_millis(2000)), ); let chain_id = provider.get_chainid().await?; - let (committee_address, limiter_address, vault_address, config_address) = + let (committee_address, limiter_address, vault_address, config_address, _weth_address) = get_eth_contract_addresses(bridge_proxy_address, &provider).await?; let config = EthBridgeConfig::new(config_address, provider.clone()); @@ -314,6 +314,7 @@ impl BridgeNodeConfig { async fn prepare_for_sui( &self, sui_client: Arc>, + metrics: Arc, ) -> anyhow::Result<(SuiKeyPair, SuiAddress, ObjectRef)> { let bridge_client_key = match &self.sui.bridge_client_key_path { None => read_key(&self.bridge_authority_key_path, true), @@ -351,7 +352,6 @@ impl BridgeNodeConfig { let client_sui_address = SuiAddress::from(&bridge_client_key.public()); - // TODO: decide a minimal amount here let gas_object_id = match self.sui.bridge_client_gas_object { Some(id) => id, None => { @@ -359,7 +359,8 @@ impl BridgeNodeConfig { .build(&self.sui.sui_rpc_url) .await?; let coin = - pick_highest_balance_coin(sui_client.coin_read_api(), client_sui_address, 0) + // Minimum balance for gas object is 10 SUI + pick_highest_balance_coin(sui_client.coin_read_api(), client_sui_address, 10_000_000_000) .await?; coin.coin_object_id } @@ -370,11 +371,11 @@ impl BridgeNodeConfig { if owner != Owner::AddressOwner(client_sui_address) { return Err(anyhow!("Gas object {:?} is not owned by bridge client key's associated sui address {:?}, but {:?}", gas_object_id, client_sui_address, owner)); } + let balance = gas_coin.value(); + metrics.gas_coin_balance.set(balance as i64); info!( "Starting bridge client with address: {:?}, gas object {:?}, balance: {}", - client_sui_address, - gas_object_ref.0, - gas_coin.value() + client_sui_address, gas_object_ref.0, balance, ); Ok((bridge_client_key, client_sui_address, gas_object_ref)) @@ -391,7 +392,6 @@ pub struct BridgeServerConfig { pub approved_governance_actions: Vec, } -// TODO: add gas balance alert threshold pub struct BridgeClientConfig { pub sui_address: SuiAddress, pub key: SuiKeyPair, diff --git a/crates/sui-bridge/src/crypto.rs b/crates/sui-bridge/src/crypto.rs index 2d992d4b19bbe..2121320a3fb39 100644 --- a/crates/sui-bridge/src/crypto.rs +++ b/crates/sui-bridge/src/crypto.rs @@ -281,6 +281,7 @@ mod tests { .unwrap(); let pubkey1 = BridgeAuthorityPublicKey::from_bytes(&public_key_bytes).unwrap(); let authority1 = BridgeAuthority { + sui_address: SuiAddress::random_for_testing_only(), pubkey: pubkey1.clone(), voting_power: 2500, is_blocklisted: false, @@ -292,6 +293,7 @@ mod tests { .unwrap(); let pubkey2 = BridgeAuthorityPublicKey::from_bytes(&public_key_bytes).unwrap(); let authority2 = BridgeAuthority { + sui_address: SuiAddress::random_for_testing_only(), pubkey: pubkey2.clone(), voting_power: 2500, is_blocklisted: false, @@ -303,6 +305,7 @@ mod tests { .unwrap(); let pubkey3 = BridgeAuthorityPublicKey::from_bytes(&public_key_bytes).unwrap(); let authority3 = BridgeAuthority { + sui_address: SuiAddress::random_for_testing_only(), pubkey: pubkey3.clone(), voting_power: 2500, is_blocklisted: false, @@ -314,6 +317,7 @@ mod tests { .unwrap(); let pubkey4 = BridgeAuthorityPublicKey::from_bytes(&public_key_bytes).unwrap(); let authority4 = BridgeAuthority { + sui_address: SuiAddress::random_for_testing_only(), pubkey: pubkey4.clone(), voting_power: 2500, is_blocklisted: false, diff --git a/crates/sui-bridge/src/e2e_tests/basic.rs b/crates/sui-bridge/src/e2e_tests/basic.rs index dff912fd39970..abdde652e9c13 100644 --- a/crates/sui-bridge/src/e2e_tests/basic.rs +++ b/crates/sui-bridge/src/e2e_tests/basic.rs @@ -3,10 +3,11 @@ use crate::abi::{eth_sui_bridge, EthBridgeEvent, EthERC20, EthSuiBridge}; use crate::client::bridge_authority_aggregator::BridgeAuthorityAggregator; -use crate::e2e_tests::test_utils::BridgeTestCluster; +use crate::crypto::BridgeAuthorityKeyPair; use crate::e2e_tests::test_utils::{ get_signatures, send_eth_tx_and_get_tx_receipt, BridgeTestClusterBuilder, }; +use crate::e2e_tests::test_utils::{BridgeTestCluster, TestClusterWrapperBuilder}; use crate::eth_transaction_builder::build_eth_transaction; use crate::events::{ SuiBridgeEvent, SuiToEthTokenBridgeV1, TokenTransferApproved, TokenTransferClaimed, @@ -16,11 +17,15 @@ use crate::sui_transaction_builder::build_add_tokens_on_sui_transaction; use crate::types::{AddTokensOnEvmAction, BridgeAction, BridgeActionStatus, SuiToEthBridgeAction}; use crate::utils::publish_and_register_coins_return_add_coins_on_sui_action; use crate::utils::EthSigner; +use crate::BRIDGE_ENABLE_PROTOCOL_VERSION; use eth_sui_bridge::EthSuiBridgeEvents; use ethers::prelude::*; use ethers::types::Address as EthAddress; use move_core_types::ident_str; use std::collections::{HashMap, HashSet}; +use sui_json_rpc_api::BridgeReadApiClient; +use sui_types::crypto::get_key_pair; +use test_cluster::TestClusterBuilder; use std::path::Path; @@ -32,10 +37,12 @@ use sui_json_rpc_types::{ use sui_sdk::wallet_context::WalletContext; use sui_sdk::SuiClient; use sui_types::base_types::{ObjectRef, SuiAddress}; -use sui_types::bridge::{BridgeChainId, BridgeTokenMetadata, BRIDGE_MODULE_NAME, TOKEN_ID_ETH}; +use sui_types::bridge::{ + get_bridge, BridgeChainId, BridgeTokenMetadata, BridgeTrait, BRIDGE_MODULE_NAME, TOKEN_ID_ETH, +}; use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder; use sui_types::transaction::{ObjectArg, TransactionData}; -use sui_types::{TypeTag, BRIDGE_PACKAGE_ID}; +use sui_types::{TypeTag, BRIDGE_PACKAGE_ID, SUI_BRIDGE_OBJECT_ID}; use tap::TapFallible; use tracing::info; @@ -327,6 +334,100 @@ async fn test_add_new_coins_on_sui_and_eth() { .unwrap(); } +#[tokio::test(flavor = "multi_thread", worker_threads = 8)] +async fn test_create_bridge_state_object() { + let test_cluster = TestClusterBuilder::new() + .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION - 1).into()) + .with_epoch_duration_ms(20000) + .build() + .await; + + let handles = test_cluster.all_node_handles(); + + // no node has the bridge state object yet + for h in &handles { + h.with(|node| { + assert!(node + .state() + .get_object_cache_reader() + .get_latest_object_ref_or_tombstone(SUI_BRIDGE_OBJECT_ID) + .unwrap() + .is_none()); + }); + } + + // wait until feature is enabled + test_cluster + .wait_for_protocol_version(BRIDGE_ENABLE_PROTOCOL_VERSION.into()) + .await; + // wait until next epoch - authenticator state object is created at the end of the first epoch + // in which it is supported. + test_cluster.wait_for_epoch_all_nodes(2).await; // protocol upgrade completes in epoch 1 + + for h in &handles { + h.with(|node| { + node.state() + .get_object_cache_reader() + .get_latest_object_ref_or_tombstone(SUI_BRIDGE_OBJECT_ID) + .unwrap() + .expect("auth state object should exist"); + }); + } +} + +#[tokio::test] +async fn test_committee_registration() { + telemetry_subscribers::init_for_testing(); + let mut bridge_keys = vec![]; + for _ in 0..=3 { + let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair(); + bridge_keys.push(kp); + } + let test_cluster = TestClusterWrapperBuilder::new() + .with_bridge_authority_keys(bridge_keys) + .build() + .await; + + let bridge = get_bridge( + test_cluster + .inner + .fullnode_handle + .sui_node + .state() + .get_object_store(), + ) + .unwrap(); + + // Member should be empty before end of epoch + assert!(bridge.committee().members.contents.is_empty()); + assert_eq!( + test_cluster.inner.swarm.active_validators().count(), + bridge.committee().member_registrations.contents.len() + ); + + test_cluster + .trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized() + .await; +} + +#[tokio::test] +async fn test_bridge_api_compatibility() { + let test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() + .with_protocol_version(BRIDGE_ENABLE_PROTOCOL_VERSION.into()) + .build() + .await; + + test_cluster.trigger_reconfiguration().await; + let client = test_cluster.rpc_client(); + client.get_latest_bridge().await.unwrap(); + // TODO: assert fields in summary + + client + .get_bridge_object_initial_shared_version() + .await + .unwrap(); +} + pub(crate) async fn deposit_native_eth_to_sol_contract( signer: &EthSigner, contract_address: EthAddress, diff --git a/crates/sui-bridge/src/e2e_tests/test_utils.rs b/crates/sui-bridge/src/e2e_tests/test_utils.rs index ae6ef6a94a0ac..187f28d5eb9b0 100644 --- a/crates/sui-bridge/src/e2e_tests/test_utils.rs +++ b/crates/sui-bridge/src/e2e_tests/test_utils.rs @@ -6,13 +6,23 @@ use crate::abi::EthBridgeConfig; use crate::config::default_ed25519_key_pair; use crate::crypto::BridgeAuthorityKeyPair; use crate::crypto::BridgeAuthorityPublicKeyBytes; +use crate::crypto::BridgeAuthoritySignInfo; use crate::events::*; use crate::metrics::BridgeMetrics; use crate::server::BridgeNodePublicMetadata; +use crate::sui_transaction_builder::build_add_tokens_on_sui_transaction; +use crate::sui_transaction_builder::build_committee_register_transaction; use crate::types::BridgeAction; +use crate::types::BridgeCommitteeValiditySignInfo; +use crate::types::CertifiedBridgeAction; +use crate::types::VerifiedCertifiedBridgeAction; use crate::utils::get_eth_signer_client; +use crate::utils::publish_and_register_coins_return_add_coins_on_sui_action; +use crate::utils::wait_for_server_to_be_up; use crate::utils::EthSigner; use ethers::types::Address as EthAddress; +use futures::future::join_all; +use futures::Future; use move_core_types::language_storage::StructTag; use prometheus::Registry; use rand::rngs::SmallRng; @@ -28,21 +38,33 @@ use std::path::PathBuf; use std::process::Command; use std::str::FromStr; use std::sync::Arc; +use sui_json_rpc_api::BridgeReadApiClient; use sui_json_rpc_types::SuiEvent; +use sui_json_rpc_types::SuiExecutionStatus; +use sui_json_rpc_types::SuiTransactionBlockEffectsAPI; use sui_json_rpc_types::SuiTransactionBlockResponse; use sui_json_rpc_types::SuiTransactionBlockResponseOptions; use sui_json_rpc_types::SuiTransactionBlockResponseQuery; use sui_json_rpc_types::TransactionFilter; use sui_sdk::wallet_context::WalletContext; use sui_test_transaction_builder::TestTransactionBuilder; +use sui_types::base_types::ObjectID; +use sui_types::bridge::get_bridge; +use sui_types::bridge::get_bridge_obj_initial_shared_version; use sui_types::bridge::BridgeChainId; +use sui_types::bridge::BridgeSummary; +use sui_types::bridge::BridgeTrait; +use sui_types::bridge::{TOKEN_ID_BTC, TOKEN_ID_ETH, TOKEN_ID_USDC, TOKEN_ID_USDT}; use sui_types::committee::TOTAL_VOTING_POWER; use sui_types::crypto::get_key_pair; +use sui_types::crypto::ToFromBytes; use sui_types::digests::TransactionDigest; -use sui_types::transaction::{ObjectArg, TransactionData}; +use sui_types::object::Object; +use sui_types::transaction::{ObjectArg, Transaction, TransactionData}; use sui_types::SUI_BRIDGE_OBJECT_ID; use tokio::join; use tokio::task::JoinHandle; +use tokio::time::Instant; use tracing::error; use tracing::info; @@ -79,7 +101,7 @@ pub const TEST_PK: &str = "0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1 /// structs that are needed for testing. pub struct BridgeTestCluster { pub num_validators: usize, - pub test_cluster: TestCluster, + pub test_cluster: TestClusterWrapper, bridge_client: SuiBridgeClient, eth_environment: EthBridgeEnvironment, bridge_node_handles: Option>>, @@ -161,8 +183,7 @@ impl BridgeTestClusterBuilder { bridge_keys.push(kp.copy()); bridge_keys_copy.push(kp); } - let start_cluster_task = - tokio::task::spawn(Self::start_test_cluster(bridge_keys, self.num_validators)); + let start_cluster_task = tokio::task::spawn(Self::start_test_cluster(bridge_keys)); let start_eth_env_task = tokio::task::spawn(Self::start_eth_env(bridge_keys_copy)); let (start_cluster_res, start_eth_env_res) = join!(start_cluster_task, start_eth_env_task); let test_cluster = start_cluster_res.unwrap(); @@ -179,9 +200,10 @@ impl BridgeTestClusterBuilder { .await, ); } - let bridge_client = SuiBridgeClient::new(&test_cluster.fullnode_handle.rpc_url, metrics) - .await - .unwrap(); + let bridge_client = + SuiBridgeClient::new(&test_cluster.inner.fullnode_handle.rpc_url, metrics) + .await + .unwrap(); info!( "Bridge committee: {:?}", bridge_client @@ -203,15 +225,11 @@ impl BridgeTestClusterBuilder { } } - async fn start_test_cluster( - bridge_keys: Vec, - num_validators: usize, - ) -> TestCluster { - assert_eq!(bridge_keys.len(), num_validators); - let test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() - .with_num_validators(num_validators) - .with_protocol_version(BRIDGE_ENABLE_PROTOCOL_VERSION.into()) - .build_with_bridge(bridge_keys, true) + async fn start_test_cluster(bridge_keys: Vec) -> TestClusterWrapper { + let test_cluster = TestClusterWrapperBuilder::new() + .with_bridge_authority_keys(bridge_keys) + .with_deploy_tokens(true) + .build() .await; info!("Test cluster built"); test_cluster @@ -261,11 +279,11 @@ impl BridgeTestCluster { } pub fn sui_client(&self) -> &SuiClient { - &self.test_cluster.fullnode_handle.sui_client + &self.test_cluster.inner.fullnode_handle.sui_client } pub fn sui_user_address(&self) -> SuiAddress { - self.test_cluster.get_address_0() + self.test_cluster.inner.get_address_0() } pub fn sui_chain_id(&self) -> BridgeChainId { @@ -289,19 +307,19 @@ impl BridgeTestCluster { } pub fn wallet_mut(&mut self) -> &mut WalletContext { - self.test_cluster.wallet_mut() + self.test_cluster.inner.wallet_mut() } pub fn wallet(&self) -> &WalletContext { - &self.test_cluster.wallet + &self.test_cluster.inner.wallet } pub fn bridge_authority_key(&self, index: usize) -> BridgeAuthorityKeyPair { - self.test_cluster.bridge_authority_keys.as_ref().unwrap()[index].copy() + self.test_cluster.bridge_authority_keys[index].copy() } pub fn sui_rpc_url(&self) -> String { - self.test_cluster.fullnode_handle.rpc_url.clone() + self.test_cluster.inner.fullnode_handle.rpc_url.clone() } pub fn eth_rpc_url(&self) -> String { @@ -317,6 +335,7 @@ impl BridgeTestCluster { sender: SuiAddress, ) -> TestTransactionBuilder { self.test_cluster + .inner .test_transaction_builder_with_sender(sender) .await } @@ -332,6 +351,7 @@ impl BridgeTestCluster { tx_data: &TransactionData, ) -> SuiTransactionBlockResponse { self.test_cluster + .inner .sign_and_execute_transaction(tx_data) .await } @@ -482,7 +502,10 @@ struct SolDeployConfig { supported_chain_ids: Vec, supported_chain_limits_in_dollars: Vec, supported_tokens: Vec, + token_ids: Vec, + sui_decimals: Vec, token_prices: Vec, + weth: String, } pub(crate) async fn deploy_sol_contract( @@ -524,7 +547,10 @@ pub(crate) async fn deploy_sol_contract( 1000000000000000, ], supported_tokens: vec![], // this is set up in the deploy script + token_ids: vec![], // this is set up in the deploy script + sui_decimals: vec![], // this is set up in the deploy script token_prices: vec![12800, 432518900, 25969600, 10000, 10000], + weth: "".to_string(), // this is set up in the deploy script }; let serialized_config = serde_json::to_string_pretty(&deploy_config).unwrap(); @@ -717,18 +743,16 @@ impl Drop for EthBridgeEnvironment { } pub(crate) async fn start_bridge_cluster( - test_cluster: &TestCluster, + test_cluster: &TestClusterWrapper, eth_environment: &EthBridgeEnvironment, approved_governance_actions: Vec>, ) -> Vec> { let bridge_authority_keys = test_cluster .bridge_authority_keys - .as_ref() - .unwrap() .iter() .map(|k| k.copy()) .collect::>(); - let bridge_server_ports = test_cluster.bridge_server_ports.as_ref().unwrap(); + let bridge_server_ports = test_cluster.bridge_server_ports.clone(); assert_eq!(bridge_authority_keys.len(), bridge_server_ports.len()); assert_eq!( bridge_authority_keys.len(), @@ -772,7 +796,7 @@ pub(crate) async fn start_bridge_cluster( eth_contracts_start_block_override: None, }, sui: SuiConfig { - sui_rpc_url: test_cluster.fullnode_handle.rpc_url.clone(), + sui_rpc_url: test_cluster.inner.fullnode_handle.rpc_url.clone(), sui_bridge_chain_id: BridgeChainId::SuiCustom as u8, bridge_client_key_path: None, bridge_client_gas_object: None, @@ -844,3 +868,345 @@ impl Drop for TempDir { } } } + +pub struct TestClusterWrapperBuilder { + protocol_version: u64, + bridge_authority_keys: Vec, + deploy_tokens: bool, +} + +impl TestClusterWrapperBuilder { + pub fn new() -> Self { + Self { + protocol_version: BRIDGE_ENABLE_PROTOCOL_VERSION, + bridge_authority_keys: vec![], + deploy_tokens: false, + } + } + + pub fn with_protocol_version(mut self, version: u64) -> Self { + self.protocol_version = version; + self + } + + pub fn with_bridge_authority_keys(mut self, keys: Vec) -> Self { + self.bridge_authority_keys = keys; + self + } + + pub fn with_deploy_tokens(mut self, deploy_tokens: bool) -> Self { + self.deploy_tokens = deploy_tokens; + self + } + + pub async fn build(self) -> TestClusterWrapper { + assert_ne!(self.bridge_authority_keys.len(), 0); + let num_validators = self.bridge_authority_keys.len(); + let builder = TestClusterBuilder::new().with_protocol_version(self.protocol_version.into()); + + let timer = Instant::now(); + let gas_objects_for_authority_keys = self + .bridge_authority_keys + .iter() + .map(|k| { + let address = SuiAddress::from(k.public()); + Object::with_id_owner_for_testing(ObjectID::random(), address) + }) + .collect::>(); + let mut test_cluster = builder + .with_num_validators(num_validators) + .with_objects(gas_objects_for_authority_keys) + .build() + .await; + info!( + "TestCluster build took {:?} secs", + timer.elapsed().as_secs() + ); + let ref_gas_price = test_cluster.get_reference_gas_price().await; + let bridge_arg = get_mut_bridge_arg(&test_cluster).await.unwrap(); + assert_eq!( + self.bridge_authority_keys.len(), + test_cluster.swarm.active_validators().count() + ); + + // Committee registers themselves + let mut server_ports = vec![]; + let mut tasks = vec![]; + let quorum_driver_api = test_cluster.quorum_driver_api().clone(); + // Reorder the nodes so that the last node has the largest stake. + let validator_with_max_stake = test_cluster + .sui_client() + .governance_api() + .get_committee_info(None) + .await + .unwrap() + .validators + .iter() + .max_by(|a, b| a.0.cmp(&b.0)) + .unwrap() + .0; + let node_with_max_stake = test_cluster + .swarm + .active_validators() + .find(|v| v.config().protocol_public_key() == validator_with_max_stake) + .unwrap(); + let other_nodes = test_cluster + .swarm + .active_validators() + .filter(|v| v.config().protocol_public_key() != validator_with_max_stake) + .collect::>(); + let reordered_nodes = other_nodes + .iter() + .chain(std::iter::once(&node_with_max_stake)); + for (node, kp) in reordered_nodes.zip(self.bridge_authority_keys.iter()) { + let validator_address = node.config().sui_address(); + // create committee registration tx + let gas = test_cluster + .wallet + .get_one_gas_object_owned_by_address(validator_address) + .await + .unwrap() + .unwrap(); + + let server_port = get_available_port("127.0.0.1"); + let server_url = format!("http://127.0.0.1:{}", server_port); + server_ports.push(server_port); + let data = build_committee_register_transaction( + validator_address, + &gas, + bridge_arg, + kp.public().as_bytes().to_vec(), + &server_url, + ref_gas_price, + 1000000000, + ) + .unwrap(); + + let tx = Transaction::from_data_and_signer( + data, + vec![node.config().account_key_pair.keypair()], + ); + let api_clone = quorum_driver_api.clone(); + tasks.push(async move { + api_clone + .execute_transaction_block( + tx, + SuiTransactionBlockResponseOptions::new().with_effects(), + None, + ) + .await + }); + } + + if self.deploy_tokens { + let timer = Instant::now(); + let token_ids = vec![TOKEN_ID_BTC, TOKEN_ID_ETH, TOKEN_ID_USDC, TOKEN_ID_USDT]; + let token_prices = vec![500_000_000u64, 30_000_000u64, 1_000u64, 1_000u64]; + let action = publish_and_register_coins_return_add_coins_on_sui_action( + test_cluster.wallet(), + bridge_arg, + vec![ + Path::new("../../bridge/move/tokens/btc").into(), + Path::new("../../bridge/move/tokens/eth").into(), + Path::new("../../bridge/move/tokens/usdc").into(), + Path::new("../../bridge/move/tokens/usdt").into(), + ], + token_ids, + token_prices, + 0, + ); + let action = action.await; + info!("register tokens took {:?} secs", timer.elapsed().as_secs()); + let sig_map = self + .bridge_authority_keys + .iter() + .map(|key| { + ( + key.public().into(), + BridgeAuthoritySignInfo::new(&action, key).signature, + ) + }) + .collect::>(); + let certified_action = CertifiedBridgeAction::new_from_data_and_sig( + action, + BridgeCommitteeValiditySignInfo { + signatures: sig_map.clone(), + }, + ); + let verifired_action_cert = + VerifiedCertifiedBridgeAction::new_from_verified(certified_action); + let sender_address = test_cluster.get_address_0(); + + await_committee_register_tasks(&test_cluster, tasks).await; + + // Wait until committee is set up + trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized( + &test_cluster, + ) + .await; + + let tx = build_add_tokens_on_sui_transaction( + sender_address, + &test_cluster + .wallet + .get_one_gas_object_owned_by_address(sender_address) + .await + .unwrap() + .unwrap(), + verifired_action_cert, + bridge_arg, + ref_gas_price, + ) + .unwrap(); + + let response = test_cluster.sign_and_execute_transaction(&tx).await; + assert_eq!( + response.effects.unwrap().status(), + &SuiExecutionStatus::Success + ); + info!("Deploy tokens took {:?} secs", timer.elapsed().as_secs()); + } else { + await_committee_register_tasks(&test_cluster, tasks).await; + } + async fn await_committee_register_tasks( + test_cluster: &TestCluster, + tasks: Vec< + impl Future>, + >, + ) { + // The tx may fail if a member tries to register when the committee is already finalized. + // In that case, we just need to check the committee members is not empty since once + // the committee is finalized, it should not be empty. + let responses = join_all(tasks).await; + let mut has_failure = false; + for response in responses { + if response.unwrap().effects.unwrap().status() != &SuiExecutionStatus::Success { + has_failure = true; + } + } + if has_failure { + let bridge_summary = get_bridge_summary(test_cluster).await; + assert_ne!(bridge_summary.committee.members.len(), 0); + } + } + + info!( + "TestCluster build_with_bridge took {:?} secs", + timer.elapsed().as_secs() + ); + TestClusterWrapper { + inner: test_cluster, + bridge_authority_keys: self.bridge_authority_keys, + bridge_server_ports: server_ports, + } + } +} + +impl Default for TestClusterWrapperBuilder { + fn default() -> Self { + Self::new() + } +} +pub struct TestClusterWrapper { + pub inner: TestCluster, + pub bridge_authority_keys: Vec, + pub bridge_server_ports: Vec, +} + +impl TestClusterWrapper { + pub fn authority_keys_clone(&self) -> Vec { + self.bridge_authority_keys + .iter() + .map(|k| k.copy()) + .collect() + } + + pub async fn trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized(&self) { + trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized(&self.inner) + .await + } + + // Wait for bridge node in the cluster to be up and running. + pub async fn wait_for_bridge_cluster_to_be_up(&self, timeout_sec: u64) { + let bridge_ports = self.bridge_server_ports.clone(); + let mut tasks = vec![]; + for port in bridge_ports.iter() { + let server_url = format!("http://127.0.0.1:{}", port); + tasks.push(wait_for_server_to_be_up(server_url, timeout_sec)); + } + join_all(tasks) + .await + .into_iter() + .collect::>>() + .unwrap(); + } + + pub async fn get_mut_bridge_arg(&self) -> Option { + get_mut_bridge_arg(&self.inner).await + } + + pub async fn get_bridge_summary(&self) -> BridgeSummary { + get_bridge_summary(&self.inner).await + } +} + +async fn get_bridge_summary(test_cluster: &TestCluster) -> BridgeSummary { + test_cluster + .sui_client() + .http() + .get_latest_bridge() + .await + .unwrap() +} + +async fn get_mut_bridge_arg(test_cluster: &TestCluster) -> Option { + get_bridge_obj_initial_shared_version( + test_cluster + .fullnode_handle + .sui_node + .state() + .get_object_store(), + ) + .unwrap() + .map(|seq| ObjectArg::SharedObject { + id: SUI_BRIDGE_OBJECT_ID, + initial_shared_version: seq, + mutable: true, + }) +} + +async fn trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized( + test_cluster: &TestCluster, +) { + let mut bridge = get_bridge( + test_cluster + .fullnode_handle + .sui_node + .state() + .get_object_store(), + ) + .unwrap(); + if !bridge.committee().members.contents.is_empty() { + assert_eq!( + test_cluster.swarm.active_validators().count(), + bridge.committee().members.contents.len() + ); + return; + } + // wait for next epoch + test_cluster.trigger_reconfiguration().await; + bridge = get_bridge( + test_cluster + .fullnode_handle + .sui_node + .state() + .get_object_store(), + ) + .unwrap(); + // Committee should be initiated + assert!(bridge.committee().member_registrations.contents.is_empty()); + assert_eq!( + test_cluster.swarm.active_validators().count(), + bridge.committee().members.contents.len() + ); +} diff --git a/crates/sui-bridge/src/eth_syncer.rs b/crates/sui-bridge/src/eth_syncer.rs index 840dab701060d..0ed56de123419 100644 --- a/crates/sui-bridge/src/eth_syncer.rs +++ b/crates/sui-bridge/src/eth_syncer.rs @@ -113,8 +113,6 @@ where tracing::debug!("Last finalized block: {}", new_value); metrics.last_finalized_eth_block.set(new_value as i64); - // TODO add a metrics for the last finalized block - if new_value > last_block_number { last_finalized_block_sender .send(new_value) @@ -136,6 +134,7 @@ where metrics: Arc, ) { tracing::info!(contract_address=?contract_address, "Starting eth events listening task from block {start_block}"); + let contract_address_str = contract_address.to_string(); let mut more_blocks = false; loop { // If no more known blocks, wait for the next finalized block. @@ -197,9 +196,10 @@ where "Observed {len} new Eth events", ); } - if let Some(last_block) = last_block { - metrics.last_synced_eth_block.set(last_block as i64); - } + metrics + .last_synced_eth_blocks + .with_label_values(&[&contract_address_str]) + .set(last_block.unwrap_or(end_block) as i64); start_block = end_block + 1; } } diff --git a/crates/sui-bridge/src/metrics.rs b/crates/sui-bridge/src/metrics.rs index adad6c9659acc..6d1fdda6e2a7c 100644 --- a/crates/sui-bridge/src/metrics.rs +++ b/crates/sui-bridge/src/metrics.rs @@ -12,7 +12,6 @@ use prometheus::{ }; use std::time::Duration; use sui_types::crypto::NetworkKeyPair; -use tokio::time::sleep; const FINE_GRAINED_LATENCY_SEC_BUCKETS: &[f64] = &[ 0.001, 0.005, 0.01, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.6, 0.7, 0.8, 0.9, @@ -54,15 +53,22 @@ pub fn start_metrics_push_task( let mut interval = tokio::time::interval(interval); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + let mut errors = 0; loop { interval.tick().await; - // Retry pushing metrics if there is an error. - while let Err(error) = push_metrics(&client, &url, ®istry).await { - tracing::warn!("unable to push metrics: {error}; new client will be created"); - sleep(Duration::from_secs(1)).await; + if let Err(error) = push_metrics(&client, &url, ®istry).await { + errors += 1; + if errors >= 10 { + // If we hit 10 failures in a row, start logging errors. + tracing::error!("unable to push metrics: {error}; new client will be created"); + } else { + tracing::warn!("unable to push metrics: {error}; new client will be created"); + } // aggressively recreate our client connection if we hit an error client = MetricsPushClient::new(metrics_key_pair.copy()); + } else { + errors = 0; } } }); @@ -72,6 +78,7 @@ pub fn start_metrics_push_task( pub struct BridgeMetrics { pub(crate) err_build_sui_transaction: IntCounter, pub(crate) err_signature_aggregation: IntCounter, + pub(crate) err_signature_aggregation_too_many_failures: IntCounter, pub(crate) err_sui_transaction_submission: IntCounter, pub(crate) err_sui_transaction_submission_too_many_failures: IntCounter, pub(crate) err_sui_transaction_execution: IntCounter, @@ -80,9 +87,9 @@ pub struct BridgeMetrics { pub(crate) err_requests: IntCounterVec, pub(crate) requests_inflight: IntGaugeVec, - pub last_synced_sui_checkpoint: IntGauge, + pub(crate) last_synced_sui_checkpoints: IntGaugeVec, pub(crate) last_finalized_eth_block: IntGauge, - pub(crate) last_synced_eth_block: IntGauge, + pub(crate) last_synced_eth_blocks: IntGaugeVec, pub(crate) sui_watcher_received_events: IntCounter, pub(crate) sui_watcher_received_actions: IntCounter, @@ -106,6 +113,7 @@ pub struct BridgeMetrics { pub(crate) sui_rpc_errors: IntCounterVec, pub(crate) observed_governance_actions: IntCounterVec, + pub(crate) current_bridge_voting_rights: IntGaugeVec, } impl BridgeMetrics { @@ -123,6 +131,12 @@ impl BridgeMetrics { registry, ) .unwrap(), + err_signature_aggregation_too_many_failures: register_int_counter_with_registry!( + "bridge_err_signature_aggregation_too_many_failures", + "Total number of continuous failures during validator signature aggregation", + registry, + ) + .unwrap(), err_sui_transaction_submission: register_int_counter_with_registry!( "bridge_err_sui_transaction_submission", "Total number of errors of submitting sui transactions", @@ -256,21 +270,23 @@ impl BridgeMetrics { registry, ) .unwrap(), - last_synced_sui_checkpoint: register_int_gauge_with_registry!( - "last_synced_sui_checkpoint", - "The latest sui checkpoint that indexer synced", + last_synced_sui_checkpoints: register_int_gauge_vec_with_registry!( + "bridge_last_synced_sui_checkpoints", + "The latest sui checkpoints synced for each module", + &["module_name"], registry, ) .unwrap(), - last_synced_eth_block: register_int_gauge_with_registry!( - "bridge_last_synced_eth_block", - "The latest finalized eth block that indexer synced", + last_synced_eth_blocks: register_int_gauge_vec_with_registry!( + "bridge_last_synced_eth_blocks", + "The latest synced eth blocks synced for each contract", + &["contract_address"], registry, ) .unwrap(), last_finalized_eth_block: register_int_gauge_with_registry!( "bridge_last_finalized_eth_block", - "The latest finalized eth block that indexer observed", + "The latest finalized eth block observed", registry, ) .unwrap(), @@ -302,6 +318,13 @@ impl BridgeMetrics { registry, ) .unwrap(), + current_bridge_voting_rights: register_int_gauge_vec_with_registry!( + "current_bridge_voting_rights", + "Current voting power in the bridge committee", + &["authority"], + registry + ) + .unwrap(), } } diff --git a/crates/sui-bridge/src/monitor.rs b/crates/sui-bridge/src/monitor.rs index 8a71a83346480..af169737f81d5 100644 --- a/crates/sui-bridge/src/monitor.rs +++ b/crates/sui-bridge/src/monitor.rs @@ -241,6 +241,16 @@ where EthBridgeCommitteeEvents::UpgradedFilter(_) => { bump_eth_counter!("committee_contract_upgraded"); } + EthBridgeCommitteeEvents::BlocklistUpdatedV2Filter(e) => { + bump_eth_counter!(if e.is_blocklisted { + "validator_blocklisted" + } else { + "validator_unblocklisted" + }); + } + EthBridgeCommitteeEvents::ContractUpgradedFilter(_) => { + bump_eth_counter!("committee_contract_upgraded"); + } }, EthBridgeEvent::EthBridgeLimiterEvents(event) => match event { EthBridgeLimiterEvents::InitializedFilter(_) => { @@ -258,6 +268,12 @@ where // This event is deprecated but we keep it for ABI compatibility // TODO: We can safely update abi and remove it once the testnet bridge contract is upgraded EthBridgeLimiterEvents::HourlyTransferAmountUpdatedFilter(_) => (), + EthBridgeLimiterEvents::ContractUpgradedFilter(_) => { + bump_eth_counter!("limiter_contract_upgraded"); + } + EthBridgeLimiterEvents::LimitUpdatedV2Filter(_) => { + bump_eth_counter!("limit_updated"); + } }, EthBridgeEvent::EthBridgeConfigEvents(event) => match event { EthBridgeConfigEvents::InitializedFilter(_) => { @@ -272,6 +288,15 @@ where EthBridgeConfigEvents::TokenPriceUpdatedFilter(_) => { bump_eth_counter!("update_token_price"); } + EthBridgeConfigEvents::ContractUpgradedFilter(_) => { + bump_eth_counter!("config_contract_upgraded"); + } + EthBridgeConfigEvents::TokenPriceUpdatedV2Filter(_) => { + bump_eth_counter!("update_token_price"); + } + EthBridgeConfigEvents::TokensAddedV2Filter(_) => { + bump_eth_counter!("new_token_added"); + } }, EthBridgeEvent::EthCommitteeUpgradeableContractEvents(event) => match event { EthCommitteeUpgradeableContractEvents::InitializedFilter(_) => { @@ -292,6 +317,16 @@ where EthSuiBridgeEvents::InitializedFilter(_) => { bump_eth_counter!("bridge_contract_initialized") } + EthSuiBridgeEvents::ContractUpgradedFilter(_) => { + bump_eth_counter!("bridge_contract_upgraded") + } + EthSuiBridgeEvents::EmergencyOperationFilter(e) => { + if e.paused { + bump_eth_counter!("bridge_paused") + } else { + bump_eth_counter!("bridge_unpaused") + } + } }, } } diff --git a/crates/sui-bridge/src/node.rs b/crates/sui-bridge/src/node.rs index 27d856ed08d25..97b0b2caf22e9 100644 --- a/crates/sui-bridge/src/node.rs +++ b/crates/sui-bridge/src/node.rs @@ -1,6 +1,8 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use crate::types::BridgeCommittee; +use crate::utils::get_committee_voting_power_by_name; use crate::{ action_executor::BridgeActionExecutor, client::bridge_authority_aggregator::BridgeAuthorityAggregator, @@ -62,13 +64,36 @@ pub async fn run_bridge_node( )) .unwrap(); + let committee = Arc::new( + server_config + .sui_client + .get_bridge_committee() + .await + .expect("Failed to get committee"), + ); // Start Client let _handles = if let Some(client_config) = client_config { - start_client_components(client_config, metrics.clone()).await + start_client_components(client_config, committee.clone(), metrics.clone()).await } else { Ok(vec![]) }?; + // Update voting right metrics + // Before reconfiguration happens we only set it once when the node starts + let sui_system = server_config + .sui_client + .sui_client() + .governance_api() + .get_latest_sui_system_state() + .await?; + let committee_name_mapping = get_committee_voting_power_by_name(&committee, sui_system).await; + for (name, voting_power) in committee_name_mapping.into_iter() { + metrics + .current_bridge_voting_rights + .with_label_values(&[name.as_str()]) + .set(voting_power as i64); + } + // Start Server let socket_address = SocketAddr::new( IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), @@ -91,6 +116,7 @@ pub async fn run_bridge_node( // TODO: is there a way to clean up the overrides after it's stored in DB? async fn start_client_components( client_config: BridgeClientConfig, + committee: Arc, metrics: Arc, ) -> anyhow::Result>> { let store: std::sync::Arc = @@ -116,19 +142,16 @@ async fn start_client_components( .expect("Failed to start eth syncer"); all_handles.extend(task_handles); - let (task_handles, sui_events_rx) = - SuiSyncer::new(client_config.sui_client, sui_modules_to_watch) - .run(Duration::from_secs(2)) - .await - .expect("Failed to start sui syncer"); + let (task_handles, sui_events_rx) = SuiSyncer::new( + client_config.sui_client, + sui_modules_to_watch, + metrics.clone(), + ) + .run(Duration::from_secs(2)) + .await + .expect("Failed to start sui syncer"); all_handles.extend(task_handles); - let committee = Arc::new( - sui_client - .get_bridge_committee() - .await - .expect("Failed to get committee"), - ); let bridge_auth_agg = Arc::new(ArcSwap::from(Arc::new(BridgeAuthorityAggregator::new( committee, )))); @@ -501,6 +524,7 @@ mod tests { // send some gas to this address bridge_test_cluster .test_cluster + .inner .transfer_sui_must_exceed(sender_address, client_sui_address, 1000000000) .await; @@ -577,6 +601,7 @@ mod tests { // send some gas to this address let gas_obj = bridge_test_cluster .test_cluster + .inner .transfer_sui_must_exceed(sender_address, client_sui_address, 1000000000) .await; diff --git a/crates/sui-bridge/src/server/mock_handler.rs b/crates/sui-bridge/src/server/mock_handler.rs index f8b5e01fc912c..71fbf1c7c124f 100644 --- a/crates/sui-bridge/src/server/mock_handler.rs +++ b/crates/sui-bridge/src/server/mock_handler.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; use std::net::SocketAddr; use std::str::FromStr; use std::sync::{Arc, Mutex}; +use std::time::Duration; use crate::crypto::BridgeAuthorityKeyPair; use crate::crypto::BridgeAuthoritySignInfo; @@ -28,8 +29,11 @@ use super::make_router; #[derive(Clone)] pub struct BridgeRequestMockHandler { signer: Arc>>, - sui_token_events: - Arc>>>, + sui_token_events: Arc< + Mutex< + HashMap<(TransactionDigest, u16), (BridgeResult, Option)>, + >, + >, sui_token_events_requested: Arc>>, } @@ -47,11 +51,12 @@ impl BridgeRequestMockHandler { tx_digest: TransactionDigest, idx: u16, response: BridgeResult, + delay: Option, ) { self.sui_token_events .lock() .unwrap() - .insert((tx_digest, idx), response); + .insert((tx_digest, idx), (response, delay)); } pub fn get_sui_token_events_requested( @@ -95,26 +100,29 @@ impl BridgeRequestHandlerTrait for BridgeRequestMockHandler { ) -> Result, BridgeError> { let tx_digest = TransactionDigest::from_str(&tx_digest_base58) .map_err(|_e| BridgeError::InvalidTxHash)?; - let preset = self.sui_token_events.lock().unwrap(); - if !preset.contains_key(&(tx_digest, event_idx)) { - // Ok to panic in test - panic!( - "No preset handle_sui_tx_digest result for tx_digest: {}, event_idx: {}", - tx_digest, event_idx - ); + let (result, delay) = { + let preset = self.sui_token_events.lock().unwrap(); + if !preset.contains_key(&(tx_digest, event_idx)) { + // Ok to panic in test + panic!( + "No preset handle_sui_tx_digest result for tx_digest: {}, event_idx: {}", + tx_digest, event_idx + ); + } + let mut requested = self.sui_token_events_requested.lock().unwrap(); + let entry = requested.entry((tx_digest, event_idx)).or_default(); + *entry += 1; + let (result, delay) = preset.get(&(tx_digest, event_idx)).unwrap(); + (result.clone(), *delay) + }; + if let Some(delay) = delay { + tokio::time::sleep(delay).await; } - let mut requested = self.sui_token_events_requested.lock().unwrap(); - let entry = requested.entry((tx_digest, event_idx)).or_default(); - *entry += 1; - let result = preset.get(&(tx_digest, event_idx)).unwrap(); - if let Err(e) = result { - return Err(e.clone()); - } - let signed_action: &sui_types::message_envelope::Envelope< + let signed_action: sui_types::message_envelope::Envelope< crate::types::BridgeAction, crate::crypto::BridgeAuthoritySignInfo, - > = result.as_ref().unwrap(); - Ok(Json(signed_action.clone())) + > = result?; + Ok(Json(signed_action)) } async fn handle_governance_action( diff --git a/crates/sui-bridge/src/sui_client.rs b/crates/sui-bridge/src/sui_client.rs index 60b51e74c1785..7319b2f4b77ca 100644 --- a/crates/sui-bridge/src/sui_client.rs +++ b/crates/sui-bridge/src/sui_client.rs @@ -254,6 +254,7 @@ where "" }); authorities.push(BridgeAuthority { + sui_address, pubkey, voting_power, base_url: base_url.into(), @@ -284,6 +285,10 @@ where } } + pub async fn get_latest_checkpoint_sequence_number(&self) -> BridgeResult { + Ok(self.inner.get_latest_checkpoint_sequence_number().await?) + } + pub async fn execute_transaction_block_with_effects( &self, tx: sui_types::transaction::Transaction, @@ -639,7 +644,7 @@ where #[cfg(test)] mod tests { use crate::crypto::BridgeAuthorityKeyPair; - use crate::BRIDGE_ENABLE_PROTOCOL_VERSION; + use crate::e2e_tests::test_utils::TestClusterWrapperBuilder; use crate::{ events::{EmittedSuiToEthTokenBridgeV1, MoveTokenDepositedEvent}, sui_mock_client::SuiMockClient, @@ -655,7 +660,6 @@ mod tests { use std::str::FromStr; use sui_types::bridge::{BridgeChainId, TOKEN_ID_SUI, TOKEN_ID_USDC}; use sui_types::crypto::get_key_pair; - use test_cluster::TestClusterBuilder; use super::*; use crate::events::{init_all_struct_tags, SuiToEthTokenBridgeV1}; @@ -785,22 +789,24 @@ mod tests { let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair(); bridge_keys.push(kp); } - let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() - .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into()) - .build_with_bridge(bridge_keys, true) + let mut test_cluster = TestClusterWrapperBuilder::new() + .with_bridge_authority_keys(bridge_keys) + .with_deploy_tokens(true) + .build() .await; let bridge_metrics = Arc::new(BridgeMetrics::new_for_testing()); - let sui_client = SuiClient::new(&test_cluster.fullnode_handle.rpc_url, bridge_metrics) - .await - .unwrap(); - let bridge_authority_keys = test_cluster.bridge_authority_keys.take().unwrap(); + let sui_client = + SuiClient::new(&test_cluster.inner.fullnode_handle.rpc_url, bridge_metrics) + .await + .unwrap(); + let bridge_authority_keys = test_cluster.authority_keys_clone(); // Wait until committee is set up test_cluster .trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized() .await; - let context = &mut test_cluster.wallet; + let context = &mut test_cluster.inner.wallet; let sender = context.active_address().unwrap(); let usdc_amount = 5000000; let bridge_object_arg = sui_client diff --git a/crates/sui-bridge/src/sui_mock_client.rs b/crates/sui-bridge/src/sui_mock_client.rs index f3094742d184f..8a9cbcc50b576 100644 --- a/crates/sui-bridge/src/sui_mock_client.rs +++ b/crates/sui-bridge/src/sui_mock_client.rs @@ -7,6 +7,7 @@ use crate::error::{BridgeError, BridgeResult}; use crate::test_utils::DUMMY_MUTALBE_BRIDGE_OBJECT_ARG; use async_trait::async_trait; use std::collections::{HashMap, VecDeque}; +use std::sync::atomic::AtomicU64; use std::sync::{Arc, Mutex}; use sui_json_rpc_types::SuiTransactionBlockResponse; use sui_json_rpc_types::{EventFilter, EventPage, SuiEvent}; @@ -32,7 +33,7 @@ use crate::types::{BridgeAction, BridgeActionStatus, IsBridgePaused}; pub struct SuiMockClient { // the top two fields do not change during tests so we don't need them to be Arc> chain_identifier: String, - latest_checkpoint_sequence_number: u64, + latest_checkpoint_sequence_number: Arc, events: Arc), EventPage>>>, past_event_query_params: Arc)>>>, events_by_tx_digest: @@ -51,7 +52,7 @@ impl SuiMockClient { pub fn default() -> Self { Self { chain_identifier: "".to_string(), - latest_checkpoint_sequence_number: 0, + latest_checkpoint_sequence_number: Arc::new(AtomicU64::new(0)), events: Default::default(), past_event_query_params: Default::default(), events_by_tx_digest: Default::default(), @@ -128,6 +129,11 @@ impl SuiMockClient { *self.wildcard_transaction_response.lock().unwrap() = Some(response); } + pub fn set_latest_checkpoint_sequence_number(&self, value: u64) { + self.latest_checkpoint_sequence_number + .store(value, std::sync::atomic::Ordering::Relaxed); + } + pub fn add_gas_object_info(&self, gas_coin: GasCoin, object_ref: ObjectRef, owner: Owner) { self.get_object_info .lock() @@ -196,7 +202,9 @@ impl SuiClientInner for SuiMockClient { } async fn get_latest_checkpoint_sequence_number(&self) -> Result { - Ok(self.latest_checkpoint_sequence_number) + Ok(self + .latest_checkpoint_sequence_number + .load(std::sync::atomic::Ordering::Relaxed)) } async fn get_mutable_bridge_object_arg(&self) -> Result { diff --git a/crates/sui-bridge/src/sui_syncer.rs b/crates/sui-bridge/src/sui_syncer.rs index 55ef52847287b..cabc396f8c7c8 100644 --- a/crates/sui-bridge/src/sui_syncer.rs +++ b/crates/sui-bridge/src/sui_syncer.rs @@ -6,6 +6,7 @@ use crate::{ error::BridgeResult, + metrics::BridgeMetrics, retry_with_max_elapsed_time, sui_client::{SuiClient, SuiClientInner}, }; @@ -15,6 +16,7 @@ use sui_json_rpc_types::SuiEvent; use sui_types::BRIDGE_PACKAGE_ID; use sui_types::{event::EventID, Identifier}; use tokio::{ + sync::Notify, task::JoinHandle, time::{self, Duration}, }; @@ -29,16 +31,22 @@ pub struct SuiSyncer { // The last transaction that the syncer has fully processed. // Syncer will resume post this transaction (i.e. exclusive), when it starts. cursors: SuiTargetModules, + metrics: Arc, } impl SuiSyncer where C: SuiClientInner + 'static, { - pub fn new(sui_client: Arc>, cursors: SuiTargetModules) -> Self { + pub fn new( + sui_client: Arc>, + cursors: SuiTargetModules, + metrics: Arc, + ) -> Self { Self { sui_client, cursors, + metrics, } } @@ -59,6 +67,7 @@ where let mut task_handles = vec![]; for (module, cursor) in self.cursors { + let metrics = self.metrics.clone(); let events_rx_clone: mysten_metrics::metered_channel::Sender<( Identifier, Vec, @@ -70,7 +79,8 @@ where cursor, events_rx_clone, sui_client_clone, - query_interval + query_interval, + metrics, ) )); } @@ -85,10 +95,33 @@ where events_sender: mysten_metrics::metered_channel::Sender<(Identifier, Vec)>, sui_client: Arc>, query_interval: Duration, + metrics: Arc, ) { tracing::info!(?module, ?cursor, "Starting sui events listening task"); let mut interval = time::interval(query_interval); interval.set_missed_tick_behavior(time::MissedTickBehavior::Skip); + + // Create a task to update metrics + let notify = Arc::new(Notify::new()); + let notify_clone = notify.clone(); + let sui_client_clone = sui_client.clone(); + let last_synced_sui_checkpoints_metric = metrics + .last_synced_sui_checkpoints + .with_label_values(&[&module.to_string()]); + spawn_logged_monitored_task!(async move { + loop { + notify_clone.notified().await; + let Ok(Ok(latest_checkpoint_sequence_number)) = retry_with_max_elapsed_time!( + sui_client_clone.get_latest_checkpoint_sequence_number(), + Duration::from_secs(120) + ) else { + tracing::error!("Failed to query latest checkpoint sequence number from sui client after retry"); + continue; + }; + last_synced_sui_checkpoints_metric.set(latest_checkpoint_sequence_number as i64); + } + }); + loop { interval.tick().await; let Ok(Ok(events)) = retry_with_max_elapsed_time!( @@ -101,6 +134,11 @@ where let len = events.data.len(); if len != 0 { + if !events.has_next_page { + // If this is the last page, it means we have processed all events up to the latest checkpoint + // We can then update the latest checkpoint metric. + notify.notify_one(); + } events_sender .send((module.clone(), events.data)) .await @@ -129,7 +167,7 @@ mod tests { telemetry_subscribers::init_for_testing(); let registry = Registry::new(); mysten_metrics::init_metrics(®istry); - + let metrics = Arc::new(BridgeMetrics::new(®istry)); let mock = SuiMockClient::default(); let client = Arc::new(SuiClient::new_for_testing(mock.clone())); let module_foo = Identifier::new("Foo").unwrap(); @@ -147,7 +185,7 @@ mod tests { (module_bar.clone(), Some(cursor)), ]); let interval = Duration::from_millis(200); - let (_handles, mut events_rx) = SuiSyncer::new(client, target_modules) + let (_handles, mut events_rx) = SuiSyncer::new(client, target_modules, metrics.clone()) .run(interval) .await .unwrap(); @@ -155,6 +193,7 @@ mod tests { // Initially there are no events assert_no_more_events(interval, &mut events_rx).await; + mock.set_latest_checkpoint_sequence_number(999); // Module Foo has new events let mut event_1: SuiEvent = SuiEvent::random_for_testing(); let package_id = BRIDGE_PACKAGE_ID; @@ -180,6 +219,14 @@ mod tests { assert_eq!(received_events[1].id, event_1.id); // No more assert_no_more_events(interval, &mut events_rx).await; + assert_eq!( + metrics + .last_synced_sui_checkpoints + .get_metric_with_label_values(&["Foo"]) + .unwrap() + .get(), + 999 + ); // Module Bar has new events let mut event_2: SuiEvent = SuiEvent::random_for_testing(); @@ -188,7 +235,7 @@ mod tests { let module_bar_events_1 = EventPage { data: vec![event_2.clone()], next_cursor: Some(event_2.id), - has_next_page: false, + has_next_page: true, // Set to true so that the syncer will not update the last synced checkpoint }; add_event_response(&mock, module_bar.clone(), event_2.id, empty_events.clone()); @@ -200,6 +247,14 @@ mod tests { assert_eq!(received_events[0].id, event_2.id); // No more assert_no_more_events(interval, &mut events_rx).await; + assert_eq!( + metrics + .last_synced_sui_checkpoints + .get_metric_with_label_values(&["Bar"]) + .unwrap() + .get(), + 0, // Not updated + ); Ok(()) } diff --git a/crates/sui-bridge/src/sui_transaction_builder.rs b/crates/sui-bridge/src/sui_transaction_builder.rs index ff188d55927d8..9aabf39b179b9 100644 --- a/crates/sui-bridge/src/sui_transaction_builder.rs +++ b/crates/sui-bridge/src/sui_transaction_builder.rs @@ -617,6 +617,7 @@ pub fn build_committee_update_url_transaction( #[cfg(test)] mod tests { use crate::crypto::BridgeAuthorityKeyPair; + use crate::e2e_tests::test_utils::TestClusterWrapperBuilder; use crate::metrics::BridgeMetrics; use crate::sui_client::SuiClient; use crate::types::BridgeAction; @@ -629,7 +630,6 @@ mod tests { approve_action_with_validator_secrets, bridge_token, get_test_eth_to_sui_bridge_action, get_test_sui_to_eth_bridge_action, }, - BRIDGE_ENABLE_PROTOCOL_VERSION, }; use ethers::types::Address as EthAddress; use std::collections::HashMap; @@ -637,7 +637,6 @@ mod tests { use sui_types::bridge::{BridgeChainId, TOKEN_ID_BTC, TOKEN_ID_USDC}; use sui_types::crypto::get_key_pair; use sui_types::crypto::ToFromBytes; - use test_cluster::TestClusterBuilder; #[tokio::test(flavor = "multi_thread", worker_threads = 8)] async fn test_build_sui_transaction_for_token_transfer() { @@ -647,23 +646,24 @@ mod tests { let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair(); bridge_keys.push(kp); } - let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() - .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into()) - .build_with_bridge(bridge_keys, true) + let mut test_cluster = TestClusterWrapperBuilder::new() + .with_bridge_authority_keys(bridge_keys) + .with_deploy_tokens(true) + .build() .await; let metrics = Arc::new(BridgeMetrics::new_for_testing()); - let sui_client = SuiClient::new(&test_cluster.fullnode_handle.rpc_url, metrics) + let sui_client = SuiClient::new(&test_cluster.inner.fullnode_handle.rpc_url, metrics) .await .unwrap(); - let bridge_authority_keys = test_cluster.bridge_authority_keys.take().unwrap(); + let bridge_authority_keys = test_cluster.authority_keys_clone(); // Note: We don't call `sui_client.get_bridge_committee` here because it will err if the committee // is not initialized during the construction of `BridgeCommittee`. test_cluster .trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized() .await; - let context = &mut test_cluster.wallet; + let context = &mut test_cluster.inner.wallet; let sender = context.active_address().unwrap(); let usdc_amount = 5000000; let bridge_object_arg = sui_client @@ -725,16 +725,16 @@ mod tests { let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair(); bridge_keys.push(kp); } - let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() - .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into()) - .with_num_validators(num_valdiator) - .build_with_bridge(bridge_keys, true) + let mut test_cluster = TestClusterWrapperBuilder::new() + .with_bridge_authority_keys(bridge_keys) + .with_deploy_tokens(true) + .build() .await; let metrics = Arc::new(BridgeMetrics::new_for_testing()); - let sui_client = SuiClient::new(&test_cluster.fullnode_handle.rpc_url, metrics) + let sui_client = SuiClient::new(&test_cluster.inner.fullnode_handle.rpc_url, metrics) .await .unwrap(); - let bridge_authority_keys = test_cluster.bridge_authority_keys.take().unwrap(); + let bridge_authority_keys = test_cluster.authority_keys_clone(); // Wait until committee is set up test_cluster @@ -743,7 +743,7 @@ mod tests { let summary = sui_client.get_bridge_summary().await.unwrap(); assert!(!summary.is_frozen); - let context = &mut test_cluster.wallet; + let context = &mut test_cluster.inner.wallet; let bridge_object_arg = sui_client .get_mutable_bridge_object_arg_must_succeed() .await; @@ -796,15 +796,16 @@ mod tests { let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair(); bridge_keys.push(kp); } - let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() - .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into()) - .build_with_bridge(bridge_keys, true) + let mut test_cluster = TestClusterWrapperBuilder::new() + .with_bridge_authority_keys(bridge_keys) + .with_deploy_tokens(true) + .build() .await; let metrics = Arc::new(BridgeMetrics::new_for_testing()); - let sui_client = SuiClient::new(&test_cluster.fullnode_handle.rpc_url, metrics) + let sui_client = SuiClient::new(&test_cluster.inner.fullnode_handle.rpc_url, metrics) .await .unwrap(); - let bridge_authority_keys = test_cluster.bridge_authority_keys.take().unwrap(); + let bridge_authority_keys = test_cluster.authority_keys_clone(); // Wait until committee is set up test_cluster @@ -816,7 +817,7 @@ mod tests { assert!(!member.1.blocklisted); } - let context = &mut test_cluster.wallet; + let context = &mut test_cluster.inner.wallet; let bridge_object_arg = sui_client .get_mutable_bridge_object_arg_must_succeed() .await; @@ -885,15 +886,16 @@ mod tests { let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair(); bridge_keys.push(kp); } - let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() - .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into()) - .build_with_bridge(bridge_keys, true) + let mut test_cluster = TestClusterWrapperBuilder::new() + .with_bridge_authority_keys(bridge_keys) + .with_deploy_tokens(true) + .build() .await; let metrics = Arc::new(BridgeMetrics::new_for_testing()); - let sui_client = SuiClient::new(&test_cluster.fullnode_handle.rpc_url, metrics) + let sui_client = SuiClient::new(&test_cluster.inner.fullnode_handle.rpc_url, metrics) .await .unwrap(); - let bridge_authority_keys = test_cluster.bridge_authority_keys.take().unwrap(); + let bridge_authority_keys = test_cluster.authority_keys_clone(); // Wait until committee is set up test_cluster @@ -909,7 +911,7 @@ mod tests { .map(|(s, d, l)| ((s, d), l)) .collect::>(); - let context = &mut test_cluster.wallet; + let context = &mut test_cluster.inner.wallet; let bridge_object_arg = sui_client .get_mutable_bridge_object_arg_must_succeed() .await; @@ -955,15 +957,16 @@ mod tests { let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair(); bridge_keys.push(kp); } - let mut test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() - .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into()) - .build_with_bridge(bridge_keys, true) + let mut test_cluster = TestClusterWrapperBuilder::new() + .with_bridge_authority_keys(bridge_keys) + .with_deploy_tokens(true) + .build() .await; let metrics = Arc::new(BridgeMetrics::new_for_testing()); - let sui_client = SuiClient::new(&test_cluster.fullnode_handle.rpc_url, metrics) + let sui_client = SuiClient::new(&test_cluster.inner.fullnode_handle.rpc_url, metrics) .await .unwrap(); - let bridge_authority_keys = test_cluster.bridge_authority_keys.take().unwrap(); + let bridge_authority_keys = test_cluster.authority_keys_clone(); // Note: We don't call `sui_client.get_bridge_committee` here because it will err if the committee // is not initialized during the construction of `BridgeCommittee`. @@ -973,7 +976,7 @@ mod tests { let notional_values = sui_client.get_notional_values().await.unwrap(); assert_ne!(notional_values[&TOKEN_ID_USDC], 69_000 * USD_MULTIPLIER); - let context = &mut test_cluster.wallet; + let context = &mut test_cluster.inner.wallet; let bridge_object_arg = sui_client .get_mutable_bridge_object_arg_must_succeed() .await; diff --git a/crates/sui-bridge/src/test_utils.rs b/crates/sui-bridge/src/test_utils.rs index 1449b87547268..a98def06fe570 100644 --- a/crates/sui-bridge/src/test_utils.rs +++ b/crates/sui-bridge/src/test_utils.rs @@ -65,6 +65,7 @@ pub fn get_test_authority_and_key( let (_, kp): (_, fastcrypto::secp256k1::Secp256k1KeyPair) = get_key_pair(); let pubkey = kp.public().clone(); let authority = BridgeAuthority { + sui_address: SuiAddress::random_for_testing_only(), pubkey: pubkey.clone(), voting_power, base_url: format!("http://127.0.0.1:{}", port), diff --git a/crates/sui-bridge/src/types.rs b/crates/sui-bridge/src/types.rs index 1da2232d4ff40..d4d69e1bf10ed 100644 --- a/crates/sui-bridge/src/types.rs +++ b/crates/sui-bridge/src/types.rs @@ -23,6 +23,7 @@ use serde::{Deserialize, Serialize}; use shared_crypto::intent::IntentScope; use std::collections::{BTreeMap, BTreeSet}; use std::fmt::Debug; +use sui_types::base_types::SuiAddress; use sui_types::bridge::{ BridgeChainId, MoveTypeTokenTransferPayload, APPROVAL_THRESHOLD_ADD_TOKENS_ON_EVM, APPROVAL_THRESHOLD_ADD_TOKENS_ON_SUI, BRIDGE_COMMITTEE_MAXIMAL_VOTING_POWER, @@ -51,6 +52,7 @@ pub const BRIDGE_UNPAUSED: bool = false; #[derive(Debug, Eq, PartialEq, Clone)] pub struct BridgeAuthority { + pub sui_address: SuiAddress, pub pubkey: BridgeAuthorityPublicKey, pub voting_power: u64, pub base_url: String, @@ -119,6 +121,30 @@ impl BridgeCommittee { pub fn total_blocklisted_stake(&self) -> StakeUnit { self.total_blocklisted_stake } + + pub fn active_stake(&self, member: &BridgeAuthorityPublicKeyBytes) -> StakeUnit { + self.members + .get(member) + .map(|a| if a.is_blocklisted { 0 } else { a.voting_power }) + .unwrap_or(0) + } +} + +impl core::fmt::Display for BridgeCommittee { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> std::fmt::Result { + for m in self.members.values() { + writeln!( + f, + "pubkey: {:?}, url: {:?}, stake: {:?}, blocklisted: {}, eth address: {:x}", + Hex::encode(m.pubkey_bytes().as_bytes()), + m.base_url, + m.voting_power, + m.is_blocklisted, + m.pubkey_bytes().to_eth_address(), + )?; + } + Ok(()) + } } impl core::fmt::Display for BridgeCommittee { diff --git a/crates/sui-bridge/src/utils.rs b/crates/sui-bridge/src/utils.rs index 3bc787842b906..7990ec79e0cec 100644 --- a/crates/sui-bridge/src/utils.rs +++ b/crates/sui-bridge/src/utils.rs @@ -10,6 +10,7 @@ use crate::config::{ use crate::crypto::BridgeAuthorityKeyPair; use crate::crypto::BridgeAuthorityPublicKeyBytes; use crate::server::APPLICATION_JSON; +use crate::types::BridgeCommittee; use crate::types::{AddTokensOnSuiAction, BridgeAction}; use anyhow::anyhow; use ethers::core::k256::ecdsa::SigningKey; @@ -24,6 +25,7 @@ use fastcrypto::secp256k1::Secp256k1KeyPair; use fastcrypto::traits::EncodeDecodeBase64; use fastcrypto::traits::KeyPair; use futures::future::join_all; +use std::collections::BTreeMap; use std::path::PathBuf; use std::str::FromStr; use std::sync::Arc; @@ -37,10 +39,12 @@ use sui_test_transaction_builder::TestTransactionBuilder; use sui_types::base_types::SuiAddress; use sui_types::bridge::BridgeChainId; use sui_types::bridge::{BRIDGE_MODULE_NAME, BRIDGE_REGISTER_FOREIGN_TOKEN_FUNCTION_NAME}; +use sui_types::committee::StakeUnit; use sui_types::crypto::get_key_pair; use sui_types::crypto::SuiKeyPair; use sui_types::crypto::ToFromBytes; use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder; +use sui_types::sui_system_state::sui_system_state_summary::SuiSystemStateSummary; use sui_types::transaction::{ObjectArg, TransactionData}; use sui_types::BRIDGE_PACKAGE_ID; @@ -103,11 +107,13 @@ pub fn generate_bridge_client_key_and_write_to_file( pub async fn get_eth_contract_addresses( bridge_proxy_address: EthAddress, provider: &Arc>, -) -> anyhow::Result<(EthAddress, EthAddress, EthAddress, EthAddress)> { +) -> anyhow::Result<(EthAddress, EthAddress, EthAddress, EthAddress, EthAddress)> { let sui_bridge = EthSuiBridge::new(bridge_proxy_address, provider.clone()); let committee_address: EthAddress = sui_bridge.committee().call().await?; let limiter_address: EthAddress = sui_bridge.limiter().call().await?; let vault_address: EthAddress = sui_bridge.vault().call().await?; + let vault = EthBridgeVault::new(vault_address, provider.clone()); + let weth_address: EthAddress = vault.w_eth().call().await?; let committee = EthBridgeCommittee::new(committee_address, provider.clone()); let config_address: EthAddress = committee.config().call().await?; @@ -116,6 +122,7 @@ pub async fn get_eth_contract_addresses a } Ok(()) } + +/// Return a mappping from validator name to their bridge voting power. +/// If a validator is not in the Sui committee, we will use its base URL as the name. +pub async fn get_committee_voting_power_by_name( + bridge_committee: &Arc, + system_state: SuiSystemStateSummary, +) -> BTreeMap { + let mut sui_committee: BTreeMap<_, _> = system_state + .active_validators + .iter() + .map(|v| (v.sui_address, v.name.clone())) + .collect(); + bridge_committee + .members() + .iter() + .map(|v| { + ( + sui_committee + .remove(&v.1.sui_address) + .unwrap_or(v.1.base_url.clone()), + v.1.voting_power, + ) + }) + .collect() +} diff --git a/crates/sui-cluster-test/src/cluster.rs b/crates/sui-cluster-test/src/cluster.rs index b90c8263ce4da..decf58e81714d 100644 --- a/crates/sui-cluster-test/src/cluster.rs +++ b/crates/sui-cluster-test/src/cluster.rs @@ -11,7 +11,9 @@ use sui_config::{PersistedConfig, SUI_KEYSTORE_FILENAME, SUI_NETWORK_CONFIG}; use sui_graphql_rpc::config::{ConnectionConfig, ServiceConfig}; use sui_graphql_rpc::test_infra::cluster::start_graphql_server_with_fn_rpc; use sui_indexer::tempdb::TempDb; -use sui_indexer::test_utils::{start_test_indexer, ReaderWriterConfig}; +use sui_indexer::test_utils::{ + start_indexer_jsonrpc_for_testing, start_indexer_writer_for_testing, +}; use sui_keys::keystore::{AccountKeystore, FileBasedKeystore, Keystore}; use sui_sdk::sui_client_config::{SuiClientConfig, SuiEnv}; use sui_sdk::wallet_context::WalletContext; @@ -229,36 +231,34 @@ impl Cluster for LocalNewCluster { let graphql_address = format!("127.0.0.1:{}", get_available_port("127.0.0.1")); let graphql_url = format!("http://{graphql_address}"); - // Start indexer writer - let (_, _, writer_token) = start_test_indexer( + let (_, _, writer_token) = start_indexer_writer_for_testing( pg_address.clone(), - fullnode_url.clone(), - ReaderWriterConfig::writer_mode(None, None), - data_ingestion_path.path().to_path_buf(), + None, + None, + Some(data_ingestion_path.path().to_path_buf()), + None, /* cancel */ ) .await; cancellation_tokens.push(writer_token.drop_guard()); // Start indexer jsonrpc service - let (_, _, reader_token) = start_test_indexer( + let (_, reader_token) = start_indexer_jsonrpc_for_testing( pg_address.clone(), fullnode_url.clone(), - ReaderWriterConfig::reader_mode(indexer_jsonrpc_address.clone()), - data_ingestion_path.path().to_path_buf(), + indexer_jsonrpc_address.clone(), + None, /* cancel */ ) .await; cancellation_tokens.push(reader_token.drop_guard()); // Start the graphql service let graphql_address = graphql_address.parse::()?; - let graphql_connection_config = ConnectionConfig::new( - Some(graphql_address.port()), - Some(graphql_address.ip().to_string()), - Some(pg_address), - None, - None, - None, - ); + let graphql_connection_config = ConnectionConfig { + port: graphql_address.port(), + host: graphql_address.ip().to_string(), + db_url: pg_address, + ..Default::default() + }; start_graphql_server_with_fn_rpc( graphql_connection_config.clone(), diff --git a/crates/sui-config/Cargo.toml b/crates/sui-config/Cargo.toml index ede803d656540..3db9576edc324 100644 --- a/crates/sui-config/Cargo.toml +++ b/crates/sui-config/Cargo.toml @@ -33,6 +33,7 @@ sui-keys.workspace = true sui-protocol-config.workspace = true sui-types.workspace = true move-vm-config.workspace = true +sui-rest-api.workspace = true [dev-dependencies] insta.workspace = true diff --git a/crates/sui-config/data/fullnode-template.yaml b/crates/sui-config/data/fullnode-template.yaml index f0816a26990d4..a3c580a86d5e5 100644 --- a/crates/sui-config/data/fullnode-template.yaml +++ b/crates/sui-config/data/fullnode-template.yaml @@ -19,3 +19,20 @@ authority-store-pruning-config: max-checkpoints-in-batch: 10 max-transactions-in-batch: 1000 pruning-run-delay-seconds: 60 + +state-archive-read-config: + - object-store-config: + object-store: "S3" + # Use mysten-testnet-archives for testnet + # Use mysten-mainnet-archives for mainnet + bucket: "mysten--archives" + # you can either provide your own aws credentials via "aws-secret-access-key" and + # "aws-access-key-id" or set no-sign-request: true + no-sign-request: true + aws-region: "us-west-2" + object-store-connection-limit: 20 + # How many objects to read ahead when catching up + concurrency: 5 + # Whether to prune local state based on latest checkpoint in archive. + # This should stay false for most use cases + use-for-pruning-watermark: false diff --git a/crates/sui-config/src/node.rs b/crates/sui-config/src/node.rs index 208f014108581..1ab9ec678d364 100644 --- a/crates/sui-config/src/node.rs +++ b/crates/sui-config/src/node.rs @@ -65,6 +65,8 @@ pub struct NodeConfig { #[serde(default)] pub enable_experimental_rest_api: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub rest: Option, #[serde(default = "default_metrics_address")] pub metrics_address: SocketAddr, @@ -206,16 +208,25 @@ pub struct NodeConfig { pub enable_db_write_stall: Option, } -#[derive(Clone, Debug, Deserialize, Serialize, Default)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub enum ExecutionCacheConfig { - #[default] PassthroughCache, WritebackCache { + /// Maximum number of entries in each cache. (There are several different caches). + /// If None, the default of 10000 is used. max_cache_size: Option, }, } +impl Default for ExecutionCacheConfig { + fn default() -> Self { + ExecutionCacheConfig::WritebackCache { + max_cache_size: None, + } + } +} + #[derive(Clone, Copy, Debug, Deserialize, Serialize)] #[serde(rename_all = "lowercase")] pub enum ServerType { @@ -275,7 +286,9 @@ pub fn default_zklogin_oauth_providers() -> BTreeMap> { "Onefc".to_string(), "FanTV".to_string(), "AwsTenant-region:us-east-1-tenant_id:us-east-1_LPSLCkC3A".to_string(), // test tenant in mysten aws - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8".to_string(), // ambrus, external partner + "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8".to_string(), // Ambrus, external partner + "Arden".to_string(), // Arden partner + "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es".to_string(), // Trace, external partner ]); // providers that are available for mainnet and testnet. @@ -284,11 +297,14 @@ pub fn default_zklogin_oauth_providers() -> BTreeMap> { "Facebook".to_string(), "Twitch".to_string(), "Apple".to_string(), - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8".to_string(), + "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8".to_string(), // Ambrus, external partner "KarrierOne".to_string(), "Credenza3".to_string(), "Playtron".to_string(), "Onefc".to_string(), + "Threedos".to_string(), + "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es".to_string(), // Trace, external partner + "Arden".to_string(), ]); map.insert(Chain::Mainnet, providers.clone()); map.insert(Chain::Testnet, providers); @@ -717,6 +733,10 @@ impl Default for AuthorityStorePruningConfig { } impl AuthorityStorePruningConfig { + pub fn set_num_epochs_to_retain(&mut self, num_epochs_to_retain: u64) { + self.num_epochs_to_retain = num_epochs_to_retain; + } + pub fn set_num_epochs_to_retain_for_checkpoints(&mut self, num_epochs_to_retain: Option) { self.num_epochs_to_retain_for_checkpoints = num_epochs_to_retain; } @@ -856,7 +876,7 @@ pub struct AuthorityOverloadConfig { } fn default_max_txn_age_in_queue() -> Duration { - Duration::from_secs(1) + Duration::from_millis(200) } fn default_overload_monitor_interval() -> Duration { @@ -892,7 +912,7 @@ fn default_max_transaction_manager_queue_length() -> usize { } fn default_max_transaction_manager_per_object_queue_length() -> usize { - 100 + 20 } impl Default for AuthorityOverloadConfig { diff --git a/crates/sui-config/src/object_storage_config.rs b/crates/sui-config/src/object_storage_config.rs index 7bdf75defe4e3..84702156174ba 100644 --- a/crates/sui-config/src/object_storage_config.rs +++ b/crates/sui-config/src/object_storage_config.rs @@ -105,6 +105,13 @@ fn default_object_store_connection_limit() -> usize { 20 } +fn no_timeout_options() -> ClientOptions { + ClientOptions::new() + .with_timeout_disabled() + .with_connect_timeout_disabled() + .with_pool_idle_timeout(std::time::Duration::from_secs(300)) +} + impl ObjectStoreConfig { fn new_local_fs(&self) -> Result, anyhow::Error> { info!(directory=?self.directory, object_store_type="File", "Object Store"); @@ -125,7 +132,9 @@ impl ObjectStoreConfig { info!(bucket=?self.bucket, object_store_type="S3", "Object Store"); - let mut builder = AmazonS3Builder::new().with_imdsv1_fallback(); + let mut builder = AmazonS3Builder::new() + .with_client_options(no_timeout_options()) + .with_imdsv1_fallback(); if self.aws_virtual_hosted_style_request { builder = builder.with_virtual_hosted_style_request(true); @@ -182,6 +191,8 @@ impl ObjectStoreConfig { if let Some(account) = &self.google_service_account { builder = builder.with_service_account_path(account); } + + let mut client_options = no_timeout_options(); if let Some(google_project_id) = &self.google_project_id { let x_project_header = HeaderName::from_static("x-goog-user-project"); let iam_req_header = HeaderName::from_static("userproject"); @@ -189,10 +200,9 @@ impl ObjectStoreConfig { let mut headers = HeaderMap::new(); headers.insert(x_project_header, HeaderValue::from_str(google_project_id)?); headers.insert(iam_req_header, HeaderValue::from_str(google_project_id)?); - - builder = - builder.with_client_options(ClientOptions::new().with_default_headers(headers)); + client_options = client_options.with_default_headers(headers); } + builder = builder.with_client_options(client_options); Ok(Arc::new(LimitStore::new( builder.build().context("Invalid gcs config")?, @@ -206,7 +216,7 @@ impl ObjectStoreConfig { info!(bucket=?self.bucket, account=?self.azure_storage_account, object_store_type="Azure", "Object Store"); - let mut builder = MicrosoftAzureBuilder::new(); + let mut builder = MicrosoftAzureBuilder::new().with_client_options(no_timeout_options()); if let Some(bucket) = &self.bucket { builder = builder.with_container_name(bucket); diff --git a/crates/sui-config/src/p2p.rs b/crates/sui-config/src/p2p.rs index 7b7b4ba59198d..fd8f88ce1d18b 100644 --- a/crates/sui-config/src/p2p.rs +++ b/crates/sui-config/src/p2p.rs @@ -319,6 +319,12 @@ pub struct DiscoveryConfig { /// to this peer, nor advertise this peer's info to other peers in the network. #[serde(skip_serializing_if = "Vec::is_empty", default)] pub allowlisted_peers: Vec, + + /// If true, Discovery will require all provided peer information to be signed + /// by the originating peer. + /// + /// If unspecified, this will default to true. + pub enable_node_info_signatures: Option, } impl DiscoveryConfig { @@ -345,6 +351,10 @@ impl DiscoveryConfig { // defaults None to Public self.access_type.unwrap_or(AccessType::Public) } + + pub fn enable_node_info_signatures(&self) -> bool { + self.enable_node_info_signatures.unwrap_or(true) + } } #[derive(Clone, Debug, Default, Deserialize, Serialize)] diff --git a/crates/sui-core/src/authority.rs b/crates/sui-core/src/authority.rs index 3389685e003bd..c3268d9247faf 100644 --- a/crates/sui-core/src/authority.rs +++ b/crates/sui-core/src/authority.rs @@ -4,6 +4,7 @@ use crate::execution_cache::ExecutionCacheTraitPointers; use crate::execution_cache::TransactionCacheRead; +use crate::jsonrpc_index::CoinIndexKey2; use crate::rest_index::RestIndexStore; use crate::transaction_outputs::TransactionOutputs; use crate::verify_indexes::verify_indexes; @@ -45,11 +46,14 @@ use std::{ use sui_config::node::{AuthorityOverloadConfig, StateDebugDumpConfig}; use sui_config::NodeConfig; use sui_types::crypto::RandomnessRound; +use sui_types::dynamic_field::visitor as DFV; use sui_types::execution_status::ExecutionStatus; use sui_types::inner_temporary_store::PackageStoreWithFallback; use sui_types::layout_resolver::into_struct_layout; use sui_types::layout_resolver::LayoutResolver; use sui_types::messages_consensus::{AuthorityCapabilitiesV1, AuthorityCapabilitiesV2}; +use sui_types::object::bounded_visitor::BoundedVisitor; +use sui_types::transaction_executor::SimulateTransactionResult; use tap::{TapFallible, TapOptional}; use tokio::sync::mpsc::unbounded_channel; use tokio::sync::{mpsc, oneshot, RwLock}; @@ -63,6 +67,8 @@ use mysten_metrics::{monitored_scope, spawn_monitored_task}; use mamoru_sui_sniffer::SuiSniffer; use move_core_types::trace::CallTrace; +use crate::jsonrpc_index::IndexStore; +use crate::jsonrpc_index::{CoinInfo, ObjectIndexChanges}; use once_cell::sync::OnceCell; use shared_crypto::intent::{AppId, Intent, IntentMessage, IntentScope, IntentVersion}; use sui_archival::reader::ArchiveReaderBalancer; @@ -75,17 +81,15 @@ use sui_json_rpc_types::{ SuiTransactionBlockEvents, TransactionFilter, }; use sui_macros::{fail_point, fail_point_async, fail_point_if}; -use sui_storage::indexes::{CoinInfo, ObjectIndexChanges}; use sui_storage::key_value_store::{TransactionKeyValueStore, TransactionKeyValueStoreTrait}; use sui_storage::key_value_store_metrics::KeyValueStoreMetrics; -use sui_storage::IndexStore; use sui_types::authenticator_state::get_authenticator_state; use sui_types::committee::{EpochId, ProtocolVersion}; use sui_types::crypto::{default_hash, AuthoritySignInfo, Signer}; use sui_types::deny_list_v1::check_coin_deny_list_v1; use sui_types::digests::ChainIdentifier; use sui_types::digests::TransactionEventsDigest; -use sui_types::dynamic_field::{DynamicFieldInfo, DynamicFieldName, DynamicFieldType}; +use sui_types::dynamic_field::{DynamicFieldInfo, DynamicFieldName}; use sui_types::effects::{ InputSharedObject, SignedTransactionEffects, TransactionEffects, TransactionEffectsAPI, TransactionEvents, VerifiedCertifiedTransactionEffects, VerifiedSignedTransactionEffects, @@ -230,9 +234,11 @@ pub struct AuthorityMetrics { batch_size: Histogram, authority_state_handle_transaction_latency: Histogram, + authority_state_handle_transaction_v2_latency: Histogram, execute_certificate_latency_single_writer: Histogram, execute_certificate_latency_shared_object: Histogram, + await_transaction_latency: Histogram, execute_certificate_with_effects_latency: Histogram, internal_execution_latency: Histogram, @@ -276,7 +282,7 @@ pub struct AuthorityMetrics { post_processing_total_tx_had_event_processed: IntCounter, post_processing_total_failures: IntCounter, - /// Consensus handler metrics + /// Consensus commit and transaction handler metrics pub consensus_handler_processed: IntCounterVec, pub consensus_handler_transaction_sizes: HistogramVec, pub consensus_handler_num_low_scoring_authorities: IntGauge, @@ -290,6 +296,8 @@ pub struct AuthorityMetrics { pub consensus_committed_user_transactions: IntGaugeVec, pub consensus_calculated_throughput: IntGauge, pub consensus_calculated_throughput_profile: IntGauge, + pub consensus_transaction_handler_processed: IntCounterVec, + pub consensus_transaction_handler_fastpath_executions: IntCounter, pub limits_metrics: Arc, @@ -436,8 +444,22 @@ impl AuthorityMetrics { registry, ) .unwrap(), + authority_state_handle_transaction_v2_latency: register_histogram_with_registry!( + "authority_state_handle_transaction_v2_latency", + "Latency of handling transactions with v2", + LATENCY_SEC_BUCKETS.to_vec(), + registry, + ) + .unwrap(), execute_certificate_latency_single_writer, execute_certificate_latency_shared_object, + await_transaction_latency: register_histogram_with_registry!( + "await_transaction_latency", + "Latency of awaiting user transaction execution, including waiting for inputs", + LATENCY_SEC_BUCKETS.to_vec(), + registry, + ) + .unwrap(), execute_certificate_with_effects_latency: register_histogram_with_registry!( "authority_state_execute_certificate_with_effects_latency", "Latency of executing certificates with effects, including waiting for inputs", @@ -743,6 +765,17 @@ impl AuthorityMetrics { "The current active calculated throughput profile", registry ).unwrap(), + consensus_transaction_handler_processed: register_int_counter_vec_with_registry!( + "consensus_transaction_handler_processed", + "Number of transactions processed by consensus transaction handler, by whether they are certified or rejected.", + &["outcome"], + registry + ).unwrap(), + consensus_transaction_handler_fastpath_executions: register_int_counter_with_registry!( + "consensus_transaction_handler_fastpath_executions", + "Number of fastpath transactions sent for execution by consensus transaction handler", + registry, + ).unwrap(), execution_queueing_latency: LatencyObserver::new(), txn_ready_rate_tracker: Arc::new(Mutex::new(RateTracker::new(Duration::from_secs(10)))), execution_rate_tracker: Arc::new(Mutex::new(RateTracker::new(Duration::from_secs(10)))), @@ -845,14 +878,11 @@ impl AuthorityState { self.checkpoint_store.get_epoch_state_commitments(epoch) } - /// This is a private method and should be kept that way. It doesn't check whether - /// the provided transaction is a system transaction, and hence can only be called internally. - #[instrument(level = "trace", skip_all)] - async fn handle_transaction_impl( + fn handle_transaction_deny_checks( &self, - transaction: VerifiedTransaction, + transaction: &VerifiedTransaction, epoch_store: &Arc, - ) -> SuiResult { + ) -> SuiResult { let tx_digest = transaction.digest(); let tx_data = transaction.data().transaction_data(); @@ -906,6 +936,23 @@ impl AuthorityState { )?; } + Ok(checked_input_objects) + } + + /// This is a private method and should be kept that way. It doesn't check whether + /// the provided transaction is a system transaction, and hence can only be called internally. + #[instrument(level = "trace", skip_all)] + async fn handle_transaction_impl( + &self, + transaction: VerifiedTransaction, + epoch_store: &Arc, + ) -> SuiResult { + // Ensure that validator cannot reconfigure while we are signing the tx + let _execution_lock = self.execution_lock_for_signing().await; + + let checked_input_objects = + self.handle_transaction_deny_checks(&transaction, epoch_store)?; + let owned_objects = checked_input_objects.inner().filter_owned_objects(); let signed_transaction = VerifiedSignedTransaction::new( @@ -947,16 +994,6 @@ impl AuthorityState { .start_timer(); self.metrics.tx_orders.inc(); - // The should_accept_user_certs check here is best effort, because - // between a validator signs a tx and a cert is formed, the validator - // could close the window. - if !epoch_store - .get_reconfig_state_read_lock_guard() - .should_accept_user_certs() - { - return Err(SuiError::ValidatorHaltedAtEpochEnd); - } - let signed = self.handle_transaction_impl(transaction, epoch_store).await; match signed { Ok(s) => { @@ -987,6 +1024,51 @@ impl AuthorityState { } } + #[instrument(level = "trace", skip_all)] + pub async fn handle_transaction_v2( + &self, + epoch_store: &Arc, + transaction: VerifiedTransaction, + ) -> SuiResult> { + let tx_digest = *transaction.digest(); + debug!("handle_transaction_v2"); + + // Ensure an idempotent answer. + let tx_status = self.get_transaction_status(&tx_digest, epoch_store)?; + if tx_status.is_some() { + return Ok(tx_status); + } + + let _metrics_guard = self + .metrics + .authority_state_handle_transaction_v2_latency + .start_timer(); + self.metrics.tx_orders.inc(); + + // The should_accept_user_certs check here is best effort, because + // between a validator signs a tx and a cert is formed, the validator + // could close the window. + if !epoch_store + .get_reconfig_state_read_lock_guard() + .should_accept_user_certs() + { + return Err(SuiError::ValidatorHaltedAtEpochEnd); + } + + match self.handle_transaction_impl(transaction, epoch_store).await { + // TODO(fastpath): We don't actually need the signed transaction here but just call + // into this function to acquire locks. Consider refactoring to avoid the extra work. + Ok(_signed) => Ok(None), + // It happens frequently that while we are checking the validity of the transaction, it + // has just been executed. + // In that case, we could still return Ok to avoid showing confusing errors. + Err(e) => self + .get_transaction_status(&tx_digest, epoch_store)? + .ok_or(e) + .map(Some), + } + } + pub fn check_system_overload_at_signing(&self) -> bool { self.config .authority_overload_config @@ -1044,6 +1126,8 @@ impl AuthorityState { /// For such transaction, we don't have to wait for consensus to set shared object /// locks because we already know the shared object versions based on the effects. /// This function can be called by a fullnode only. + // TODO: This function is no longer needed. Remove it and cleanup all the + // related functions. #[instrument(level = "trace", skip_all)] pub async fn fullnode_execute_certificate_with_effects( &self, @@ -1133,6 +1217,7 @@ impl AuthorityState { self.metrics.total_cert_attempts.inc(); + // TODO(fastpath): use a separate function to check if a transaction should be executed in fastpath. if !certificate.contains_shared_object() { // Shared object transactions need to be sequenced by the consensus before enqueueing // for execution, done in AuthorityPerEpochStore::handle_consensus_transaction(). @@ -1140,7 +1225,21 @@ impl AuthorityState { self.enqueue_certificates_for_execution(vec![certificate.clone()], epoch_store); } - self.notify_read_effects(certificate).await + self.notify_read_effects(*certificate.digest()).await + } + + /// Awaits the effects of executing a user transaction. + /// + /// Relies on consensus to enqueue the transaction for execution. + pub async fn await_transaction_effects( + &self, + digest: TransactionDigest, + ) -> SuiResult { + let _metrics_guard = self.metrics.await_transaction_latency.start_timer(); + debug!("await_transaction"); + + // TODO(fastpath): Add handling for transactions rejected by Mysticeti fast path. + self.notify_read_effects(digest).await } /// Internal logic to execute a certificate. @@ -1232,10 +1331,10 @@ impl AuthorityState { pub async fn notify_read_effects( &self, - certificate: &VerifiedCertificate, + digest: TransactionDigest, ) -> SuiResult { self.get_transaction_cache_reader() - .notify_read_executed_effects(&[*certificate.digest()]) + .notify_read_executed_effects(&[digest]) .await .map(|mut r| r.pop().expect("must return correct number of effects")) } @@ -1705,6 +1804,8 @@ impl AuthorityState { self.prepare_certificate(&execution_guard, certificate, input_objects, epoch_store) } + #[instrument(skip_all)] + #[allow(clippy::type_complexity)] pub async fn dry_exec_transaction( &self, transaction: TransactionData, @@ -1919,8 +2020,138 @@ impl AuthorityState { )) } + pub fn simulate_transaction( + &self, + transaction: TransactionData, + ) -> SuiResult { + if transaction.kind().is_system_tx() { + return Err(SuiError::UnsupportedFeatureError { + error: "simulate does not support system transactions".to_string(), + }); + } + + let epoch_store = self.load_epoch_store_one_call_per_task(); + if !self.is_fullnode(&epoch_store) { + return Err(SuiError::UnsupportedFeatureError { + error: "simulate is only supported on fullnodes".to_string(), + }); + } + + self.simulate_transaction_impl(&epoch_store, transaction) + } + + fn simulate_transaction_impl( + &self, + epoch_store: &AuthorityPerEpochStore, + transaction: TransactionData, + ) -> SuiResult { + // Cheap validity checks for a transaction, including input size limits. + transaction.validity_check_no_gas_check(epoch_store.protocol_config())?; + + let input_object_kinds = transaction.input_objects()?; + let receiving_object_refs = transaction.receiving_objects(); + + sui_transaction_checks::deny::check_transaction_for_signing( + &transaction, + &[], + &input_object_kinds, + &receiving_object_refs, + &self.config.transaction_deny_config, + self.get_backing_package_store().as_ref(), + )?; + + let (input_objects, receiving_objects) = self.input_loader.read_objects_for_signing( + // We don't want to cache this transaction since it's a dry run. + None, + &input_object_kinds, + &receiving_object_refs, + epoch_store.epoch(), + )?; + + // make a gas object if one was not provided + let mut gas_object_refs = transaction.gas().to_vec(); + let ((gas_status, checked_input_objects), mock_gas) = if transaction.gas().is_empty() { + let sender = transaction.sender(); + // use a 1B sui coin + const MIST_TO_SUI: u64 = 1_000_000_000; + const DRY_RUN_SUI: u64 = 1_000_000_000; + let max_coin_value = MIST_TO_SUI * DRY_RUN_SUI; + let gas_object_id = ObjectID::MAX; + let gas_object = Object::new_move( + MoveObject::new_gas_coin(OBJECT_START_VERSION, gas_object_id, max_coin_value), + Owner::AddressOwner(sender), + TransactionDigest::genesis_marker(), + ); + let gas_object_ref = gas_object.compute_object_reference(); + gas_object_refs = vec![gas_object_ref]; + ( + sui_transaction_checks::check_transaction_input_with_given_gas( + epoch_store.protocol_config(), + epoch_store.reference_gas_price(), + &transaction, + input_objects, + receiving_objects, + gas_object, + &self.metrics.bytecode_verifier_metrics, + &self.config.verifier_signing_config, + )?, + Some(gas_object_id), + ) + } else { + ( + sui_transaction_checks::check_transaction_input( + epoch_store.protocol_config(), + epoch_store.reference_gas_price(), + &transaction, + input_objects, + &receiving_objects, + &self.metrics.bytecode_verifier_metrics, + &self.config.verifier_signing_config, + )?, + None, + ) + }; + + let protocol_config = epoch_store.protocol_config(); + let (kind, signer, _) = transaction.execution_parts(); + + let silent = true; + let executor = sui_execution::executor(protocol_config, silent, None) + .expect("Creating an executor should not fail here"); + + let expensive_checks = false; + let (inner_temp_store, _, effects, _execution_error) = executor + .execute_transaction_to_effects( + self.get_backing_store().as_ref(), + protocol_config, + self.metrics.limits_metrics.clone(), + expensive_checks, + self.config.certificate_deny_config.certificate_deny_set(), + &epoch_store.epoch_start_config().epoch_data().epoch_id(), + epoch_store + .epoch_start_config() + .epoch_data() + .epoch_start_timestamp(), + checked_input_objects, + gas_object_refs, + gas_status, + kind, + signer, + transaction.digest(), + ); + + Ok(SimulateTransactionResult { + input_objects: inner_temp_store.input_objects, + output_objects: inner_temp_store.written, + events: effects.events_digest().map(|_| inner_temp_store.events), + effects, + mock_gas_id: mock_gas, + }) + } + /// The object ID for gas can be any object ID, even for an uncreated object #[allow(clippy::collapsible_else_if)] + #[instrument(skip_all)] pub async fn dev_inspect_transaction_block( &self, sender: SuiAddress, @@ -2345,34 +2576,59 @@ impl AuthorityState { let Some(move_object) = o.data.try_as_move().cloned() else { return Ok(None); }; + // We only index dynamic field objects if !move_object.type_().is_dynamic_field() { return Ok(None); } - let layout = into_struct_layout( - resolver.get_annotated_layout(&move_object.type_().clone().into())?, - )?; - let move_struct = move_object.to_move_struct(&layout)?; + let layout = resolver + .get_annotated_layout(&move_object.type_().clone().into())? + .into_layout(); - let (name_value, type_, object_id) = - DynamicFieldInfo::parse_move_object(&move_struct).tap_err(|e| warn!("{e}"))?; + let field = + DFV::FieldVisitor::deserialize(move_object.contents(), &layout).map_err(|e| { + SuiError::ObjectDeserializationError { + error: e.to_string(), + } + })?; - let name_type = move_object.type_().try_extract_field_name(&type_)?; + let type_ = field.kind; + let name_type: TypeTag = field.name_layout.into(); + let bcs_name = field.name_bytes.to_owned(); - let bcs_name = bcs::to_bytes(&name_value.clone().undecorate()).map_err(|e| { - SuiError::ObjectSerializationError { - error: format!("{e}"), - } - })?; + let name_value = BoundedVisitor::deserialize_value(field.name_bytes, field.name_layout) + .map_err(|e| { + warn!("{e}"); + SuiError::ObjectDeserializationError { + error: e.to_string(), + } + })?; let name = DynamicFieldName { type_: name_type, value: SuiMoveValue::from(name_value).to_json_value(), }; - Ok(Some(match type_ { - DynamicFieldType::DynamicObject => { + let value_metadata = field.value_metadata().map_err(|e| { + warn!("{e}"); + SuiError::ObjectDeserializationError { + error: e.to_string(), + } + })?; + + Ok(Some(match value_metadata { + DFV::ValueMetadata::DynamicField(object_type) => DynamicFieldInfo { + name, + bcs_name, + type_, + object_type: object_type.to_canonical_string(/* with_prefix */ true), + object_id: o.id(), + version: o.version(), + digest: o.digest(), + }, + + DFV::ValueMetadata::DynamicObjectField(object_id) => { // Find the actual object from storage using the object id obtained from the wrapper. // Try to find the object in the written objects first. @@ -2395,6 +2651,7 @@ impl AuthorityState { let object_type = object.data.type_().unwrap().clone(); (version, digest, object_type) }; + DynamicFieldInfo { name, bcs_name, @@ -2405,15 +2662,6 @@ impl AuthorityState { digest, } } - DynamicFieldType::DynamicField { .. } => DynamicFieldInfo { - name, - bcs_name, - type_, - object_type: move_object.into_type().into_type_params()[1].to_string(), - object_id: o.id(), - version: o.version(), - digest: o.digest(), - }, })) } @@ -2946,6 +3194,14 @@ impl AuthorityState { } } + /// Acquires the execution lock for the duration of a transaction signing request. + /// This prevents reconfiguration from starting until we are finished handling the signing request. + /// Otherwise, in-memory lock state could be cleared (by `ObjectLocks::clear_cached_locks`) + /// while we are attempting to acquire locks for the transaction. + pub async fn execution_lock_for_signing(&self) -> ExecutionLockReadGuard { + self.execution_lock.read().await + } + pub async fn execution_lock_for_reconfiguration(&self) -> ExecutionLockWriteGuard { self.execution_lock.write().await } @@ -3470,10 +3726,10 @@ impl AuthorityState { &self, owner: SuiAddress, // If `Some`, the query will start from the next item after the specified cursor - cursor: (String, ObjectID), + cursor: (String, u64, ObjectID), limit: usize, one_coin_type_only: bool, - ) -> SuiResult + '_> { + ) -> SuiResult + '_> { if let Some(indexes) = &self.indexes { indexes.get_owned_coins_iterator_with_cursor(owner, cursor, limit, one_coin_type_only) } else { @@ -3897,18 +4153,7 @@ impl AuthorityState { let limit = limit + 1; let mut event_keys = match query { - EventFilter::All(filters) => { - if filters.is_empty() { - index_store.all_events(tx_num, event_num, limit, descending)? - } else { - return Err(SuiError::UserInputError { - error: UserInputError::Unsupported( - "This query type does not currently support filter combinations" - .to_string(), - ), - }); - } - } + EventFilter::All([]) => index_store.all_events(tx_num, event_num, limit, descending)?, EventFilter::Transaction(digest) => { index_store.events_by_transaction(&digest, tx_num, event_num, limit, descending)? } @@ -3941,14 +4186,10 @@ impl AuthorityState { descending, )?, // not using "_ =>" because we want to make sure we remember to add new variants here - EventFilter::Package(_) - | EventFilter::MoveEventField { .. } - | EventFilter::Any(_) - | EventFilter::And(_, _) - | EventFilter::Or(_, _) => { + EventFilter::Any(_) => { return Err(SuiError::UserInputError { error: UserInputError::Unsupported( - "This query type is not supported by the full node.".to_string(), + "'Any' queries are not supported by the fullnode.".to_string(), ), }) } @@ -5010,7 +5251,7 @@ impl AuthorityState { .protocol_config() .simplified_unwrap_then_delete(); self.get_accumulator_store() - .iter_live_object_set(include_wrapped_object) + .iter_cached_live_object_set_for_testing(include_wrapped_object) } #[cfg(test)] @@ -5029,6 +5270,8 @@ impl AuthorityState { .bulk_insert_genesis_objects(objects)?; self.get_object_cache_reader() .force_reload_system_packages(&BuiltInFramework::all_package_ids()); + self.get_reconfig_api() + .clear_state_end_of_epoch(&self.execution_lock_for_reconfiguration().await); Ok(()) } } @@ -5143,6 +5386,7 @@ impl RandomnessRoundReceiver { #[async_trait] impl TransactionKeyValueStoreTrait for AuthorityState { + #[instrument(skip(self))] async fn multi_get( &self, transactions: &[TransactionDigest], @@ -5180,6 +5424,7 @@ impl TransactionKeyValueStoreTrait for AuthorityState { Ok((txns, fx, evts)) } + #[instrument(skip(self))] async fn multi_get_checkpoints( &self, checkpoint_summaries: &[CheckpointSequenceNumber], @@ -5232,6 +5477,7 @@ impl TransactionKeyValueStoreTrait for AuthorityState { Ok((summaries, contents, summaries_by_digest, contents_by_digest)) } + #[instrument(skip(self))] async fn deprecated_get_transaction_checkpoint( &self, digest: TransactionDigest, @@ -5241,6 +5487,7 @@ impl TransactionKeyValueStoreTrait for AuthorityState { .map(|res| res.map(|(_epoch, checkpoint)| checkpoint)) } + #[instrument(skip(self))] async fn get_object( &self, object_id: ObjectID, @@ -5251,6 +5498,7 @@ impl TransactionKeyValueStoreTrait for AuthorityState { .map_err(Into::into) } + #[instrument(skip(self))] async fn multi_get_transaction_checkpoint( &self, digests: &[TransactionDigest], diff --git a/crates/sui-core/src/authority/authority_per_epoch_store.rs b/crates/sui-core/src/authority/authority_per_epoch_store.rs index 8e38a41242e84..70c1e98bbb2e1 100644 --- a/crates/sui-core/src/authority/authority_per_epoch_store.rs +++ b/crates/sui-core/src/authority/authority_per_epoch_store.rs @@ -162,12 +162,6 @@ pub enum ConsensusCertificateResult { ), } -#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq)] -pub struct ExecutionIndicesWithHash { - pub index: ExecutionIndices, - pub hash: u64, -} - /// ConsensusStats is versioned because we may iterate on the struct, and it is /// stored on disk. #[enum_dispatch] @@ -266,6 +260,7 @@ impl PartialOrd for ExecutionIndices { #[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq)] pub struct ExecutionIndicesWithStats { pub index: ExecutionIndices, + // Hash is always 0 and kept for compatibility only. pub hash: u64, pub stats: ConsensusStats, } @@ -321,6 +316,11 @@ pub struct AuthorityPerEpochStore { executed_digests_notify_read: NotifyRead, + /// Get notified when a synced checkpoint has reached CheckpointExecutor. + synced_checkpoint_notify_read: NotifyRead, + /// Caches the highest synced checkpoint sequence number as this has been notified from the CheckpointExecutor + highest_synced_checkpoint: RwLock, + /// This is used to notify all epoch specific tasks that epoch has ended. epoch_alive_notify: NotifyOnce, @@ -418,10 +418,13 @@ pub struct AuthorityEpochTables { /// versions from the transaction effect. Next object versions are not updated. /// /// REQUIRED: all authorities must assign the same shared object versions for each transaction. - assigned_shared_object_versions: DBMap>, assigned_shared_object_versions_v2: DBMap>, next_shared_object_versions: DBMap, + /// Deprecated table for pre-random-beacon shared object versions. + #[allow(dead_code)] + assigned_shared_object_versions: DBMap>, + /// Certificates that have been received from clients or received from consensus, but not yet /// executed. Entries are cleared after execution. /// This table is critical for crash recovery, because usually the consensus output progress @@ -452,11 +455,9 @@ pub struct AuthorityEpochTables { #[allow(dead_code)] consensus_message_order: DBMap, - /// The following table is used to store a single value (the corresponding key is a constant). The value - /// represents the index of the latest consensus message this authority processed. This field is written - /// by a single process acting as consensus (light) client. It is used to ensure the authority processes - /// every message output by consensus (and in the right order). - last_consensus_index: DBMap, + /// this table is not used + #[allow(dead_code)] + last_consensus_index: DBMap<(), ()>, /// The following table is used to store a single value (the corresponding key is a constant). The value /// represents the index of the latest consensus message this authority processed, running hash of @@ -487,10 +488,12 @@ pub struct AuthorityEpochTables { /// Because we don't want to create checkpoints with empty content(see CheckpointBuilder::write_checkpoint), /// the sequence number of checkpoint does not match height here. #[default_options_override_fn = "pending_checkpoints_table_default_config"] - pending_checkpoints: DBMap, - #[default_options_override_fn = "pending_checkpoints_table_default_config"] pending_checkpoints_v2: DBMap, + /// Deprecated table for pre-random-beacon checkpoints. + #[allow(dead_code)] + pending_checkpoints: DBMap, + /// Checkpoint builder maintains internal list of transactions it included in checkpoints here builder_digest_to_checkpoint: DBMap, @@ -680,8 +683,11 @@ impl AuthorityEpochTables { Ok(()) } - pub fn get_last_consensus_index(&self) -> SuiResult> { - Ok(self.last_consensus_index.get(&LAST_CONSENSUS_STATS_ADDR)?) + pub fn get_last_consensus_index(&self) -> SuiResult> { + Ok(self + .last_consensus_stats + .get(&LAST_CONSENSUS_STATS_ADDR)? + .map(|s| s.index)) } pub fn get_last_consensus_stats(&self) -> SuiResult> { @@ -875,6 +881,8 @@ impl AuthorityPerEpochStore { checkpoint_state_notify_read: NotifyRead::new(), running_root_notify_read: NotifyRead::new(), executed_digests_notify_read: NotifyRead::new(), + synced_checkpoint_notify_read: NotifyRead::new(), + highest_synced_checkpoint: RwLock::new(0), end_of_publish: Mutex::new(end_of_publish), pending_consensus_certificates: RwLock::new(pending_consensus_certificates), mutex_table: MutexTable::new(MUTEX_TABLE_SIZE), @@ -889,6 +897,19 @@ impl AuthorityPerEpochStore { randomness_manager: OnceCell::new(), randomness_reporter: OnceCell::new(), }); + + if matches!(chain_identifier.chain(), Chain::Mainnet | Chain::Testnet) { + // If we disable randomness, and if the release in which it was disabled did not have + // the commit that added this comment, we will need to revert this commit. This is + // because the previous release will have been writing to the deprecated + // assigned_shared_object_versions table. + // + // If we disable randomness *after* this commit has been shipped to all networks, then + // we can simply remove this assert, as we will no longer switch back and forth between + // the two tables. + assert!(s.randomness_state_enabled()); + } + s.update_buffer_stake_metric(); s } @@ -1350,13 +1371,6 @@ impl AuthorityPerEpochStore { .collect() } - pub fn get_last_consensus_index(&self) -> SuiResult { - self.tables()? - .get_last_consensus_index() - .map(|x| x.unwrap_or_default()) - .map_err(SuiError::from) - } - pub fn get_last_consensus_stats(&self) -> SuiResult { match self .tables()? @@ -1364,7 +1378,6 @@ impl AuthorityPerEpochStore { .map_err(SuiError::from)? { Some(stats) => Ok(stats), - // TODO: stop reading from last_consensus_index after rollout. None => { let indices = self .tables()? @@ -1372,8 +1385,8 @@ impl AuthorityPerEpochStore { .map(|x| x.unwrap_or_default()) .map_err(SuiError::from)?; Ok(ExecutionIndicesWithStats { - index: indices.index, - hash: indices.hash, + index: indices, + hash: 0, // unused stats: ConsensusStats::default(), }) } @@ -1458,6 +1471,15 @@ impl AuthorityPerEpochStore { // Now that the transaction effects are committed, we will never re-execute, so we // don't need to worry about equivocating. batch.delete_batch(&tables.signed_effects_digests, digests)?; + + // Note that this does not delete keys for random transactions. The worst case result + // of this is that we restart at the end of the epoch and load about 160k keys into + // memory. + batch.delete_batch( + &tables.assigned_shared_object_versions_v2, + digests.iter().map(|d| TransactionKey::Digest(*d)), + )?; + batch.write()?; Ok(()) } @@ -1482,15 +1504,9 @@ impl AuthorityPerEpochStore { tx_digest: &TransactionDigest, assigned_versions: &Vec<(ObjectID, SequenceNumber)>, ) -> SuiResult { - if self.randomness_state_enabled() { - self.tables()? - .assigned_shared_object_versions_v2 - .insert(&TransactionKey::Digest(*tx_digest), assigned_versions)?; - } else { - self.tables()? - .assigned_shared_object_versions - .insert(tx_digest, assigned_versions)?; - } + self.tables()? + .assigned_shared_object_versions_v2 + .insert(&TransactionKey::Digest(*tx_digest), assigned_versions)?; Ok(()) } @@ -1648,17 +1664,7 @@ impl AuthorityPerEpochStore { db_batch: &mut DBBatch, ) -> SuiResult { debug!("set_assigned_shared_object_versions: {:?}", versions); - - if self.randomness_state_enabled() { - db_batch.insert_batch(&self.tables()?.assigned_shared_object_versions_v2, versions)?; - } else { - db_batch.insert_batch( - &self.tables()?.assigned_shared_object_versions, - versions - .iter() - .map(|(key, versions)| (key.unwrap_digest(), versions)), - )?; - } + db_batch.insert_batch(&self.tables()?.assigned_shared_object_versions_v2, versions)?; Ok(()) } @@ -1673,7 +1679,7 @@ impl AuthorityPerEpochStore { cache_reader: &dyn ObjectCacheRead, certificates: &[VerifiedExecutableTransaction], ) -> SuiResult { - let mut db_batch = self.tables()?.assigned_shared_object_versions.batch(); + let mut db_batch = self.tables()?.assigned_shared_object_versions_v2.batch(); let assigned_versions = SharedObjVerManager::assign_versions_from_consensus( self, cache_reader, @@ -1860,7 +1866,7 @@ impl AuthorityPerEpochStore { cache_reader, ) .await?; - let mut db_batch = self.tables()?.assigned_shared_object_versions.batch(); + let mut db_batch = self.tables()?.assigned_shared_object_versions_v2.batch(); self.set_assigned_shared_object_versions_with_db_batch(versions, &mut db_batch) .await?; db_batch.write()?; @@ -1879,20 +1885,26 @@ impl AuthorityPerEpochStore { .pending_consensus_transactions .multi_insert(key_value_pairs)?; - // TODO: lock once for all insert() calls. - for transaction in transactions { - if let ConsensusTransactionKind::CertifiedTransaction(cert) = &transaction.kind { - let state = lock.expect("Must pass reconfiguration lock when storing certificate"); - // Caller is responsible for performing graceful check - assert!( - state.should_accept_user_certs(), - "Reconfiguration state should allow accepting user transactions" - ); - self.pending_consensus_certificates - .write() - .insert(*cert.digest()); - } + // UserTransaction exists only when mysticeti_fastpath is enabled in protocol config. + let digests: Vec<_> = transactions + .iter() + .filter_map(|tx| match &tx.kind { + ConsensusTransactionKind::CertifiedTransaction(cert) => Some(cert.digest()), + ConsensusTransactionKind::UserTransaction(txn) => Some(txn.digest()), + _ => None, + }) + .collect(); + if !digests.is_empty() { + let state = lock.expect("Must pass reconfiguration lock when storing certificate"); + // Caller is responsible for performing graceful check + assert!( + state.should_accept_user_certs(), + "Reconfiguration state should allow accepting user transactions" + ); + let mut pending_consensus_certificates = self.pending_consensus_certificates.write(); + pending_consensus_certificates.extend(digests); } + Ok(()) } @@ -1965,17 +1977,12 @@ impl AuthorityPerEpochStore { .any(|processed| processed)) } - /// Check whether any certificates were processed by consensus. - /// This handles multiple certificates at once. - pub fn is_all_tx_certs_consensus_message_processed<'a>( + /// Returns true if all messages with the given keys were processed by consensus. + pub fn all_external_consensus_messages_processed( &self, - certificates: impl Iterator, + keys: impl Iterator, ) -> SuiResult { - let keys = certificates.map(|cert| { - SequencedConsensusTransactionKey::External(ConsensusTransactionKey::Certificate( - *cert.digest(), - )) - }); + let keys = keys.map(SequencedConsensusTransactionKey::External); Ok(self .check_consensus_messages_processed(keys)? .into_iter() @@ -2037,6 +2044,33 @@ impl AuthorityPerEpochStore { Ok(()) } + /// Notifies that a synced checkpoint of sequence number `checkpoint_seq` is available. The source of the notification + /// is the CheckpointExecutor. The consumer here is guaranteed to be notified in sequence order. + pub fn notify_synced_checkpoint(&self, checkpoint_seq: CheckpointSequenceNumber) { + let mut highest_synced_checkpoint = self.highest_synced_checkpoint.write(); + *highest_synced_checkpoint = checkpoint_seq; + self.synced_checkpoint_notify_read + .notify(&checkpoint_seq, &()); + } + + /// Get notified when a synced checkpoint of sequence number `>= checkpoint_seq` is available. + pub async fn synced_checkpoint_notify( + &self, + checkpoint_seq: CheckpointSequenceNumber, + ) -> Result<(), SuiError> { + let registration = self + .synced_checkpoint_notify_read + .register_one(&checkpoint_seq); + { + let synced_checkpoint = self.highest_synced_checkpoint.read(); + if *synced_checkpoint >= checkpoint_seq { + return Ok(()); + } + } + registration.await; + Ok(()) + } + pub fn has_sent_end_of_publish(&self, authority: &AuthorityName) -> SuiResult { Ok(self .end_of_publish @@ -2578,7 +2612,7 @@ impl AuthorityPerEpochStore { } fn db_batch(&self) -> SuiResult { - Ok(self.tables()?.last_consensus_index.batch()) + Ok(self.tables()?.last_consensus_stats.batch()) } #[cfg(test)] @@ -2774,7 +2808,7 @@ impl AuthorityPerEpochStore { .collect(); let ( - transactions_to_schedule, + verified_transactions, notifications, lock, final_round, @@ -2796,10 +2830,7 @@ impl AuthorityPerEpochStore { authority_metrics, ) .await?; - self.finish_consensus_certificate_process_with_batch( - &mut output, - &transactions_to_schedule, - )?; + self.finish_consensus_certificate_process_with_batch(&mut output, &verified_transactions)?; output.record_consensus_commit_stats(consensus_stats.clone()); // Create pending checkpoints if we are still accepting tx. @@ -2907,7 +2938,7 @@ impl AuthorityPerEpochStore { self.record_end_of_message_quorum_time_metric(); } - Ok(transactions_to_schedule) + Ok(verified_transactions) } // Adds the consensus commit prologue transaction to the beginning of input `transactions` to update @@ -3004,25 +3035,14 @@ impl AuthorityPerEpochStore { #[cfg(any(test, feature = "test-utils"))] pub fn get_highest_pending_checkpoint_height(&self) -> CheckpointHeight { - if self.randomness_state_enabled() { - self.tables() - .expect("test should not cross epoch boundary") - .pending_checkpoints_v2 - .unbounded_iter() - .skip_to_last() - .next() - .map(|(key, _)| key) - .unwrap_or_default() - } else { - self.tables() - .expect("test should not cross epoch boundary") - .pending_checkpoints - .unbounded_iter() - .skip_to_last() - .next() - .map(|(key, _)| key) - .unwrap_or_default() - } + self.tables() + .expect("test should not cross epoch boundary") + .pending_checkpoints_v2 + .unbounded_iter() + .skip_to_last() + .next() + .map(|(key, _)| key) + .unwrap_or_default() } // Caller is not required to set ExecutionIndices with the right semantics in @@ -3136,10 +3156,14 @@ impl AuthorityPerEpochStore { // they will be in different checkpoints. let mut shared_object_congestion_tracker = SharedObjectCongestionTracker::new( self.protocol_config().per_object_congestion_control_mode(), + self.protocol_config() + .gas_budget_based_txn_cost_cap_factor_as_option(), ); let mut shared_object_using_randomness_congestion_tracker = SharedObjectCongestionTracker::new( self.protocol_config().per_object_congestion_control_mode(), + self.protocol_config() + .gas_budget_based_txn_cost_cap_factor_as_option(), ); fail_point_arg!( @@ -3699,7 +3723,7 @@ impl AuthorityPerEpochStore { kind: ConsensusTransactionKind::UserTransaction(_tx), .. }) => { - // TODO: implement handling of unsigned user transactions. + // TODO(fastpath): implement handling of user transactions from consensus commits. Ok(ConsensusCertificateResult::Ignored) } SequencedConsensusTransactionKind::System(system_transaction) => { @@ -3757,34 +3781,18 @@ impl AuthorityPerEpochStore { last: Option, ) -> SuiResult> { let tables = self.tables()?; - if self.randomness_state_enabled() { - let mut iter = tables.pending_checkpoints_v2.unbounded_iter(); - if let Some(last_processed_height) = last { - iter = iter.skip_to(&(last_processed_height + 1))?; - } - Ok(iter.collect()) - } else { - let mut iter = tables.pending_checkpoints.unbounded_iter(); - if let Some(last_processed_height) = last { - iter = iter.skip_to(&(last_processed_height + 1))?; - } - Ok(iter.map(|(height, cp)| (height, cp.into())).collect()) + let mut iter = tables.pending_checkpoints_v2.unbounded_iter(); + if let Some(last_processed_height) = last { + iter = iter.skip_to(&(last_processed_height + 1))?; } + Ok(iter.collect()) } pub fn get_pending_checkpoint( &self, index: &CheckpointHeight, ) -> SuiResult> { - if self.randomness_state_enabled() { - Ok(self.tables()?.pending_checkpoints_v2.get(index)?) - } else { - Ok(self - .tables()? - .pending_checkpoints - .get(index)? - .map(|c| c.into())) - } + Ok(self.tables()?.pending_checkpoints_v2.get(index)?) } pub fn process_pending_checkpoint( @@ -3792,10 +3800,11 @@ impl AuthorityPerEpochStore { commit_height: CheckpointHeight, content_info: Vec<(CheckpointSummary, CheckpointContents)>, ) -> SuiResult<()> { + let tables = self.tables()?; // All created checkpoints are inserted in builder_checkpoint_summary in a single batch. // This means that upon restart we can use BuilderCheckpointSummary::commit_height // from the last built summary to resume building checkpoints. - let mut batch = self.tables()?.pending_checkpoints.batch(); + let mut batch = tables.pending_checkpoints_v2.batch(); for (position_in_commit, (summary, transactions)) in content_info.into_iter().enumerate() { let sequence_number = summary.sequence_number; let summary = BuilderCheckpointSummary { @@ -3804,17 +3813,32 @@ impl AuthorityPerEpochStore { position_in_commit, }; batch.insert_batch( - &self.tables()?.builder_checkpoint_summary_v2, + &tables.builder_checkpoint_summary_v2, [(&sequence_number, summary)], )?; batch.insert_batch( - &self.tables()?.builder_digest_to_checkpoint, + &tables.builder_digest_to_checkpoint, transactions .iter() .map(|tx| (tx.transaction, sequence_number)), )?; + + batch.delete_batch( + &tables.user_signatures_for_checkpoints, + transactions.iter().map(|tx| tx.transaction), + )?; } + // find all pending checkpoints <= commit_height and remove them + let iter = tables + .pending_checkpoints_v2 + .safe_range_iter(0..=commit_height); + let keys = iter + .map(|c| c.map(|(h, _)| h)) + .collect::, _>>()?; + + batch.delete_batch(&tables.pending_checkpoints_v2, &keys)?; + Ok(batch.write()?) } @@ -4191,16 +4215,6 @@ impl ConsensusCommitOutput { } if let Some(consensus_commit_stats) = &self.consensus_commit_stats { - batch.insert_batch( - &tables.last_consensus_index, - [( - LAST_CONSENSUS_STATS_ADDR, - ExecutionIndicesWithHash { - index: consensus_commit_stats.index, - hash: consensus_commit_stats.hash, - }, - )], - )?; batch.insert_batch( &tables.last_consensus_stats, [(LAST_CONSENSUS_STATS_ADDR, consensus_commit_stats)], @@ -4215,19 +4229,10 @@ impl ConsensusCommitOutput { )?; if let Some((assigned_versions, next_versions)) = self.shared_object_versions { - if epoch_store.randomness_state_enabled() { - batch.insert_batch( - &tables.assigned_shared_object_versions_v2, - assigned_versions, - )?; - } else { - batch.insert_batch( - &tables.assigned_shared_object_versions, - assigned_versions - .into_iter() - .map(|(key, versions)| (*key.unwrap_digest(), versions)), - )?; - } + batch.insert_batch( + &tables.assigned_shared_object_versions_v2, + assigned_versions, + )?; batch.insert_batch(&tables.next_shared_object_versions, next_versions)?; } @@ -4240,21 +4245,12 @@ impl ConsensusCommitOutput { self.user_signatures_for_checkpoints, )?; - if epoch_store.randomness_state_enabled() { - batch.insert_batch( - &tables.pending_checkpoints_v2, - self.pending_checkpoints - .into_iter() - .map(|cp| (cp.height(), cp)), - )?; - } else { - batch.insert_batch( - &tables.pending_checkpoints, - self.pending_checkpoints - .into_iter() - .map(|cp| (cp.height(), cp.expect_v1())), - )?; - } + batch.insert_batch( + &tables.pending_checkpoints_v2, + self.pending_checkpoints + .into_iter() + .map(|cp| (cp.height(), cp)), + )?; if let Some((round, commit_timestamp)) = self.next_randomness_round { batch.insert_batch(&tables.randomness_next_round, [(SINGLETON_KEY, round)])?; @@ -4298,19 +4294,11 @@ impl GetSharedLocks for AuthorityPerEpochStore { &self, key: &TransactionKey, ) -> Result, SuiError> { - if self.randomness_state_enabled() { - Ok(self - .tables()? - .assigned_shared_object_versions_v2 - .get(key)? - .unwrap_or_default()) - } else { - Ok(self - .tables()? - .assigned_shared_object_versions - .get(key.unwrap_digest())? - .unwrap_or_default()) - } + Ok(self + .tables()? + .assigned_shared_object_versions_v2 + .get(key)? + .unwrap_or_default()) } } diff --git a/crates/sui-core/src/authority/authority_store.rs b/crates/sui-core/src/authority/authority_store.rs index a1ea916d1fd38..cb4dc555d975c 100644 --- a/crates/sui-core/src/authority/authority_store.rs +++ b/crates/sui-core/src/authority/authority_store.rs @@ -904,7 +904,7 @@ impl AuthorityStore { .iter() .map(|(id, new_object)| { let version = new_object.version(); - debug!(?id, ?version, "writing object"); + trace!(?id, ?version, "writing object"); let StoreObjectPair(store_object, indirect_object) = get_store_object_pair(new_object.clone(), self.indirect_objects_threshold); ( diff --git a/crates/sui-core/src/authority/authority_test_utils.rs b/crates/sui-core/src/authority/authority_test_utils.rs index 07d91da570ddd..a55c820334da5 100644 --- a/crates/sui-core/src/authority/authority_test_utils.rs +++ b/crates/sui-core/src/authority/authority_test_utils.rs @@ -377,7 +377,7 @@ pub async fn enqueue_all_and_execute_all( ); let mut output = Vec::new(); for cert in certificates { - let effects = authority.notify_read_effects(&cert).await?; + let effects = authority.notify_read_effects(*cert.digest()).await?; output.push(effects); } Ok(output) @@ -519,7 +519,7 @@ pub async fn publish_package_on_single_authority( dep_original_addresses: impl IntoIterator, dep_ids: Vec, state: &Arc, -) -> SuiResult<(ObjectID, ObjectRef)> { +) -> SuiResult<(TransactionDigest, (ObjectID, ObjectRef))> { let mut build_config = BuildConfig::new_for_testing(); for (addr_name, obj_id) in dep_original_addresses { build_config @@ -561,7 +561,7 @@ pub async fn publish_package_on_single_authority( .find(|c| matches!(c.1, Owner::AddressOwner(..))) .unwrap() .0; - Ok((package_id, cap_object)) + Ok((*effects.transaction_digest(), (package_id, cap_object))) } pub async fn upgrade_package_on_single_authority( @@ -574,7 +574,7 @@ pub async fn upgrade_package_on_single_authority( dep_original_addresses: impl IntoIterator, dep_id_mapping: impl IntoIterator, state: &Arc, -) -> SuiResult { +) -> SuiResult<(TransactionDigest, ObjectID)> { let package = build_test_modules_with_dep_addr(path, dep_original_addresses, dep_id_mapping); let with_unpublished_deps = false; @@ -606,5 +606,5 @@ pub async fn upgrade_package_on_single_authority( .unwrap() .0 .0; - Ok(package_id) + Ok((*effects.transaction_digest(), package_id)) } diff --git a/crates/sui-core/src/authority/shared_object_congestion_tracker.rs b/crates/sui-core/src/authority/shared_object_congestion_tracker.rs index 041e523594074..237d400114a43 100644 --- a/crates/sui-core/src/authority/shared_object_congestion_tracker.rs +++ b/crates/sui-core/src/authority/shared_object_congestion_tracker.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use sui_protocol_config::PerObjectCongestionControlMode; use sui_types::base_types::{ObjectID, TransactionDigest}; use sui_types::executable_transaction::VerifiedExecutableTransaction; -use sui_types::transaction::SharedInputObject; +use sui_types::transaction::{Argument, SharedInputObject, TransactionDataAPI}; // SharedObjectCongestionTracker stores the accumulated cost of executing transactions on an object, for // all transactions in a consensus commit. @@ -25,19 +25,25 @@ use sui_types::transaction::SharedInputObject; pub struct SharedObjectCongestionTracker { object_execution_cost: HashMap, mode: PerObjectCongestionControlMode, + gas_budget_based_txn_cost_cap_factor: Option, } impl SharedObjectCongestionTracker { - pub fn new(mode: PerObjectCongestionControlMode) -> Self { + pub fn new( + mode: PerObjectCongestionControlMode, + gas_budget_based_txn_cost_cap_factor: Option, + ) -> Self { Self { object_execution_cost: HashMap::new(), mode, + gas_budget_based_txn_cost_cap_factor, } } pub fn new_with_initial_value_for_test( init_values: &[(ObjectID, u64)], mode: PerObjectCongestionControlMode, + gas_budget_based_txn_cost_cap_factor: Option, ) -> Self { let mut object_execution_cost = HashMap::new(); for (object_id, total_cost) in init_values { @@ -46,6 +52,7 @@ impl SharedObjectCongestionTracker { Self { object_execution_cost, mode, + gas_budget_based_txn_cost_cap_factor, } } @@ -67,6 +74,9 @@ impl SharedObjectCongestionTracker { PerObjectCongestionControlMode::None => None, PerObjectCongestionControlMode::TotalGasBudget => Some(cert.gas_budget()), PerObjectCongestionControlMode::TotalTxCount => Some(1), + PerObjectCongestionControlMode::TotalGasBudgetWithCap => { + Some(std::cmp::min(cert.gas_budget(), self.get_tx_cost_cap(cert))) + } } } @@ -154,6 +164,25 @@ impl SharedObjectCongestionTracker { .copied() .unwrap_or(0) } + + fn get_tx_cost_cap(&self, cert: &VerifiedExecutableTransaction) -> u64 { + let mut number_of_move_call = 0; + let mut number_of_move_input = 0; + for command in cert.transaction_data().kind().iter_commands() { + if let sui_types::transaction::Command::MoveCall(move_call) = command { + number_of_move_call += 1; + for aug in move_call.arguments.iter() { + if let Argument::Input(_) = aug { + number_of_move_input += 1; + } + } + } + } + (number_of_move_call + number_of_move_input) as u64 + * self + .gas_budget_based_txn_cost_cap_factor + .expect("cap factor must be set if TotalGasBudgetWithCap mode is used.") + } } #[cfg(test)] @@ -164,7 +193,9 @@ mod object_cost_tests { use sui_test_transaction_builder::TestTransactionBuilder; use sui_types::base_types::{random_object_ref, SequenceNumber}; use sui_types::crypto::{get_key_pair, AccountKeyPair}; + use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder; use sui_types::transaction::{CallArg, ObjectArg, VerifiedTransaction}; + use sui_types::Identifier; fn construct_shared_input_objects(objects: &[(ObjectID, bool)]) -> Vec { objects @@ -187,6 +218,7 @@ mod object_cost_tests { SharedObjectCongestionTracker::new_with_initial_value_for_test( &[(object_id_0, 5), (object_id_1, 10)], PerObjectCongestionControlMode::TotalGasBudget, + None, ); let shared_input_objects = construct_shared_input_objects(&[(object_id_0, false)]); @@ -257,11 +289,56 @@ mod object_cost_tests { ) } + fn build_programmable_transaction( + objects: &[(ObjectID, bool)], + number_of_commands: u64, + gas_budget: u64, + ) -> VerifiedExecutableTransaction { + let (sender, keypair): (_, AccountKeyPair) = get_key_pair(); + let gas_object = random_object_ref(); + + let package_id = ObjectID::random(); + let mut pt_builder = ProgrammableTransactionBuilder::new(); + let mut arguments = Vec::new(); + for object in objects { + arguments.push( + pt_builder + .obj(ObjectArg::SharedObject { + id: object.0, + initial_shared_version: SequenceNumber::new(), + mutable: object.1, + }) + .unwrap(), + ); + } + for _ in 0..number_of_commands { + pt_builder.programmable_move_call( + package_id, + Identifier::new("unimportant_module").unwrap(), + Identifier::new("unimportant_function").unwrap(), + vec![], + arguments.clone(), + ); + } + + let pt = pt_builder.finish(); + VerifiedExecutableTransaction::new_system( + VerifiedTransaction::new_unchecked( + TestTransactionBuilder::new(sender, gas_object, 1000) + .with_gas_budget(gas_budget) + .programmable(pt) + .build_and_sign(&keypair), + ), + 0, + ) + } + #[rstest] fn test_should_defer_return_correct_congested_objects( #[values( PerObjectCongestionControlMode::TotalGasBudget, - PerObjectCongestionControlMode::TotalTxCount + PerObjectCongestionControlMode::TotalTxCount, + PerObjectCongestionControlMode::TotalGasBudgetWithCap )] mode: PerObjectCongestionControlMode, ) { @@ -276,6 +353,7 @@ mod object_cost_tests { PerObjectCongestionControlMode::None => unreachable!(), PerObjectCongestionControlMode::TotalGasBudget => tx_gas_budget + 1, PerObjectCongestionControlMode::TotalTxCount => 2, + PerObjectCongestionControlMode::TotalGasBudgetWithCap => tx_gas_budget - 1, }; let shared_object_congestion_tracker = match mode { @@ -288,6 +366,7 @@ mod object_cost_tests { SharedObjectCongestionTracker::new_with_initial_value_for_test( &[(shared_obj_0, 10), (shared_obj_1, 1)], mode, + None, ) } PerObjectCongestionControlMode::TotalTxCount => { @@ -298,6 +377,18 @@ mod object_cost_tests { SharedObjectCongestionTracker::new_with_initial_value_for_test( &[(shared_obj_0, 2), (shared_obj_1, 1)], mode, + None, + ) + } + PerObjectCongestionControlMode::TotalGasBudgetWithCap => { + // Construct object execution cost as following + // 1 10 + // object 0: | + // object 1: | + SharedObjectCongestionTracker::new_with_initial_value_for_test( + &[(shared_obj_0, 10), (shared_obj_1, 1)], + mode, + Some(45), // Make the cap just less than the gas budget, there are 1 objects in tx. ) } }; @@ -321,6 +412,8 @@ mod object_cost_tests { } // Read/write to object 1 should go through. + // When congestion control mode is TotalGasBudgetWithCap, even though the gas budget is over the limit, + // the cap should prevent the transaction from being deferred. for mutable in [true, false].iter() { let tx = build_transaction(&[(shared_obj_1, *mutable)], tx_gas_budget); assert!(shared_object_congestion_tracker @@ -361,7 +454,8 @@ mod object_cost_tests { fn test_should_defer_return_correct_deferral_key( #[values( PerObjectCongestionControlMode::TotalGasBudget, - PerObjectCongestionControlMode::TotalTxCount + PerObjectCongestionControlMode::TotalTxCount, + PerObjectCongestionControlMode::TotalGasBudgetWithCap )] mode: PerObjectCongestionControlMode, ) { @@ -369,7 +463,7 @@ mod object_cost_tests { let tx = build_transaction(&[(shared_obj_0, true)], 100); // Make should_defer_due_to_object_congestion always defer transactions. let max_accumulated_txn_cost_per_object_in_commit = 0; - let shared_object_congestion_tracker = SharedObjectCongestionTracker::new(mode); + let shared_object_congestion_tracker = SharedObjectCongestionTracker::new(mode, Some(2)); // Insert a random pre-existing transaction. let mut previously_deferred_tx_digests = HashMap::new(); @@ -460,7 +554,8 @@ mod object_cost_tests { fn test_bump_object_execution_cost( #[values( PerObjectCongestionControlMode::TotalGasBudget, - PerObjectCongestionControlMode::TotalTxCount + PerObjectCongestionControlMode::TotalTxCount, + PerObjectCongestionControlMode::TotalGasBudgetWithCap )] mode: PerObjectCongestionControlMode, ) { @@ -468,10 +563,13 @@ mod object_cost_tests { let object_id_1 = ObjectID::random(); let object_id_2 = ObjectID::random(); + let cap_factor = Some(1); + let mut shared_object_congestion_tracker = SharedObjectCongestionTracker::new_with_initial_value_for_test( &[(object_id_0, 5), (object_id_1, 10)], mode, + cap_factor, ); assert_eq!(shared_object_congestion_tracker.max_cost(), 10); @@ -482,7 +580,8 @@ mod object_cost_tests { shared_object_congestion_tracker, SharedObjectCongestionTracker::new_with_initial_value_for_test( &[(object_id_0, 5), (object_id_1, 10)], - mode + mode, + cap_factor, ) ); assert_eq!(shared_object_congestion_tracker.max_cost(), 10); @@ -494,12 +593,14 @@ mod object_cost_tests { PerObjectCongestionControlMode::None => unreachable!(), PerObjectCongestionControlMode::TotalGasBudget => 20, PerObjectCongestionControlMode::TotalTxCount => 11, + PerObjectCongestionControlMode::TotalGasBudgetWithCap => 13, // 2 objects, 1 command. }; assert_eq!( shared_object_congestion_tracker, SharedObjectCongestionTracker::new_with_initial_value_for_test( &[(object_id_0, expected_object_0_cost), (object_id_1, 10)], - mode + mode, + cap_factor, ) ); assert_eq!( @@ -520,6 +621,41 @@ mod object_cost_tests { PerObjectCongestionControlMode::None => unreachable!(), PerObjectCongestionControlMode::TotalGasBudget => 30, PerObjectCongestionControlMode::TotalTxCount => 12, + PerObjectCongestionControlMode::TotalGasBudgetWithCap => 17, // 3 objects, 1 command + }; + shared_object_congestion_tracker.bump_object_execution_cost(&cert); + assert_eq!( + shared_object_congestion_tracker, + SharedObjectCongestionTracker::new_with_initial_value_for_test( + &[ + (object_id_0, expected_object_cost), + (object_id_1, expected_object_cost), + (object_id_2, expected_object_cost) + ], + mode, + cap_factor, + ) + ); + assert_eq!( + shared_object_congestion_tracker.max_cost(), + expected_object_cost + ); + + // Write to all objects with PTBs containing 7 commands. + let cert = build_programmable_transaction( + &[ + (object_id_0, true), + (object_id_1, true), + (object_id_2, true), + ], + 7, + 30, + ); + let expected_object_cost = match mode { + PerObjectCongestionControlMode::None => unreachable!(), + PerObjectCongestionControlMode::TotalGasBudget => 60, + PerObjectCongestionControlMode::TotalTxCount => 13, + PerObjectCongestionControlMode::TotalGasBudgetWithCap => 45, // 3 objects, 7 commands }; shared_object_congestion_tracker.bump_object_execution_cost(&cert); assert_eq!( @@ -530,7 +666,8 @@ mod object_cost_tests { (object_id_1, expected_object_cost), (object_id_2, expected_object_cost) ], - mode + mode, + cap_factor, ) ); assert_eq!( diff --git a/crates/sui-core/src/authority/test_authority_builder.rs b/crates/sui-core/src/authority/test_authority_builder.rs index 6ba9dd32d4801..3427153f8395a 100644 --- a/crates/sui-core/src/authority/test_authority_builder.rs +++ b/crates/sui-core/src/authority/test_authority_builder.rs @@ -10,6 +10,7 @@ use crate::epoch::committee_store::CommitteeStore; use crate::epoch::epoch_metrics::EpochMetrics; use crate::epoch::randomness::RandomnessManager; use crate::execution_cache::build_execution_cache; +use crate::jsonrpc_index::IndexStore; use crate::mock_consensus::{ConsensusMode, MockConsensusClient}; use crate::module_cache_metrics::ResolverMetrics; use crate::rest_index::RestIndexStore; @@ -30,7 +31,6 @@ use sui_config::ExecutionCacheConfig; use sui_macros::nondeterministic; use sui_network::randomness; use sui_protocol_config::ProtocolConfig; -use sui_storage::IndexStore; use sui_swarm_config::genesis_config::AccountConfig; use sui_swarm_config::network_config::NetworkConfig; use sui_types::base_types::{AuthorityName, ObjectID}; @@ -270,6 +270,7 @@ impl<'a> TestAuthorityBuilder<'a> { .protocol_config() .max_move_identifier_len_as_option(), false, + &authority_store, ))) }; let rest_index = if self.disable_indexer { @@ -362,6 +363,12 @@ impl<'a> TestAuthorityBuilder<'a> { .await .unwrap(); + state + .get_cache_commit() + .commit_transaction_outputs(epoch_store.epoch(), &[*genesis.transaction().digest()]) + .await + .unwrap(); + // We want to insert these objects directly instead of relying on genesis because // genesis process would set the previous transaction field for these objects, which would // change their object digest. This makes it difficult to write tests that want to use diff --git a/crates/sui-core/src/authority_server.rs b/crates/sui-core/src/authority_server.rs index abe16af083743..a5ece01381f9b 100644 --- a/crates/sui-core/src/authority_server.rs +++ b/crates/sui-core/src/authority_server.rs @@ -19,9 +19,9 @@ use sui_network::{ api::{Validator, ValidatorServer}, tonic, }; -use sui_types::effects::TransactionEffectsAPI; -use sui_types::messages_consensus::ConsensusTransaction; -use sui_types::messages_grpc::{HandleCertificateRequestV3, HandleCertificateResponseV3}; +use sui_types::messages_grpc::{ + HandleCertificateRequestV3, HandleCertificateResponseV3, HandleTransactionResponseV2, +}; use sui_types::messages_grpc::{ HandleCertificateResponseV2, HandleTransactionResponse, ObjectInfoRequest, ObjectInfoResponse, SubmitCertificateResponse, SystemStateRequest, TransactionInfoRequest, TransactionInfoResponse, @@ -32,6 +32,7 @@ use sui_types::messages_grpc::{ use sui_types::multiaddr::Multiaddr; use sui_types::sui_system_state::SuiSystemState; use sui_types::traffic_control::{ClientIdSource, PolicyConfig, RemoteFirewallConfig, Weight}; +use sui_types::{effects::TransactionEffectsAPI, messages_grpc::HandleTransactionRequestV2}; use sui_types::{error::*, transaction::*}; use sui_types::{ fp_ensure, @@ -39,6 +40,10 @@ use sui_types::{ CheckpointRequest, CheckpointRequestV2, CheckpointResponse, CheckpointResponseV2, }, }; +use sui_types::{ + messages_consensus::{ConsensusTransaction, ConsensusTransactionKind}, + messages_grpc::TransactionStatus, +}; use tap::TapFallible; use tokio::task::JoinHandle; use tonic::metadata::{Ascii, MetadataValue}; @@ -51,6 +56,7 @@ use crate::{ use crate::{ authority::AuthorityState, consensus_adapter::{ConsensusAdapter, ConsensusAdapterMetrics}, + traffic_controller::parse_ip, traffic_controller::policies::TrafficTally, traffic_controller::TrafficController, }; @@ -170,10 +176,12 @@ pub struct ValidatorServiceMetrics { pub cert_verification_latency: Histogram, pub consensus_latency: Histogram, pub handle_transaction_latency: Histogram, + pub handle_transaction_v2_latency: Histogram, pub submit_certificate_consensus_latency: Histogram, pub handle_certificate_consensus_latency: Histogram, pub handle_certificate_non_consensus_latency: Histogram, pub handle_soft_bundle_certificates_consensus_latency: Histogram, + pub handle_transaction_consensus_latency: Histogram, num_rejected_tx_in_epoch_boundary: IntCounter, num_rejected_cert_in_epoch_boundary: IntCounter, @@ -222,6 +230,13 @@ impl ValidatorServiceMetrics { registry, ) .unwrap(), + handle_transaction_v2_latency: register_histogram_with_registry!( + "validator_service_handle_transaction_v2_latency", + "Latency of v2 transaction handler", + mysten_metrics::SUBSECOND_LATENCY_SEC_BUCKETS.to_vec(), + registry, + ) + .unwrap(), handle_certificate_consensus_latency: register_histogram_with_registry!( "validator_service_handle_certificate_consensus_latency", "Latency of handling a consensus transaction certificate", @@ -250,6 +265,13 @@ impl ValidatorServiceMetrics { registry, ) .unwrap(), + handle_transaction_consensus_latency: register_histogram_with_registry!( + "validator_service_handle_transaction_consensus_latency", + "Latency of handling a user transaction sent through consensus", + mysten_metrics::COARSE_LATENCY_SEC_BUCKETS.to_vec(), + registry, + ) + .unwrap(), num_rejected_tx_in_epoch_boundary: register_int_counter_with_registry!( "validator_service_num_rejected_tx_in_epoch_boundary", "Number of rejected transaction during epoch transitioning", @@ -332,7 +354,7 @@ impl ValidatorService { consensus_adapter, metrics: validator_metrics, traffic_controller: policy_config.clone().map(|policy| { - Arc::new(TrafficController::spawn( + Arc::new(TrafficController::init( policy, traffic_controller_metrics, firewall_config, @@ -451,6 +473,127 @@ impl ValidatorService { Ok((tonic::Response::new(info), Weight::zero())) } + async fn handle_transaction_v2( + &self, + request: tonic::Request, + ) -> WrappedServiceResponse { + let Self { + state, + consensus_adapter, + metrics, + traffic_controller: _, + client_id_source: _, + } = self.clone(); + let epoch_store = state.load_epoch_store_one_call_per_task(); + if !epoch_store.protocol_config().mysticeti_fastpath() { + return Err(SuiError::UnsupportedFeatureError { + error: "Mysticeti fastpath".to_string(), + } + .into()); + } + + let HandleTransactionRequestV2 { + transaction, + include_events, + include_input_objects, + include_output_objects, + include_auxiliary_data, + } = request.into_inner(); + + transaction.validity_check(epoch_store.protocol_config(), epoch_store.epoch())?; + + // Check system overload + let overload_check_res = self.state.check_system_overload( + &consensus_adapter, + transaction.data(), + state.check_system_overload_at_signing(), + ); + if let Err(error) = overload_check_res { + metrics + .num_rejected_tx_during_overload + .with_label_values(&[error.as_ref()]) + .inc(); + return Err(error.into()); + } + + let _handle_tx_metrics_guard = metrics.handle_transaction_v2_latency.start_timer(); + + let tx_verif_metrics_guard = metrics.tx_verification_latency.start_timer(); + let transaction = epoch_store.verify_transaction(transaction).tap_err(|_| { + metrics.signature_errors.inc(); + })?; + drop(tx_verif_metrics_guard); + + // Enable Trace Propagation across spans/processes using tx_digest + let tx_digest = transaction.digest(); + let span = error_span!("validator_state_process_tx_v2", ?tx_digest); + + let tx_status = state + .handle_transaction_v2(&epoch_store, transaction.clone()) + .instrument(span) + .await + .tap_err(|e| { + if let SuiError::ValidatorHaltedAtEpochEnd = e { + metrics.num_rejected_tx_in_epoch_boundary.inc(); + } + })?; + if let Some(( + _sender_signed_data, + // TODO(fastpath): Suppress duplicate transaction submission in consensus + // adapter, if not already done. (If we get back `TransactionStatus::Signed`` + // here we still need to proceed with submission logic, because the previous + // RPC might have been dropped after signing but before submission.) + TransactionStatus::Executed(_sign_info, signed_effects, events), + )) = tx_status + { + let input_objects = include_input_objects + .then(|| state.get_transaction_input_objects(signed_effects.data())) + .and_then(Result::ok); + let output_objects = include_output_objects + .then(|| state.get_transaction_output_objects(signed_effects.data())) + .and_then(Result::ok); + + return Ok(( + tonic::Response::new(HandleTransactionResponseV2 { + effects: signed_effects.into_data(), + events: include_events.then_some(events), + input_objects, + output_objects, + auxiliary_data: None, // We don't have any aux data generated presently + }), + Weight::zero(), + )); + } + + let _latency_metric_guard = metrics.handle_transaction_consensus_latency.start_timer(); + let span = error_span!("handle_transaction_v2", tx_digest = ?transaction.digest()); + self.handle_submit_to_consensus( + nonempty![ConsensusTransaction::new_user_transaction_message( + &self.state.name, + transaction.into() + )], + include_events, + include_input_objects, + include_output_objects, + include_auxiliary_data, + &epoch_store, + true, + ) + .instrument(span) + .await + .map(|(resp, spam_weight)| { + ( + tonic::Response::new( + resp.expect( + "handle_submit_to_consensus should not return none with wait_for_effects=true", + ) + .remove(0), + ), + spam_weight, + ) + }) + } + // In addition to the response from handling the certificates, // returns a bool indicating whether the request should be tallied // toward spam count. In general, this should be set to true for @@ -462,7 +605,7 @@ impl ValidatorService { include_events: bool, include_input_objects: bool, include_output_objects: bool, - _include_auxiliary_data: bool, + include_auxiliary_data: bool, epoch_store: &Arc, wait_for_effects: bool, ) -> Result<(Option>, Weight), tonic::Status> { @@ -555,7 +698,62 @@ impl ValidatorService { .into_iter() .collect::, _>>()? }; + let consensus_transactions = + NonEmpty::collect(verified_certificates.iter().map(|certificate| { + ConsensusTransaction::new_certificate_message( + &self.state.name, + certificate.clone().into(), + ) + })) + .unwrap(); + + let (responses, weight) = self + .handle_submit_to_consensus( + consensus_transactions, + include_events, + include_input_objects, + include_output_objects, + include_auxiliary_data, + epoch_store, + wait_for_effects, + ) + .await?; + // Sign the returned TransactionEffects. + let responses = if let Some(responses) = responses { + Some( + responses + .into_iter() + .map(|response| { + let signed_effects = + self.state.sign_effects(response.effects, epoch_store)?; + Ok(HandleCertificateResponseV3 { + effects: signed_effects.into_inner(), + events: response.events, + input_objects: response.input_objects, + output_objects: response.output_objects, + auxiliary_data: response.auxiliary_data, + }) + }) + .collect::, tonic::Status>>()?, + ) + } else { + None + }; + + Ok((responses, weight)) + } + async fn handle_submit_to_consensus( + &self, + consensus_transactions: NonEmpty, + include_events: bool, + include_input_objects: bool, + include_output_objects: bool, + _include_auxiliary_data: bool, + epoch_store: &Arc, + wait_for_effects: bool, + ) -> Result<(Option>, Weight), tonic::Status> { + let consensus_transactions: Vec<_> = consensus_transactions.into(); { // code block within reconfiguration lock let reconfiguration_lock = epoch_store.get_reconfig_state_read_lock_guard(); @@ -564,29 +762,27 @@ impl ValidatorService { return Err(SuiError::ValidatorHaltedAtEpochEnd.into()); } - // 3) All certificates are sent to consensus (at least by some authorities) - // For shared objects this will wait until either timeout or we have heard back from consensus. - // For owned objects this will return without waiting for certificate to be sequenced + // 3) All transactions are sent to consensus (at least by some authorities) + // For certs with shared objects this will wait until either timeout or we have heard back from consensus. + // For certs with owned objects this will return without waiting for certificate to be sequenced. + // For uncertified transactions this will wait for fast path processing. // First do quick dirty non-async check. - if !epoch_store - .is_all_tx_certs_consensus_message_processed(verified_certificates.iter())? - { - let _metrics_guard = if shared_object_tx { + if !epoch_store.all_external_consensus_messages_processed( + consensus_transactions.iter().map(|tx| tx.key()), + )? { + let _metrics_guard = if consensus_transactions.iter().any(|tx| match &tx.kind { + ConsensusTransactionKind::CertifiedTransaction(tx) => { + tx.contains_shared_object() + } + ConsensusTransactionKind::UserTransaction(_) => true, + _ => false, + }) { Some(self.metrics.consensus_latency.start_timer()) } else { None }; - let transactions = verified_certificates - .iter() - .map(|certificate| { - ConsensusTransaction::new_certificate_message( - &self.state.name, - certificate.clone().into(), - ) - }) - .collect::>(); self.consensus_adapter.submit_batch( - &transactions, + &consensus_transactions, Some(&reconfiguration_lock), epoch_store, )?; @@ -598,10 +794,17 @@ impl ValidatorService { if !wait_for_effects { // It is useful to enqueue owned object transaction for execution locally, // even when we are not returning effects to user - let certificates_without_shared_objects = verified_certificates + let certificates_without_shared_objects = consensus_transactions .iter() - .filter(|certificate| !certificate.contains_shared_object()) - .cloned() + .filter_map(|tx| { + if let ConsensusTransactionKind::CertifiedTransaction(certificate) = &tx.kind { + (!certificate.contains_shared_object()) + // Certificates already verified by callers of this function. + .then_some(VerifiedCertificate::new_unchecked(*(certificate.clone()))) + } else { + None + } + }) .collect::>(); if !certificates_without_shared_objects.is_empty() { self.state.enqueue_certificates_for_execution( @@ -614,12 +817,21 @@ impl ValidatorService { // 4) Execute the certificates immediately if they contain only owned object transactions, // or wait for the execution results if it contains shared objects. - let responses = futures::future::try_join_all(verified_certificates.into_iter().map( - |certificate| async move { - let effects = self - .state - .execute_certificate(&certificate, epoch_store) - .await?; + let responses = futures::future::try_join_all(consensus_transactions.into_iter().map( + |tx| async move { + let effects = match &tx.kind { + ConsensusTransactionKind::CertifiedTransaction(certificate) => { + // Certificates already verified by callers of this function. + let certificate = VerifiedCertificate::new_unchecked(*(certificate.clone())); + self.state + .execute_certificate(&certificate, epoch_store) + .await? + } + ConsensusTransactionKind::UserTransaction(tx) => { + self.state.await_transaction_effects(*tx.digest()).await? + } + _ => panic!("`handle_submit_to_consensus` received transaction that is not a CertifiedTransaction or UserTransaction"), + }; let events = if include_events { if let Some(digest) = effects.events_digest() { Some(self.state.get_transaction_events(digest)?) @@ -638,11 +850,13 @@ impl ValidatorService { .then(|| self.state.get_transaction_output_objects(&effects)) .and_then(Result::ok); - let signed_effects = self.state.sign_effects(effects, epoch_store)?; - epoch_store.insert_tx_cert_sig(certificate.digest(), certificate.auth_sig())?; + if let ConsensusTransactionKind::CertifiedTransaction(certificate) = &tx.kind { + epoch_store.insert_tx_cert_sig(certificate.digest(), certificate.auth_sig())?; + // TODO(fastpath): Make sure consensus handler does this for a UserTransaction. + } - Ok::<_, SuiError>(HandleCertificateResponseV3 { - effects: signed_effects.into_inner(), + Ok::<_, SuiError>(HandleTransactionResponseV2 { + effects, events, input_objects, output_objects, @@ -666,6 +880,13 @@ impl ValidatorService { self.handle_transaction(request).await } + async fn transaction_v2_impl( + &self, + request: tonic::Request, + ) -> WrappedServiceResponse { + self.handle_transaction_v2(request).await + } + async fn submit_certificate_impl( &self, request: tonic::Request, @@ -1009,17 +1230,9 @@ impl ValidatorService { ); return None; }; - client_ip.parse::().ok().or_else(|| { - client_ip.parse::().ok().map(|socket_addr| socket_addr.ip()).or_else(|| { - self.metrics.forwarded_header_parse_error.inc(); - error!( - "Failed to parse x-forwarded-for header value of {:?} to ip address or socket. \ - Please ensure that your proxy is configured to resolve client domains to an \ - IP address before writing header", - client_ip, - ); - None - }) + parse_ip(client_ip).or_else(|| { + self.metrics.forwarded_header_parse_error.inc(); + None }) } Err(e) => { @@ -1151,6 +1364,23 @@ impl Validator for ValidatorService { .unwrap() } + async fn transaction_v2( + &self, + request: tonic::Request, + ) -> Result, tonic::Status> { + let validator_service = self.clone(); + + // Spawns a task which handles the transaction. The task will unconditionally continue + // processing in the event that the client connection is dropped. + spawn_monitored_task!(async move { + // NB: traffic tally wrapping handled within the task rather than on task exit + // to prevent an attacker from subverting traffic control by severing the connection + handle_with_decoration!(validator_service, transaction_v2_impl, request) + }) + .await + .unwrap() + } + async fn submit_certificate( &self, request: tonic::Request, diff --git a/crates/sui-core/src/checkpoints/checkpoint_executor/mod.rs b/crates/sui-core/src/checkpoints/checkpoint_executor/mod.rs index 92c8dde74ff9e..f04ea86367464 100644 --- a/crates/sui-core/src/checkpoints/checkpoint_executor/mod.rs +++ b/crates/sui-core/src/checkpoints/checkpoint_executor/mod.rs @@ -532,6 +532,8 @@ impl CheckpointExecutor { let accumulator = self.accumulator.clone(); let state = self.state.clone(); + epoch_store.notify_synced_checkpoint(*checkpoint.sequence_number()); + pending.push_back(spawn_monitored_task!(async move { let epoch_store = epoch_store.clone(); let (tx_digests, checkpoint_acc) = loop { diff --git a/crates/sui-core/src/checkpoints/mod.rs b/crates/sui-core/src/checkpoints/mod.rs index 9860ed36a25db..d9aed27ff48f3 100644 --- a/crates/sui-core/src/checkpoints/mod.rs +++ b/crates/sui-core/src/checkpoints/mod.rs @@ -2264,7 +2264,8 @@ impl CheckpointService { max_checkpoint_size_bytes, ); - spawn_monitored_task!(builder.run()); + let epoch_store_clone = epoch_store.clone(); + spawn_monitored_task!(epoch_store_clone.within_alive_epoch(builder.run())); let aggregator = CheckpointAggregator::new( checkpoint_store.clone(), diff --git a/crates/sui-core/src/consensus_adapter.rs b/crates/sui-core/src/consensus_adapter.rs index 20ff98da8180d..22b94b87f1f9a 100644 --- a/crates/sui-core/src/consensus_adapter.rs +++ b/crates/sui-core/src/consensus_adapter.rs @@ -2,15 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 use arc_swap::{ArcSwap, ArcSwapOption}; -use bytes::Bytes; use dashmap::try_result::TryResult; use dashmap::DashMap; -use futures::future::{select, Either}; +use futures::future::{self, select, Either}; use futures::stream::FuturesUnordered; use futures::FutureExt; use futures::{pin_mut, StreamExt}; use itertools::Itertools; -use narwhal_types::{TransactionProto, TransactionsClient}; use parking_lot::RwLockReadGuard; use prometheus::Histogram; use prometheus::HistogramVec; @@ -34,7 +32,6 @@ use sui_types::base_types::TransactionDigest; use sui_types::committee::Committee; use sui_types::error::{SuiError, SuiResult}; -use tap::prelude::*; use tokio::sync::{Semaphore, SemaphorePermit}; use tokio::task::JoinHandle; use tokio::time::{self}; @@ -44,10 +41,10 @@ use crate::consensus_handler::{classify, SequencedConsensusTransactionKey}; use crate::consensus_throughput_calculator::{ConsensusThroughputProfiler, Level}; use crate::epoch::reconfiguration::{ReconfigState, ReconfigurationInitiator}; use crate::metrics::LatencyObserver; +use consensus_core::ConnectionStatus; use mysten_metrics::{spawn_monitored_task, GaugeGuard, GaugeGuardFutureExt}; use sui_protocol_config::ProtocolConfig; use sui_simulator::anemo::PeerId; -use sui_simulator::narwhal_network::connectivity::ConnectionStatus; use sui_types::base_types::AuthorityName; use sui_types::fp_ensure; use sui_types::messages_consensus::ConsensusTransactionKind; @@ -127,7 +124,7 @@ impl ConsensusAdapterMetrics { sequencing_certificate_latency: register_histogram_vec_with_registry!( "sequencing_certificate_latency", "The latency for sequencing a certificate.", - &["position", "tx_type"], + &["position", "tx_type", "processed_method"], SEQUENCING_CERTIFICATE_LATENCY_SEC_BUCKETS.to_vec(), registry, ).unwrap(), @@ -196,36 +193,6 @@ pub trait SubmitToConsensus: Sync + Send + 'static { epoch_store: &Arc, ) -> SuiResult; } - -#[async_trait::async_trait] -impl SubmitToConsensus for TransactionsClient { - async fn submit_to_consensus( - &self, - transactions: &[ConsensusTransaction], - _epoch_store: &Arc, - ) -> SuiResult { - let transactions_bytes = transactions - .iter() - .map(|t| { - let serialized = - bcs::to_bytes(t).expect("Serializing consensus transaction cannot fail"); - Bytes::from(serialized) - }) - .collect::>(); - self.clone() - .submit_transaction(TransactionProto { - transactions: transactions_bytes, - }) - .await - .map_err(|e| SuiError::ConsensusConnectionBroken(format!("{:?}", e))) - .tap_err(|r| { - // Will be logged by caller as well. - warn!("Submit transaction failed with: {:?}", r); - })?; - Ok(()) - } -} - /// Submit Sui certificates to the consensus. pub struct ConsensusAdapter { /// The network client connecting to the consensus node of this authority. @@ -361,6 +328,12 @@ impl ConsensusAdapter { committee: &Committee, transactions: &[ConsensusTransaction], ) -> (impl Future, usize, usize, usize) { + if transactions.iter().any(|tx| tx.is_user_transaction()) { + // UserTransactions are generally sent to just one validator and should + // be submitted to consensus without delay. + return (tokio::time::sleep(Duration::ZERO), 0, 0, 0); + } + // Use the minimum digest to compute submit delay. let min_digest = transactions .iter() @@ -580,8 +553,8 @@ impl ConsensusAdapter { epoch_store: &Arc, ) -> SuiResult> { if transactions.len() > 1 { - // In soft bundle, we need to check if all transactions are of UserTransaction kind. - // The check is required because we assume this in submit_and_wait_inner. + // In soft bundle, we need to check if all transactions are of CertifiedTransaction + // kind. The check is required because we assume this in submit_and_wait_inner. for transaction in transactions { fp_ensure!( matches!( @@ -811,13 +784,14 @@ impl ConsensusAdapter { .with_label_values(&[&bucket, tx_type]) .observe(ack_start.elapsed().as_secs_f64()); }; - match select(processed_waiter, submit_inner.boxed()).await { - Either::Left((processed, _submit_inner)) => processed, + + guard.processed_method = match select(processed_waiter, submit_inner.boxed()).await { + Either::Left((observed_via_consensus, _submit_inner)) => observed_via_consensus, Either::Right(((), processed_waiter)) => { debug!("Submitted {transaction_keys:?} to consensus"); processed_waiter.await } - } + }; } debug!("{transaction_keys:?} processed by consensus"); @@ -870,11 +844,12 @@ impl ConsensusAdapter { } /// Waits for transactions to appear either to consensus output or been executed via a checkpoint (state sync). + /// Returns the processed method, whether the transactions have been processed via consensus, or have been synced via checkpoint. async fn await_consensus_or_checkpoint( self: &Arc, transaction_keys: Vec, epoch_store: &Arc, - ) { + ) -> ProcessedMethod { let notifications = FuturesUnordered::new(); for transaction_key in transaction_keys { let transaction_digests = if let SequencedConsensusTransactionKey::External( @@ -886,6 +861,17 @@ impl ConsensusAdapter { vec![] }; + let checkpoint_synced_future = if let SequencedConsensusTransactionKey::External( + ConsensusTransactionKey::CheckpointSignature(_, checkpoint_sequence_number), + ) = transaction_key + { + // If the transaction is a checkpoint signature, we can also wait to get notified when a checkpoint with equal or higher sequence + // number has been already synced. This way we don't try to unnecessarily sequence the signature for an already verified checkpoint. + Either::Left(epoch_store.synced_checkpoint_notify(checkpoint_sequence_number)) + } else { + Either::Right(future::pending()) + }; + // We wait for each transaction individually to be processed by consensus or executed in a checkpoint. We could equally just // get notified in aggregate when all transactions are processed, but with this approach can get notified in a more fine-grained way // as transactions can be marked as processed in different ways. This is mostly a concern for the soft-bundle transactions. @@ -894,16 +880,28 @@ impl ConsensusAdapter { processed = epoch_store.consensus_messages_processed_notify(vec![transaction_key]) => { processed.expect("Storage error when waiting for consensus message processed"); self.metrics.sequencing_certificate_processed.with_label_values(&["consensus"]).inc(); + return ProcessedMethod::Consensus; }, processed = epoch_store.transactions_executed_in_checkpoint_notify(transaction_digests), if !transaction_digests.is_empty() => { processed.expect("Storage error when waiting for transaction executed in checkpoint"); self.metrics.sequencing_certificate_processed.with_label_values(&["checkpoint"]).inc(); } + processed = checkpoint_synced_future => { + processed.expect("Error when waiting for checkpoint sequence number"); + self.metrics.sequencing_certificate_processed.with_label_values(&["synced_checkpoint"]).inc(); + } } + ProcessedMethod::Checkpoint }); } - notifications.collect::>().await; + let processed_methods = notifications.collect::>().await; + for method in processed_methods { + if method == ProcessedMethod::Checkpoint { + return ProcessedMethod::Checkpoint; + } + } + ProcessedMethod::Consensus } } @@ -1029,6 +1027,13 @@ struct InflightDropGuard<'a> { positions_moved: Option, preceding_disconnected: Option, tx_type: &'static str, + processed_method: ProcessedMethod, +} + +#[derive(PartialEq, Eq)] +enum ProcessedMethod { + Consensus, + Checkpoint, } impl<'a> InflightDropGuard<'a> { @@ -1053,6 +1058,7 @@ impl<'a> InflightDropGuard<'a> { positions_moved: None, preceding_disconnected: None, tx_type, + processed_method: ProcessedMethod::Consensus, } } } @@ -1093,10 +1099,14 @@ impl<'a> Drop for InflightDropGuard<'a> { }; let latency = self.start.elapsed(); + let processed_method = match self.processed_method { + ProcessedMethod::Consensus => "processed_via_consensus", + ProcessedMethod::Checkpoint => "processed_via_checkpoint", + }; self.adapter .metrics .sequencing_certificate_latency - .with_label_values(&[&position, self.tx_type]) + .with_label_values(&[&position, self.tx_type, processed_method]) .observe(latency.as_secs_f64()); // Only sample latency after consensus quorum is up. Otherwise, the wait for consensus @@ -1110,7 +1120,8 @@ impl<'a> Drop for InflightDropGuard<'a> { self.tx_type, "shared_certificate" | "owned_certificate" | "checkpoint_signature" | "soft_bundle" ); - if sampled { + // if tx has been processed by checkpoint state sync, then exclude from the latency calculations as this can introduce to misleading results. + if sampled && self.processed_method == ProcessedMethod::Consensus { self.adapter.latency_observer.report(latency); } } diff --git a/crates/sui-core/src/consensus_handler.rs b/crates/sui-core/src/consensus_handler.rs index d2fc91649fbbc..d00b8273f1d02 100644 --- a/crates/sui-core/src/consensus_handler.rs +++ b/crates/sui-core/src/consensus_handler.rs @@ -2,17 +2,21 @@ // SPDX-License-Identifier: Apache-2.0 use std::{ - collections::{hash_map::DefaultHasher, HashMap, HashSet}, - hash::{Hash, Hasher}, + collections::{HashMap, HashSet}, + hash::Hash, num::NonZeroUsize, sync::Arc, }; use arc_swap::ArcSwap; use consensus_config::Committee as ConsensusCommittee; -use consensus_core::CommitConsumerMonitor; +use consensus_core::{CommitConsumerMonitor, TransactionIndex, VerifiedBlock}; use lru::LruCache; -use mysten_metrics::{monitored_mpsc::UnboundedReceiver, monitored_scope, spawn_monitored_task}; +use mysten_metrics::{ + monitored_future, + monitored_mpsc::{self, UnboundedReceiver}, + monitored_scope, spawn_monitored_task, +}; use serde::{Deserialize, Serialize}; use sui_macros::{fail_point_async, fail_point_if}; use sui_protocol_config::ProtocolConfig; @@ -21,10 +25,13 @@ use sui_types::{ base_types::{AuthorityName, EpochId, ObjectID, SequenceNumber, TransactionDigest}, digests::ConsensusCommitDigest, executable_transaction::{TrustedExecutableTransaction, VerifiedExecutableTransaction}, - messages_consensus::{ConsensusTransaction, ConsensusTransactionKey, ConsensusTransactionKind}, + messages_consensus::{ + AuthorityIndex, ConsensusTransaction, ConsensusTransactionKey, ConsensusTransactionKind, + }, sui_system_state::epoch_start_sui_system_state::EpochStartSystemStateTrait, transaction::{SenderSignedData, VerifiedTransaction}, }; +use tokio::task::JoinSet; use tracing::{debug, error, info, instrument, trace_span, warn}; use crate::{ @@ -38,7 +45,9 @@ use crate::{ }, checkpoints::{CheckpointService, CheckpointServiceNotify}, consensus_throughput_calculator::ConsensusThroughputCalculator, - consensus_types::{consensus_output_api::ConsensusOutputAPI, AuthorityIndex}, + consensus_types::consensus_output_api::{ + parse_block_transactions, ConsensusCommitAPI, ParsedTransaction, + }, execution_cache::ObjectCacheRead, scoring_decision::update_low_scoring_authorities, transaction_manager::TransactionManager, @@ -69,7 +78,8 @@ impl ConsensusHandlerInitializer { } } - pub fn new_for_testing( + #[cfg(test)] + pub(crate) fn new_for_testing( state: Arc, checkpoint_service: Arc, ) -> Self { @@ -85,7 +95,7 @@ impl ConsensusHandlerInitializer { } } - pub fn new_consensus_handler(&self) -> ConsensusHandler { + pub(crate) fn new_consensus_handler(&self) -> ConsensusHandler { let new_epoch_start_state = self.epoch_store.epoch_start_state(); let consensus_committee = new_epoch_start_state.get_consensus_committee(); @@ -100,6 +110,10 @@ impl ConsensusHandlerInitializer { self.throughput_calculator.clone(), ) } + + pub(crate) fn metrics(&self) -> &Arc { + &self.state.metrics + } } pub struct ConsensusHandler { @@ -122,7 +136,8 @@ pub struct ConsensusHandler { metrics: Arc, /// Lru cache to quickly discard transactions processed by consensus processed_cache: LruCache, - transaction_scheduler: AsyncTransactionScheduler, + /// Enqueues transactions to the transaction manager via a separate task. + transaction_manager_sender: TransactionManagerSender, /// Using the throughput calculator to record the current consensus throughput throughput_calculator: Arc, } @@ -148,8 +163,8 @@ impl ConsensusHandler { if !last_consensus_stats.stats.is_initialized() { last_consensus_stats.stats = ConsensusStats::new(committee.size()); } - let transaction_scheduler = - AsyncTransactionScheduler::start(transaction_manager, epoch_store.clone()); + let transaction_manager_sender = + TransactionManagerSender::start(transaction_manager, epoch_store.clone()); Self { epoch_store, last_consensus_stats, @@ -159,62 +174,29 @@ impl ConsensusHandler { committee, metrics, processed_cache: LruCache::new(NonZeroUsize::new(PROCESSED_CACHE_CAP).unwrap()), - transaction_scheduler, + transaction_manager_sender, throughput_calculator, } } /// Returns the last subdag index processed by the handler. - pub fn last_processed_subdag_index(&self) -> u64 { + pub(crate) fn last_processed_subdag_index(&self) -> u64 { self.last_consensus_stats.index.sub_dag_index } - /// Updates the execution indexes based on the provided input. - fn update_index_and_hash(&mut self, index: ExecutionIndices, v: &[u8]) { - update_index_and_hash(&mut self.last_consensus_stats, index, v) + pub(crate) fn transaction_manager_sender(&self) -> &TransactionManagerSender { + &self.transaction_manager_sender } } -fn update_index_and_hash( - last_consensus_stats: &mut ExecutionIndicesWithStats, - index: ExecutionIndices, - v: &[u8], -) { - // The entry point of handle_consensus_output_internal() has filtered out any already processed - // consensus output. So we can safely assume that the index is always increasing. - assert!(last_consensus_stats.index < index); - - let previous_hash = last_consensus_stats.hash; - let mut hasher = DefaultHasher::new(); - previous_hash.hash(&mut hasher); - v.hash(&mut hasher); - let hash = hasher.finish(); - // Log hash every 1000th transaction of the subdag - if index.transaction_index % 1000 == 0 { - info!( - "Integrity hash for consensus output at subdag {} transaction {} is {:016x}", - index.sub_dag_index, index.transaction_index, hash - ); - } - - last_consensus_stats.index = index; - last_consensus_stats.hash = hash; -} - impl ConsensusHandler { #[instrument(level = "debug", skip_all)] - async fn handle_consensus_output(&mut self, consensus_output: impl ConsensusOutputAPI) { + async fn handle_consensus_commit(&mut self, consensus_commit: impl ConsensusCommitAPI) { let _scope = monitored_scope("HandleConsensusOutput"); - // This code no longer supports old protocol versions. - assert!(self - .epoch_store - .protocol_config() - .consensus_order_end_of_epoch_last()); - let last_committed_round = self.last_consensus_stats.index.last_committed_round; - let round = consensus_output.leader_round(); + let round = consensus_commit.leader_round(); // TODO: Remove this once narwhal is deprecated. For now mysticeti will not return // more than one leader per round so we are not in danger of ignoring any commits. @@ -230,11 +212,11 @@ impl ConsensusHandler { return; } - /* (serialized, transaction, output_cert) */ + /* (transaction, serialized length) */ let mut transactions = vec![]; - let timestamp = consensus_output.commit_timestamp_ms(); - let leader_author = consensus_output.leader_author_index(); - let commit_sub_dag_index = consensus_output.commit_sub_dag_index(); + let timestamp = consensus_commit.commit_timestamp_ms(); + let leader_author = consensus_commit.leader_author_index(); + let commit_sub_dag_index = consensus_commit.commit_sub_dag_index(); let epoch_start = self .epoch_store @@ -250,23 +232,24 @@ impl ConsensusHandler { }; info!( - %consensus_output, + %consensus_commit, epoch = ?self.epoch_store.epoch(), "Received consensus output" ); - // TODO: testing empty commit explicitly. + let execution_index = ExecutionIndices { + last_committed_round: round, + sub_dag_index: commit_sub_dag_index, + transaction_index: 0_u64, + }; + // This function has filtered out any already processed consensus output. + // So we can safely assume that the index is always increasing. + assert!(self.last_consensus_stats.index < execution_index); + + // TODO: test empty commit explicitly. // Note that consensus commit batch may contain no transactions, but we still need to record the current // round and subdag index in the last_consensus_stats, so that it won't be re-executed in the future. - let empty_bytes = vec![]; - self.update_index_and_hash( - ExecutionIndices { - last_committed_round: round, - sub_dag_index: commit_sub_dag_index, - transaction_index: 0_u64, - }, - &empty_bytes, - ); + self.last_consensus_stats.index = execution_index; // Load all jwks that became active in the previous round, and commit them in this round. // We want to delay one round because none of the transactions in the previous round could @@ -290,7 +273,6 @@ impl ConsensusHandler { ); transactions.push(( - empty_bytes.as_slice(), SequencedConsensusTransactionKind::System(authenticator_state_update_transaction), leader_author, )); @@ -300,7 +282,7 @@ impl ConsensusHandler { self.low_scoring_authorities.clone(), self.epoch_store.committee(), &self.committee, - consensus_output.reputation_score_sorted_desc(), + consensus_commit.reputation_score_sorted_desc(), &self.metrics, self.epoch_store .protocol_config() @@ -315,13 +297,18 @@ impl ConsensusHandler { { let span = trace_span!("process_consensus_certs"); let _guard = span.enter(); - for (authority_index, authority_transactions) in consensus_output.transactions() { + for (authority_index, parsed_transactions) in consensus_commit.transactions() { // TODO: consider only messages within 1~3 rounds of the leader? self.last_consensus_stats .stats .inc_num_messages(authority_index as usize); - for (serialized_transaction, transaction) in authority_transactions { - let kind = classify(&transaction); + for parsed in parsed_transactions { + // Skip executing rejected transactions. Unlocking is the responsibility of the + // consensus transaction handler. + if parsed.rejected { + continue; + } + let kind = classify(&parsed.transaction); self.metrics .consensus_handler_processed .with_label_values(&[kind]) @@ -329,23 +316,26 @@ impl ConsensusHandler { self.metrics .consensus_handler_transaction_sizes .with_label_values(&[kind]) - .observe(serialized_transaction.len() as f64); + .observe(parsed.serialized_len as f64); + // UserTransaction exists only when mysticeti_fastpath is enabled in protocol config. if matches!( - &transaction.kind, + &parsed.transaction.kind, ConsensusTransactionKind::CertifiedTransaction(_) + | ConsensusTransactionKind::UserTransaction(_) ) { self.last_consensus_stats .stats .inc_num_user_transactions(authority_index as usize); } if let ConsensusTransactionKind::RandomnessStateUpdate(randomness_round, _) = - &transaction.kind + &parsed.transaction.kind { // These are deprecated and we should never see them. Log an error and eat the tx if one appears. error!("BUG: saw deprecated RandomnessStateUpdate tx for commit round {round:?}, randomness round {randomness_round:?}") } else { - let transaction = SequencedConsensusTransactionKind::External(transaction); - transactions.push((serialized_transaction, transaction, authority_index)); + let transaction = + SequencedConsensusTransactionKind::External(parsed.transaction); + transactions.push((transaction, authority_index)); } } } @@ -373,9 +363,7 @@ impl ConsensusHandler { // entries while we're iterating over the sequenced transactions. let mut processed_set = HashSet::new(); - for (seq, (serialized, transaction, cert_origin)) in - transactions.into_iter().enumerate() - { + for (seq, (transaction, cert_origin)) in transactions.into_iter().enumerate() { // In process_consensus_transactions_and_commit_boundary(), we will add a system consensus commit // prologue transaction, which will be the first transaction in this consensus commit batch. // Therefore, the transaction sequence number starts from 1 here. @@ -385,7 +373,7 @@ impl ConsensusHandler { transaction_index: (seq + 1) as u64, }; - self.update_index_and_hash(current_tx_index, serialized); + self.last_consensus_stats.index = current_tx_index; let certificate_author = *self .epoch_store @@ -416,14 +404,14 @@ impl ConsensusHandler { } } - let transactions_to_schedule = self + let executable_transactions = self .epoch_store .process_consensus_transactions_and_commit_boundary( all_transactions, &self.last_consensus_stats, &self.checkpoint_service, self.cache_reader.as_ref(), - &ConsensusCommitInfo::new(self.epoch_store.protocol_config(), &consensus_output), + &ConsensusCommitInfo::new(self.epoch_store.protocol_config(), &consensus_commit), &self.metrics, ) .await @@ -431,7 +419,7 @@ impl ConsensusHandler { // update the calculated throughput self.throughput_calculator - .add_transactions(timestamp, transactions_to_schedule.len() as u64); + .add_transactions(timestamp, executable_transactions.len() as u64); fail_point_if!("correlated-crash-after-consensus-commit-boundary", || { let key = [commit_sub_dag_index, self.epoch_store.epoch()]; @@ -442,32 +430,35 @@ impl ConsensusHandler { fail_point_async!("crash"); // for tests that produce random crashes - self.transaction_scheduler - .schedule(transactions_to_schedule) - .await; + self.transaction_manager_sender + .send(executable_transactions); } } -struct AsyncTransactionScheduler { - sender: tokio::sync::mpsc::Sender>, +/// Sends transactions to the transaction manager in a separate task, +/// to avoid blocking consensus handler. +#[derive(Clone)] +pub(crate) struct TransactionManagerSender { + // Using unbounded channel to avoid blocking consensus commit and transaction handler. + sender: monitored_mpsc::UnboundedSender>, } -impl AsyncTransactionScheduler { - pub fn start( +impl TransactionManagerSender { + fn start( transaction_manager: Arc, epoch_store: Arc, ) -> Self { - let (sender, recv) = tokio::sync::mpsc::channel(16); + let (sender, recv) = monitored_mpsc::unbounded_channel("transaction_manager_sender"); spawn_monitored_task!(Self::run(recv, transaction_manager, epoch_store)); Self { sender } } - pub async fn schedule(&self, transactions: Vec) { - self.sender.send(transactions).await.ok(); + fn send(&self, transactions: Vec) { + let _ = self.sender.send(transactions); } - pub async fn run( - mut recv: tokio::sync::mpsc::Receiver>, + async fn run( + mut recv: monitored_mpsc::UnboundedReceiver>, transaction_manager: Arc, epoch_store: Arc, ) { @@ -478,48 +469,51 @@ impl AsyncTransactionScheduler { } } -/// Consensus handler used by Mysticeti. Since Mysticeti repo is not yet integrated, we use a -/// channel to receive the consensus output from Mysticeti. -/// During initialization, the sender is passed into Mysticeti which can send consensus output -/// to the channel. -pub struct MysticetiConsensusHandler { - handle: Option>, +/// Manages the lifetime of tasks handling the commits and transactions output by consensus. +pub(crate) struct MysticetiConsensusHandler { + tasks: JoinSet<()>, } impl MysticetiConsensusHandler { - pub fn new( + pub(crate) fn new( mut consensus_handler: ConsensusHandler, - mut receiver: UnboundedReceiver, + consensus_transaction_handler: ConsensusTransactionHandler, + mut commit_receiver: UnboundedReceiver, + mut transaction_receiver: UnboundedReceiver)>>, commit_consumer_monitor: Arc, ) -> Self { - let handle = spawn_monitored_task!(async move { + let mut tasks = JoinSet::new(); + tasks.spawn(monitored_future!(async move { // TODO: pause when execution is overloaded, so consensus can detect the backpressure. - while let Some(consensus_output) = receiver.recv().await { - let commit_index = consensus_output.commit_ref.index; + while let Some(consensus_commit) = commit_receiver.recv().await { + let commit_index = consensus_commit.commit_ref.index; consensus_handler - .handle_consensus_output(consensus_output) + .handle_consensus_commit(consensus_commit) .await; commit_consumer_monitor.set_highest_handled_commit(commit_index); } - }); - Self { - handle: Some(handle), - } - } - - pub async fn abort(&mut self) { - if let Some(handle) = self.handle.take() { - handle.abort(); - let _ = handle.await; + })); + if consensus_transaction_handler.enabled() { + tasks.spawn(monitored_future!(async move { + while let Some(blocks_and_rejected_transactions) = transaction_receiver.recv().await + { + let parsed_transactions = blocks_and_rejected_transactions + .into_iter() + .flat_map(|(block, rejected_transactions)| { + parse_block_transactions(&block, &rejected_transactions) + }) + .collect::>(); + consensus_transaction_handler + .handle_consensus_transactions(parsed_transactions) + .await; + } + })); } + Self { tasks } } -} -impl Drop for MysticetiConsensusHandler { - fn drop(&mut self) { - if let Some(handle) = self.handle.take() { - handle.abort(); - } + pub(crate) async fn abort(&mut self) { + self.tasks.shutdown().await; } } @@ -668,7 +662,7 @@ impl SequencedConsensusTransactionKind { pub fn is_executable_transaction(&self) -> bool { match self { - SequencedConsensusTransactionKind::External(ext) => ext.is_user_certificate(), + SequencedConsensusTransactionKind::External(ext) => ext.is_certified_transaction(), SequencedConsensusTransactionKind::System(_) => true, } } @@ -782,11 +776,11 @@ pub struct ConsensusCommitInfo { } impl ConsensusCommitInfo { - fn new(protocol_config: &ProtocolConfig, consensus_output: &impl ConsensusOutputAPI) -> Self { + fn new(protocol_config: &ProtocolConfig, consensus_commit: &impl ConsensusCommitAPI) -> Self { Self { - round: consensus_output.leader_round(), - timestamp: consensus_output.commit_timestamp_ms(), - consensus_commit_digest: consensus_output.consensus_digest(protocol_config), + round: consensus_commit.leader_round(), + timestamp: consensus_commit.commit_timestamp_ms(), + consensus_commit_digest: consensus_commit.consensus_digest(protocol_config), #[cfg(any(test, feature = "test-utils"))] skip_consensus_commit_prologue_in_test: false, @@ -862,6 +856,105 @@ impl ConsensusCommitInfo { } } +/// Handles certified and rejected transactions output by consensus. +pub(crate) struct ConsensusTransactionHandler { + /// Whether to enable handling certified transactions. + enabled: bool, + /// Per-epoch store. + epoch_store: Arc, + /// Enqueues transactions to the transaction manager via a separate task. + transaction_manager_sender: TransactionManagerSender, + /// Metrics for consensus transaction handling. + metrics: Arc, +} + +impl ConsensusTransactionHandler { + pub fn new( + epoch_store: Arc, + transaction_manager_sender: TransactionManagerSender, + metrics: Arc, + ) -> Self { + Self { + enabled: epoch_store.protocol_config().mysticeti_fastpath(), + epoch_store, + transaction_manager_sender, + metrics, + } + } + + pub fn enabled(&self) -> bool { + self.enabled + } + + pub async fn handle_consensus_transactions(&self, parsed_transactions: Vec) { + let mut pending_consensus_transactions = vec![]; + let executable_transactions: Vec<_> = parsed_transactions + .into_iter() + .filter_map(|parsed| { + // TODO(fastpath): unlock rejected transactions. + // TODO(fastpath): maybe avoid parsing blocks twice between commit and transaction handling? + if parsed.rejected { + self.metrics + .consensus_transaction_handler_processed + .with_label_values(&["rejected"]) + .inc(); + return None; + } + self.metrics + .consensus_transaction_handler_processed + .with_label_values(&["certified"]) + .inc(); + match &parsed.transaction.kind { + ConsensusTransactionKind::UserTransaction(tx) => { + // TODO(fastpath): use a separate function to check if a transaction should be executed in fastpath. + if tx.contains_shared_object() { + return None; + } + pending_consensus_transactions.push(parsed.transaction.clone()); + let tx = VerifiedTransaction::new_from_verified(*tx.clone()); + Some(VerifiedExecutableTransaction::new_from_consensus( + tx, + self.epoch_store.epoch(), + parsed.round, + parsed.authority, + parsed.transaction_index, + )) + } + _ => None, + } + }) + .collect(); + + if pending_consensus_transactions.is_empty() { + return; + } + { + let reconfig_state = self.epoch_store.get_reconfig_state_read_lock_guard(); + // Stop executing fastpath transactions when epoch change starts. + if !reconfig_state.should_accept_user_certs() { + return; + } + // Otherwise, try to ensure the certified transactions get into consensus before epoch change. + // TODO(fastpath): avoid race with removals in consensus adapter, by waiting to handle commit after + // all blocks in the commit are processed via the transaction handler. Other kinds of races need to be + // avoided as well. Or we can track pending consensus transactions inside consensus instead. + self.epoch_store + .insert_pending_consensus_transactions( + &pending_consensus_transactions, + Some(&reconfig_state), + ) + .unwrap_or_else(|e| { + panic!("Failed to insert pending consensus transactions: {}", e) + }); + } + self.metrics + .consensus_transaction_handler_fastpath_executions + .inc_by(executable_transactions.len() as u64); + self.transaction_manager_sender + .send(executable_transactions); + } +} + #[cfg(test)] mod tests { use consensus_core::{ @@ -872,6 +965,7 @@ mod tests { use sui_types::{ base_types::{random_object_ref, AuthorityName, SuiAddress}, committee::Committee, + crypto::deterministic_random_account_key, messages_consensus::{ AuthorityCapabilitiesV1, ConsensusTransaction, ConsensusTransactionKind, }, @@ -889,12 +983,14 @@ mod tests { test_authority_builder::TestAuthorityBuilder, }, checkpoints::CheckpointServiceNoop, - consensus_adapter::consensus_tests::{test_certificates, test_gas_objects}, + consensus_adapter::consensus_tests::{ + test_certificates, test_gas_objects, test_user_transaction, + }, post_consensus_tx_reorder::PostConsensusTxReorder, }; #[tokio::test] - pub async fn test_consensus_handler() { + pub async fn test_consensus_commit_handler() { // GIVEN let mut objects = test_gas_objects(); let shared_object = Object::shared_for_testing(); @@ -955,6 +1051,7 @@ mod tests { let committed_sub_dag = CommittedSubDag::new( leader_block.reference(), blocks.clone(), + vec![vec![]; blocks.len()], leader_block.timestamp_ms(), CommitRef::new(10, CommitDigest::MIN), vec![], @@ -962,7 +1059,7 @@ mod tests { // AND processing the consensus output once consensus_handler - .handle_consensus_output(committed_sub_dag.clone()) + .handle_consensus_commit(committed_sub_dag.clone()) .await; // AND capturing the consensus stats @@ -975,7 +1072,7 @@ mod tests { ); assert_eq!(last_consensus_stats_1.index.sub_dag_index, 10_u64); assert_eq!(last_consensus_stats_1.index.last_committed_round, 100_u64); - assert_ne!(last_consensus_stats_1.hash, 0); + assert_eq!(last_consensus_stats_1.hash, 0); assert_eq!( last_consensus_stats_1.stats.get_num_messages(0), num_blocks as u64 @@ -989,36 +1086,137 @@ mod tests { // THEN the consensus stats do not update for _ in 0..2 { consensus_handler - .handle_consensus_output(committed_sub_dag.clone()) + .handle_consensus_commit(committed_sub_dag.clone()) .await; let last_consensus_stats_2 = consensus_handler.last_consensus_stats.clone(); assert_eq!(last_consensus_stats_1, last_consensus_stats_2); } } - #[test] - pub fn test_update_index_and_hash() { - let index0 = ExecutionIndices { - sub_dag_index: 0, - transaction_index: 5, - last_committed_round: 0, - }; - let index1 = ExecutionIndices { - sub_dag_index: 1, - transaction_index: 2, - last_committed_round: 3, - }; + #[tokio::test] + pub async fn test_consensus_transaction_handler() { + // GIVEN + // 1 account keypair + let (sender, keypair) = deterministic_random_account_key(); + // 8 gas objects. + let gas_objects: Vec = (0..8) + .map(|_| Object::with_id_owner_for_testing(ObjectID::random(), sender)) + .collect(); + // 4 owned objects. + let owned_objects: Vec = (0..4) + .map(|_| Object::with_id_owner_for_testing(ObjectID::random(), sender)) + .collect(); + // 4 shared objects. + let shared_objects: Vec = (0..4) + .map(|_| Object::shared_for_testing()) + .collect::>(); + let mut all_objects = gas_objects.clone(); + all_objects.extend(owned_objects.clone()); + all_objects.extend(shared_objects.clone()); - let mut last_seen = ExecutionIndicesWithStats { - index: index0, - hash: 1000, - stats: ConsensusStats::default(), - }; + let network_config = + sui_swarm_config::network_config_builder::ConfigBuilder::new_with_temp_dir() + .with_objects(all_objects.clone()) + .build(); + + let state = TestAuthorityBuilder::new() + .with_network_config(&network_config, 0) + .build() + .await; + let epoch_store = state.epoch_store_for_testing().clone(); + let transaction_manager_sender = TransactionManagerSender::start( + state.transaction_manager().clone(), + epoch_store.clone(), + ); + let transaction_handler = ConsensusTransactionHandler::new( + epoch_store, + transaction_manager_sender, + state.metrics.clone(), + ); + + // AND create test transactions alternating between owned and shared input. + let mut transactions = vec![]; + for (i, gas_object) in gas_objects.iter().enumerate() { + let input_object = if i % 2 == 0 { + owned_objects.get(i / 2).unwrap().clone() + } else { + shared_objects.get(i / 2).unwrap().clone() + }; + let transaction = test_user_transaction( + &state, + sender, + &keypair, + gas_object.clone(), + vec![input_object], + ) + .await; + transactions.push(transaction); + } + + let serialized_transactions: Vec<_> = transactions + .iter() + .map(|t| { + Transaction::new( + bcs::to_bytes(&ConsensusTransaction::new_user_transaction_message( + &state.name, + t.inner().clone(), + )) + .unwrap(), + ) + }) + .collect(); + + // AND create block for all transactions + let block = VerifiedBlock::new_for_test( + TestBlock::new(100, 1) + .set_transactions(serialized_transactions.clone()) + .build(), + ); + + // AND set rejected transactions. + let rejected_transactions = vec![0, 3, 4]; + + // AND process the transactions from consensus output. + transaction_handler + .handle_consensus_transactions(parse_block_transactions(&block, &rejected_transactions)) + .await; - let tx = &[0]; - update_index_and_hash(&mut last_seen, index1, tx); - assert_eq!(last_seen.index, index1); - assert_ne!(last_seen.hash, 1000); + // THEN check for execution status of transactions. + for (i, t) in transactions.iter().enumerate() { + // Do not expect shared transactions or rejected transactions to be executed. + if i % 2 == 1 || rejected_transactions.contains(&(i as TransactionIndex)) { + continue; + } + let digest = t.digest(); + if let Ok(Ok(_)) = tokio::time::timeout( + std::time::Duration::from_secs(10), + state.notify_read_effects(*digest), + ) + .await + { + // Effects exist as expected. + } else { + panic!("Transaction {} {} did not execute", i, digest); + } + } + + // THEN check for no inflight or suspended transactions. + state.transaction_manager().check_empty_for_testing(); + + // THEN check that rejected transactions are not executed. + for (i, t) in transactions.iter().enumerate() { + // Expect shared transactions or rejected transactions to not have executed. + if i % 2 == 0 && !rejected_transactions.contains(&(i as TransactionIndex)) { + continue; + } + let digest = t.digest(); + assert!( + !state.is_tx_already_executed(digest).unwrap(), + "Rejected transaction {} {} should not have been executed", + i, + digest + ); + } } #[test] diff --git a/crates/sui-core/src/consensus_manager/mysticeti_manager.rs b/crates/sui-core/src/consensus_manager/mysticeti_manager.rs index 93612791e90f3..9b71d11b7f473 100644 --- a/crates/sui-core/src/consensus_manager/mysticeti_manager.rs +++ b/crates/sui-core/src/consensus_manager/mysticeti_manager.rs @@ -5,9 +5,9 @@ use std::{path::PathBuf, sync::Arc, time::Duration}; use arc_swap::ArcSwapOption; use async_trait::async_trait; use consensus_config::{Committee, NetworkKeyPair, Parameters, ProtocolKeyPair}; -use consensus_core::{CommitConsumer, CommitIndex, ConsensusAuthority}; +use consensus_core::{CommitConsumer, CommitConsumerMonitor, CommitIndex, ConsensusAuthority}; use fastcrypto::ed25519; -use mysten_metrics::{monitored_mpsc::unbounded_channel, RegistryID, RegistryService}; +use mysten_metrics::{RegistryID, RegistryService}; use prometheus::Registry; use sui_config::NodeConfig; use sui_protocol_config::ConsensusNetwork; @@ -19,7 +19,9 @@ use tracing::info; use crate::{ authority::authority_per_epoch_store::AuthorityPerEpochStore, - consensus_handler::{ConsensusHandlerInitializer, MysticetiConsensusHandler}, + consensus_handler::{ + ConsensusHandlerInitializer, ConsensusTransactionHandler, MysticetiConsensusHandler, + }, consensus_manager::{ ConsensusManagerMetrics, ConsensusManagerTrait, Running, RunningLockGuard, }, @@ -46,6 +48,7 @@ pub struct MysticetiManager { client: Arc, // TODO: switch to parking_lot::Mutex. consensus_handler: Mutex>, + consumer_monitor: ArcSwapOption, } impl MysticetiManager { @@ -70,6 +73,7 @@ impl MysticetiManager { client, consensus_handler: Mutex::new(None), boot_counter: Mutex::new(0), + consumer_monitor: ArcSwapOption::empty(), } } @@ -144,18 +148,35 @@ impl ConsensusManagerTrait for MysticetiManager { let registry = Registry::new_custom(Some("consensus".to_string()), None).unwrap(); - let (commit_sender, commit_receiver) = unbounded_channel("consensus_output"); - let consensus_handler = consensus_handler_initializer.new_consensus_handler(); - let consumer = CommitConsumer::new( - commit_sender, - consensus_handler.last_processed_subdag_index() as CommitIndex, - ); - let monitor = consumer.monitor(); + let (commit_consumer, commit_receiver, transaction_receiver) = + CommitConsumer::new(consensus_handler.last_processed_subdag_index() as CommitIndex); + let monitor = commit_consumer.monitor(); + + // If there is a previous consumer monitor, it indicates that the consensus engine has been restarted, due to an epoch change. However, that on its + // own doesn't tell us much whether it participated on an active epoch or an old one. We need to check if it has handled any commits to determine this. + // If indeed any commits did happen, then we assume that node did participate on previous run. + let participated_on_previous_run = + if let Some(previous_monitor) = self.consumer_monitor.swap(Some(monitor.clone())) { + previous_monitor.highest_handled_commit() > 0 + } else { + false + }; + + // Increment the boot counter only if the consensus successfully participated in the previous run. + // This is typical during normal epoch changes, where the node restarts as expected, and the boot counter is incremented to prevent amnesia recovery on the next start. + // If the node is recovering from a restore process and catching up across multiple epochs, it won't handle any commits until it reaches the last active epoch. + // In this scenario, we do not increment the boot counter, as we need amnesia recovery to run. + let mut boot_counter = self.boot_counter.lock().await; + if participated_on_previous_run { + *boot_counter += 1; + } else { + info!( + "Node has not participated in previous run. Boot counter will not increment {}", + *boot_counter + ); + } - // TODO(mysticeti): Investigate if we need to return potential errors from - // AuthorityNode and add retries here? - let boot_counter = *self.boot_counter.lock().await; let authority = ConsensusAuthority::start( network_type, own_index, @@ -165,17 +186,13 @@ impl ConsensusManagerTrait for MysticetiManager { self.protocol_keypair.clone(), self.network_keypair.clone(), Arc::new(tx_validator.clone()), - consumer, + commit_consumer, registry.clone(), - boot_counter, + *boot_counter, ) .await; let client = authority.transaction_client(); - // Now increment the boot counter - let mut boot_counter = self.boot_counter.lock().await; - *boot_counter += 1; - let registry_id = self.registry_service.add(registry.clone()); let registered_authority = Arc::new((authority, registry_id)); @@ -185,7 +202,19 @@ impl ConsensusManagerTrait for MysticetiManager { self.client.set(client); // spin up the new mysticeti consensus handler to listen for committed sub dags - let handler = MysticetiConsensusHandler::new(consensus_handler, commit_receiver, monitor); + let consensus_transaction_handler = ConsensusTransactionHandler::new( + epoch_store.clone(), + consensus_handler.transaction_manager_sender().clone(), + consensus_handler_initializer.metrics().clone(), + ); + let handler = MysticetiConsensusHandler::new( + consensus_handler, + consensus_transaction_handler, + commit_receiver, + transaction_receiver, + monitor, + ); + let mut consensus_handler = self.consensus_handler.lock().await; *consensus_handler = Some(handler); diff --git a/crates/sui-core/src/consensus_types/consensus_output_api.rs b/crates/sui-core/src/consensus_types/consensus_output_api.rs index d75655ed754ac..e0c2eeebc16e4 100644 --- a/crates/sui-core/src/consensus_types/consensus_output_api.rs +++ b/crates/sui-core/src/consensus_types/consensus_output_api.rs @@ -1,21 +1,30 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::fmt::Display; +use std::{cmp::Ordering, fmt::Display}; -use consensus_core::{BlockAPI, CommitDigest}; -use fastcrypto::hash::Hash; -use narwhal_types::{BatchAPI, CertificateAPI, ConsensusOutputDigest, HeaderAPI}; +use consensus_core::{BlockAPI, CommitDigest, TransactionIndex, VerifiedBlock}; use sui_protocol_config::ProtocolConfig; -use sui_types::{digests::ConsensusCommitDigest, messages_consensus::ConsensusTransaction}; - -use crate::consensus_types::AuthorityIndex; - -/// A list of tuples of: -/// (certificate origin authority index, all transactions corresponding to the certificate). -/// For each transaction, returns the serialized transaction and the deserialized transaction. -type ConsensusOutputTransactions<'a> = Vec<(AuthorityIndex, Vec<(&'a [u8], ConsensusTransaction)>)>; +use sui_types::{ + digests::ConsensusCommitDigest, + messages_consensus::{AuthorityIndex, ConsensusTransaction, Round}, +}; + +pub(crate) struct ParsedTransaction { + // Transaction from consensus output. + pub(crate) transaction: ConsensusTransaction, + // Whether the transaction was rejected in voting. + pub(crate) rejected: bool, + // Bytes length of the serialized transaction + pub(crate) serialized_len: usize, + // Consensus round of the block containing the transaction. + pub(crate) round: Round, + // Authority index of the block containing the transaction. + pub(crate) authority: AuthorityIndex, + // Transaction index in the block. + pub(crate) transaction_index: TransactionIndex, +} -pub(crate) trait ConsensusOutputAPI: Display { +pub(crate) trait ConsensusCommitAPI: Display { fn reputation_score_sorted_desc(&self) -> Option>; fn leader_round(&self) -> u64; fn leader_author_index(&self) -> AuthorityIndex; @@ -26,85 +35,14 @@ pub(crate) trait ConsensusOutputAPI: Display { /// Returns a unique global index for each committed sub-dag. fn commit_sub_dag_index(&self) -> u64; - /// Returns all transactions in the commit. - fn transactions(&self) -> ConsensusOutputTransactions<'_>; + /// Returns all accepted and rejected transactions per block in the commit in deterministic order. + fn transactions(&self) -> Vec<(AuthorityIndex, Vec)>; /// Returns the digest of consensus output. fn consensus_digest(&self, protocol_config: &ProtocolConfig) -> ConsensusCommitDigest; } -impl ConsensusOutputAPI for narwhal_types::ConsensusOutput { - fn reputation_score_sorted_desc(&self) -> Option> { - if !self.sub_dag.reputation_score.final_of_schedule { - return None; - } - Some( - self.sub_dag - .reputation_score - .authorities_by_score_desc() - .into_iter() - .map(|(id, score)| (id.0 as AuthorityIndex, score)) - .collect(), - ) - } - - fn leader_round(&self) -> u64 { - self.sub_dag.leader_round() - } - - fn leader_author_index(&self) -> AuthorityIndex { - self.sub_dag.leader.origin().0 as AuthorityIndex - } - - fn commit_timestamp_ms(&self) -> u64 { - self.sub_dag.commit_timestamp() - } - - fn commit_sub_dag_index(&self) -> u64 { - self.sub_dag.sub_dag_index - } - - fn transactions(&self) -> ConsensusOutputTransactions { - assert!(self.sub_dag.certificates.len() == self.batches.len()); - self.sub_dag - .certificates - .iter() - .zip(&self.batches) - .map(|(cert, batches)| { - assert_eq!(cert.header().payload().len(), batches.len()); - let transactions: Vec<(&[u8], ConsensusTransaction)> = - batches.iter().flat_map(|batch| { - let digest = batch.digest(); - assert!(cert.header().payload().contains_key(&digest)); - batch.transactions().iter().map(move |serialized_transaction| { - let transaction = match bcs::from_bytes::( - serialized_transaction, - ) { - Ok(transaction) => transaction, - Err(err) => { - // This should have been prevented by transaction verifications in consensus. - panic!( - "Unexpected malformed transaction (failed to deserialize): {}\nCertificate={:?} BatchDigest={:?} Transaction={:?}", - err, cert, digest, serialized_transaction - ); - } - }; - (serialized_transaction.as_ref(), transaction) - }) - }).collect(); - (cert.origin().0 as AuthorityIndex, transactions) - }).collect() - } - - fn consensus_digest(&self, _protocol_config: &ProtocolConfig) -> ConsensusCommitDigest { - // We port ConsensusOutputDigest, a narwhal space object, into ConsensusCommitDigest, a sui-core space object. - // We assume they always have the same format. - static_assertions::assert_eq_size!(ConsensusCommitDigest, ConsensusOutputDigest); - ConsensusCommitDigest::new(self.digest().into_inner()) - } -} - -impl ConsensusOutputAPI for consensus_core::CommittedSubDag { +impl ConsensusCommitAPI for consensus_core::CommittedSubDag { fn reputation_score_sorted_desc(&self) -> Option> { if !self.reputation_scores_desc.is_empty() { Some( @@ -135,30 +73,15 @@ impl ConsensusOutputAPI for consensus_core::CommittedSubDag { self.commit_ref.index.into() } - fn transactions(&self) -> ConsensusOutputTransactions { + fn transactions(&self) -> Vec<(AuthorityIndex, Vec)> { self.blocks .iter() - .map(|block| { - let round = block.round(); - let author = block.author().value() as AuthorityIndex; - let transactions: Vec<_> = block - .transactions() - .iter() - .flat_map(|tx| { - let transaction = bcs::from_bytes::(tx.data()); - match transaction { - Ok(transaction) => Some(( - tx.data(), - transaction, - )), - Err(err) => { - tracing::error!("Failed to deserialize sequenced consensus transaction(this should not happen) {} from {author} at {round}", err); - None - }, - } - }) - .collect(); - (author, transactions) + .zip(self.rejected_transactions_by_block.iter()) + .map(|(block, rejected_transactions)| { + ( + block.author().value() as AuthorityIndex, + parse_block_transactions(block, rejected_transactions), + ) }) .collect() } @@ -174,3 +97,49 @@ impl ConsensusOutputAPI for consensus_core::CommittedSubDag { } } } + +pub(crate) fn parse_block_transactions( + block: &VerifiedBlock, + rejected_transactions: &[TransactionIndex], +) -> Vec { + let round = block.round(); + let authority = block.author().value() as AuthorityIndex; + + let mut rejected_idx = 0; + block + .transactions() + .iter().enumerate() + .map(|(index, tx)| { + let transaction = match bcs::from_bytes::(tx.data()) { + Ok(transaction) => transaction, + Err(err) => { + panic!("Failed to deserialize sequenced consensus transaction(this should not happen) {err} from {authority} at {round}"); + }, + }; + let rejected = if rejected_idx < rejected_transactions.len() { + match (index as TransactionIndex).cmp(&rejected_transactions[rejected_idx]) { + Ordering::Less => { + false + }, + Ordering::Equal => { + rejected_idx += 1; + true + }, + Ordering::Greater => { + panic!("Rejected transaction indices are not in order. Block {block:?}, rejected transactions: {rejected_transactions:?}"); + }, + } + } else { + false + }; + ParsedTransaction { + transaction, + rejected, + serialized_len: tx.data().len(), + round, + authority, + transaction_index: index as TransactionIndex, + } + }) + .collect() +} diff --git a/crates/sui-core/src/consensus_types/mod.rs b/crates/sui-core/src/consensus_types/mod.rs index 5aab7d258d1bd..742bb95d2a16b 100644 --- a/crates/sui-core/src/consensus_types/mod.rs +++ b/crates/sui-core/src/consensus_types/mod.rs @@ -2,7 +2,3 @@ // SPDX-License-Identifier: Apache-2.0 pub(crate) mod consensus_output_api; - -/// An unique integer ID for a validator used by consensus. -/// In Mysticeti, this is used the same way as the AuthorityIndex type there. -pub type AuthorityIndex = u32; diff --git a/crates/sui-core/src/consensus_validator.rs b/crates/sui-core/src/consensus_validator.rs index 9c2609e36d971..ad47a537b2110 100644 --- a/crates/sui-core/src/consensus_validator.rs +++ b/crates/sui-core/src/consensus_validator.rs @@ -91,7 +91,13 @@ impl SuiTxValidator { | ConsensusTransactionKind::RandomnessStateUpdate(_, _) => {} ConsensusTransactionKind::UserTransaction(_tx) => { - // TODO: implement verification for uncertified user transactions if needed + if !self.epoch_store.protocol_config().mysticeti_fastpath() { + return Err(SuiError::UnexpectedMessage( + "ConsensusTransactionKind::UserTransaction is unsupported".to_string(), + ) + .into()); + } + // TODO(fastpath): implement verification for uncertified user transactions. } } } diff --git a/crates/sui-core/src/execution_cache.rs b/crates/sui-core/src/execution_cache.rs index 226c4efa3b7c6..83cca46df4ccb 100644 --- a/crates/sui-core/src/execution_cache.rs +++ b/crates/sui-core/src/execution_cache.rs @@ -104,7 +104,7 @@ impl ExecutionCacheTraitPointers { } } -static ENABLE_WRITEBACK_CACHE_ENV_VAR: &str = "ENABLE_WRITEBACK_CACHE"; +static DISABLE_WRITEBACK_CACHE_ENV_VAR: &str = "DISABLE_WRITEBACK_CACHE"; #[derive(Debug)] pub enum ExecutionCacheConfigType { @@ -130,12 +130,12 @@ pub fn choose_execution_cache(config: &ExecutionCacheConfig) -> ExecutionCacheCo } } - if std::env::var(ENABLE_WRITEBACK_CACHE_ENV_VAR).is_ok() - || matches!(config, ExecutionCacheConfig::WritebackCache { .. }) + if std::env::var(DISABLE_WRITEBACK_CACHE_ENV_VAR).is_ok() + || matches!(config, ExecutionCacheConfig::PassthroughCache) { - ExecutionCacheConfigType::WritebackCache - } else { ExecutionCacheConfigType::PassthroughCache + } else { + ExecutionCacheConfigType::WritebackCache } } @@ -158,13 +158,13 @@ pub fn build_execution_cache_from_env( ) -> ExecutionCacheTraitPointers { let execution_cache_metrics = Arc::new(ExecutionCacheMetrics::new(prometheus_registry)); - if std::env::var(ENABLE_WRITEBACK_CACHE_ENV_VAR).is_ok() { + if std::env::var(DISABLE_WRITEBACK_CACHE_ENV_VAR).is_ok() { ExecutionCacheTraitPointers::new( - WritebackCache::new(store.clone(), execution_cache_metrics).into(), + PassthroughCache::new(store.clone(), execution_cache_metrics).into(), ) } else { ExecutionCacheTraitPointers::new( - PassthroughCache::new(store.clone(), execution_cache_metrics).into(), + WritebackCache::new(store.clone(), execution_cache_metrics).into(), ) } } @@ -847,11 +847,11 @@ macro_rules! implement_passthrough_traits { impl ExecutionCacheReconfigAPI for $implementor { fn insert_genesis_object(&self, object: Object) -> SuiResult { - self.store.insert_genesis_object(object) + self.insert_genesis_object_impl(object) } fn bulk_insert_genesis_objects(&self, objects: &[Object]) -> SuiResult { - self.store.bulk_insert_genesis_objects(objects) + self.bulk_insert_genesis_objects_impl(objects) } fn revert_state_update(&self, digest: &TransactionDigest) -> SuiResult { diff --git a/crates/sui-core/src/execution_cache/object_locks.rs b/crates/sui-core/src/execution_cache/object_locks.rs index ed27ebb1907fe..f77b9fde6155a 100644 --- a/crates/sui-core/src/execution_cache/object_locks.rs +++ b/crates/sui-core/src/execution_cache/object_locks.rs @@ -4,6 +4,7 @@ use crate::authority::authority_per_epoch_store::{AuthorityPerEpochStore, LockDetails}; use dashmap::mapref::entry::Entry as DashMapEntry; use dashmap::DashMap; +use mysten_common::*; use sui_types::base_types::{ObjectID, ObjectRef}; use sui_types::error::{SuiError, SuiResult, UserInputError}; use sui_types::object::Object; @@ -13,6 +14,8 @@ use tracing::{debug, info, instrument, trace}; use super::writeback_cache::WritebackCache; +type RefCount = usize; + pub(super) struct ObjectLocks { // When acquire transaction locks, lock entries are briefly inserted into this map. The map // exists to provide atomic test-and-set operations on the locks. After all locks have been inserted @@ -23,7 +26,7 @@ pub(super) struct ObjectLocks { // those objects. Therefore we do a db read for each object we are locking. // // TODO: find a strategy to allow us to avoid db reads for each object. - locked_transactions: DashMap, + locked_transactions: DashMap, } impl ObjectLocks { @@ -38,29 +41,10 @@ impl ObjectLocks { obj_ref: &ObjectRef, epoch_store: &AuthorityPerEpochStore, ) -> SuiResult> { - match self.locked_transactions.entry(*obj_ref) { - DashMapEntry::Vacant(vacant) => { - let tables = epoch_store.tables()?; - let lock = tables.get_locked_transaction(obj_ref)?; - if let Some(lock_details) = lock { - vacant.insert(lock_details); - } - Ok(lock) - } - DashMapEntry::Occupied(occupied) => { - if cfg!(debug_assertions) { - if let Some(lock_details) = epoch_store - .tables() - .unwrap() - .get_locked_transaction(obj_ref) - .unwrap() - { - assert_eq!(*occupied.get(), lock_details); - } - } - Ok(Some(*occupied.get())) - } - } + // We don't consult the in-memory state here. We are only interested in state that + // has been committed to the db. This is because in memory state is reverted + // if the transaction is not successfully locked. + epoch_store.tables()?.get_locked_transaction(obj_ref) } /// Attempts to atomically test-and-set a transaction lock on an object. @@ -96,15 +80,18 @@ impl ObjectLocks { let tables = epoch_store.tables()?; if let Some(lock_details) = tables.get_locked_transaction(obj_ref)? { trace!("read lock from db: {:?}", lock_details); - vacant.insert(lock_details); + vacant.insert((1, lock_details)); lock_details } else { trace!("set lock: {:?}", new_lock); - vacant.insert(new_lock); + vacant.insert((1, new_lock)); new_lock } } - DashMapEntry::Occupied(occupied) => *occupied.get(), + DashMapEntry::Occupied(mut occupied) => { + occupied.get_mut().0 += 1; + occupied.get().1 + } }; if prev_lock != new_lock { @@ -141,7 +128,6 @@ impl ObjectLocks { let live_digest = live_object.digest(); if obj_ref.2 != live_digest { - debug!("object digest mismatch: {:?} vs {:?}", obj_ref, live_digest); return Err(SuiError::UserInputError { error: UserInputError::InvalidObjectDigest { object_id: obj_ref.0, @@ -156,14 +142,20 @@ impl ObjectLocks { fn clear_cached_locks(&self, locks: &[(ObjectRef, LockDetails)]) { for (obj_ref, lock) in locks { let entry = self.locked_transactions.entry(*obj_ref); - let occupied = match entry { - DashMapEntry::Vacant(_) => panic!("lock must exist"), + let mut occupied = match entry { + DashMapEntry::Vacant(_) => { + debug_fatal!("lock must exist for object: {:?}", obj_ref); + continue; + } DashMapEntry::Occupied(occupied) => occupied, }; - if occupied.get() == lock { - trace!("clearing lock: {:?}", lock); - occupied.remove(); + if occupied.get().1 == *lock { + occupied.get_mut().0 -= 1; + if occupied.get().0 == 0 { + trace!("clearing lock: {:?}", lock); + occupied.remove(); + } } else { // this is impossible because the only case in which we overwrite a // lock is when the lock is from a previous epoch. but we are holding diff --git a/crates/sui-core/src/execution_cache/passthrough_cache.rs b/crates/sui-core/src/execution_cache/passthrough_cache.rs index c60abb4a98f00..1518bda18a0d6 100644 --- a/crates/sui-core/src/execution_cache/passthrough_cache.rs +++ b/crates/sui-core/src/execution_cache/passthrough_cache.rs @@ -76,6 +76,14 @@ impl PassthroughCache { }) .ok(); } + + fn bulk_insert_genesis_objects_impl(&self, objects: &[Object]) -> SuiResult { + self.store.bulk_insert_genesis_objects(objects) + } + + fn insert_genesis_object_impl(&self, object: Object) -> SuiResult { + self.store.insert_genesis_object(object) + } } impl ObjectCacheRead for PassthroughCache { diff --git a/crates/sui-core/src/execution_cache/unit_tests/writeback_cache_tests.rs b/crates/sui-core/src/execution_cache/unit_tests/writeback_cache_tests.rs index 4dd4894c244d8..5df5de61ca47f 100644 --- a/crates/sui-core/src/execution_cache/unit_tests/writeback_cache_tests.rs +++ b/crates/sui-core/src/execution_cache/unit_tests/writeback_cache_tests.rs @@ -384,7 +384,7 @@ impl Scenario { }; let id = o.id(); // genesis objects are not managed by Scenario, ignore them - if reverse_id_map.get(&id).is_some() { + if reverse_id_map.contains_key(&id) { self.objects.insert(id, o); } }); @@ -1145,6 +1145,79 @@ async fn test_concurrent_lockers() { } } +#[tokio::test(flavor = "multi_thread", worker_threads = 8)] +async fn test_concurrent_lockers_same_tx() { + telemetry_subscribers::init_for_testing(); + + let mut s = Scenario::new(None, Arc::new(AtomicU32::new(0))).await; + let cache = s.cache.clone(); + let mut txns = Vec::new(); + + for i in 0..1000 { + let a = i * 4; + let b = i * 4 + 1; + s.with_created(&[a, b]); + s.do_tx().await; + + let a_ref = s.obj_ref(a); + let b_ref = s.obj_ref(b); + + let tx1 = s.take_outputs(); + + let tx1 = s.make_signed_transaction(&tx1.transaction); + + txns.push((tx1, a_ref, b_ref)); + } + + let barrier = Arc::new(tokio::sync::Barrier::new(2)); + + let t1 = { + let txns = txns.clone(); + let cache = cache.clone(); + let barrier = barrier.clone(); + let epoch_store = s.epoch_store.clone(); + tokio::task::spawn(async move { + let mut results = Vec::new(); + for (tx1, a_ref, b_ref) in txns { + results.push( + cache + .acquire_transaction_locks(&epoch_store, &[a_ref, b_ref], tx1) + .await, + ); + barrier.wait().await; + } + results + }) + }; + + let t2 = { + let txns = txns.clone(); + let cache = cache.clone(); + let barrier = barrier.clone(); + let epoch_store = s.epoch_store.clone(); + tokio::task::spawn(async move { + let mut results = Vec::new(); + for (tx1, a_ref, b_ref) in txns { + results.push( + cache + .acquire_transaction_locks(&epoch_store, &[a_ref, b_ref], tx1) + .await, + ); + barrier.wait().await; + } + results + }) + }; + + let results1 = t1.await.unwrap(); + let results2 = t2.await.unwrap(); + + for (r1, r2) in results1.into_iter().zip(results2) { + assert!(r1.is_ok()); + assert!(r2.is_ok()); + } +} + #[tokio::test] async fn latest_object_cache_race_test() { let authority = TestAuthorityBuilder::new().build().await; diff --git a/crates/sui-core/src/execution_cache/writeback_cache.rs b/crates/sui-core/src/execution_cache/writeback_cache.rs index 2b770f740d50a..8431429b683db 100644 --- a/crates/sui-core/src/execution_cache/writeback_cache.rs +++ b/crates/sui-core/src/execution_cache/writeback_cache.rs @@ -460,7 +460,7 @@ impl WritebackCache { version: SequenceNumber, object: ObjectEntry, ) { - debug!(?object_id, ?version, ?object, "inserting object entry"); + trace!(?object_id, ?version, ?object, "inserting object entry"); fail_point_async!("write_object_entry"); self.metrics.record_cache_write("object"); self.dirty @@ -1146,16 +1146,33 @@ impl WritebackCache { self.packages.invalidate(object_id); } self.cached.object_by_id_cache.invalidate(object_id); + self.cached.object_cache.invalidate(object_id); } for ObjectKey(object_id, _) in outputs.deleted.iter().chain(outputs.wrapped.iter()) { self.cached.object_by_id_cache.invalidate(object_id); + self.cached.object_cache.invalidate(object_id); } // Note: individual object entries are removed when clear_state_end_of_epoch_impl is called Ok(()) } + fn bulk_insert_genesis_objects_impl(&self, objects: &[Object]) -> SuiResult { + self.store.bulk_insert_genesis_objects(objects)?; + for obj in objects { + self.cached.object_cache.invalidate(&obj.id()); + self.cached.object_by_id_cache.invalidate(&obj.id()); + } + Ok(()) + } + + fn insert_genesis_object_impl(&self, object: Object) -> SuiResult { + self.cached.object_by_id_cache.invalidate(&object.id()); + self.cached.object_cache.invalidate(&object.id()); + self.store.insert_genesis_object(object) + } + pub fn clear_caches_and_assert_empty(&self) { info!("clearing caches"); self.cached.clear_and_assert_empty(); diff --git a/crates/sui-core/src/execution_driver.rs b/crates/sui-core/src/execution_driver.rs index d43ee31adf129..ca433a8a4d383 100644 --- a/crates/sui-core/src/execution_driver.rs +++ b/crates/sui-core/src/execution_driver.rs @@ -107,7 +107,8 @@ pub async fn execution_process( authority.metrics.execution_rate_tracker.lock().record(); // Certificate execution can take significant time, so run it in a separate task. - spawn_monitored_task!(async move { + let epoch_store_clone = epoch_store.clone(); + spawn_monitored_task!(epoch_store.within_alive_epoch(async move { let _scope = monitored_scope("ExecutionDriver::task"); let _guard = permit; if let Ok(true) = authority.is_tx_already_executed(&digest) { @@ -118,7 +119,7 @@ pub async fn execution_process( fail_point_async!("transaction_execution_delay"); attempts += 1; let res = authority - .try_execute_immediately(&certificate, expected_effects_digest, &epoch_store) + .try_execute_immediately(&certificate, expected_effects_digest, &epoch_store_clone) .await; if let Err(e) = res { if attempts == EXECUTION_MAX_ATTEMPTS { @@ -136,6 +137,6 @@ pub async fn execution_process( .metrics .execution_driver_executed_transactions .inc(); - }.instrument(error_span!("execution_driver", tx_digest = ?digest))); + }.instrument(error_span!("execution_driver", tx_digest = ?digest)))); } } diff --git a/crates/sui-core/src/generate_format.rs b/crates/sui-core/src/generate_format.rs index 3d81953f3f74d..21d00d1a10da3 100644 --- a/crates/sui-core/src/generate_format.rs +++ b/crates/sui-core/src/generate_format.rs @@ -10,16 +10,27 @@ use move_core_types::language_storage::{ModuleId, StructTag, TypeTag}; use pretty_assertions::assert_str_eq; use rand::rngs::StdRng; use rand::SeedableRng; +use roaring::RoaringBitmap; use serde_reflection::{Registry, Result, Samples, Tracer, TracerConfig}; use shared_crypto::intent::{Intent, IntentMessage, PersonalMessage}; use std::str::FromStr; use std::{fs::File, io::Write}; +use sui_types::base_types::SuiAddress; +use sui_types::crypto::{ + AggregateAuthoritySignature, AuthorityQuorumSignInfo, AuthorityStrongQuorumSignInfo, +}; +use sui_types::effects::TransactionEvents; +use sui_types::event::Event; use sui_types::execution_status::{ CommandArgumentError, ExecutionFailureStatus, ExecutionStatus, PackageUpgradeError, TypeArgumentError, }; +use sui_types::full_checkpoint_content::{CheckpointData, CheckpointTransaction}; +use sui_types::messages_checkpoint::CertifiedCheckpointSummary; use sui_types::messages_grpc::ObjectInfoRequestKind; use sui_types::move_package::TypeOrigin; +use sui_types::object::Object; +use sui_types::transaction::{SenderSignedData, TransactionData}; use sui_types::type_input::{StructInput, TypeInput}; use sui_types::{ base_types::MoveObjectType_, @@ -66,6 +77,9 @@ fn get_registry() -> Result { let m = ModuleId::new(AccountAddress::ZERO, Identifier::new("foo").unwrap()); tracer.trace_value(&mut samples, &m).unwrap(); + tracer + .trace_value(&mut samples, &Identifier::new("foo").unwrap()) + .unwrap(); let (addr, kp): (_, AuthorityKeyPair) = get_key_pair(); let (s_addr, s_kp): (_, AccountKeyPair) = get_key_pair(); @@ -221,6 +235,47 @@ fn get_registry() -> Result { tracer.trace_type::(&samples).unwrap(); tracer.trace_type::(&samples).unwrap(); + let sender_data = SenderSignedData::new( + TransactionData::new_with_gas_coins( + TransactionKind::EndOfEpochTransaction(Vec::new()), + SuiAddress::ZERO, + Vec::new(), + 0, + 0, + ), + Vec::new(), + ); + tracer.trace_value(&mut samples, &sender_data).unwrap(); + + let quorum_sig: AuthorityStrongQuorumSignInfo = AuthorityQuorumSignInfo { + epoch: 0, + signature: AggregateAuthoritySignature::default(), + signers_map: RoaringBitmap::default(), + }; + tracer.trace_value(&mut samples, &quorum_sig).unwrap(); + + tracer + .trace_type::(&samples) + .unwrap(); + + let event = Event { + package_id: ObjectID::random(), + transaction_module: Identifier::new("foo").unwrap(), + sender: SuiAddress::ZERO, + type_: struct_tag.clone(), + contents: vec![0], + }; + tracer.trace_value(&mut samples, &event).unwrap(); + + tracer.trace_type::(&samples).unwrap(); + + tracer.trace_type::(&samples).unwrap(); + tracer + .trace_type::(&samples) + .unwrap(); + + tracer.trace_type::(&samples).unwrap(); + tracer.registry() } diff --git a/crates/sui-storage/src/indexes.rs b/crates/sui-core/src/jsonrpc_index.rs similarity index 82% rename from crates/sui-storage/src/indexes.rs rename to crates/sui-core/src/jsonrpc_index.rs index 7241d473d907d..220990d11eda9 100644 --- a/crates/sui-storage/src/indexes.rs +++ b/crates/sui-core/src/jsonrpc_index.rs @@ -5,7 +5,7 @@ //! The main user of this data is the explorer. use std::cmp::{max, min}; -use std::collections::{HashMap, HashSet}; +use std::collections::{BTreeMap, HashMap, HashSet}; use std::path::{Path, PathBuf}; use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::Arc; @@ -17,9 +17,9 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use tokio::sync::OwnedMutexGuard; use typed_store::TypedStoreError; -use crate::mutex_table::MutexTable; -use crate::sharded_lru::ShardedLruCache; use sui_json_rpc_types::{SuiObjectDataFilter, TransactionFilter}; +use sui_storage::mutex_table::MutexTable; +use sui_storage::sharded_lru::ShardedLruCache; use sui_types::base_types::{ ObjectDigest, ObjectID, SequenceNumber, SuiAddress, TransactionDigest, TxSequenceNumber, }; @@ -31,8 +31,9 @@ use sui_types::error::{SuiError, SuiResult, UserInputError}; use sui_types::inner_temporary_store::TxCoins; use sui_types::object::{Object, Owner}; use sui_types::parse_sui_struct_tag; +use sui_types::storage::error::Error as StorageError; use tokio::task::spawn_blocking; -use tracing::{debug, trace}; +use tracing::{debug, info, instrument, trace}; use typed_store::rocks::{ default_db_options, read_size_from_env, DBBatch, DBMap, DBOptions, MetricConf, }; @@ -40,13 +41,70 @@ use typed_store::traits::Map; use typed_store::traits::{TableSummary, TypedStoreDebug}; use typed_store::DBMapUtils; +use crate::authority::AuthorityStore; +use crate::par_index_live_object_set::{LiveObjectIndexer, ParMakeLiveObjectIndexer}; + type OwnerIndexKey = (SuiAddress, ObjectID); -pub type CoinIndexKey = (SuiAddress, String, ObjectID); +type CoinIndexKey = (SuiAddress, String, ObjectID); type DynamicFieldKey = (ObjectID, ObjectID); type EventId = (TxSequenceNumber, usize); type EventIndex = (TransactionEventsDigest, TransactionDigest, u64); type AllBalance = HashMap; +#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct CoinIndexKey2 { + pub owner: SuiAddress, + pub coin_type: String, + // the balance of the coin inverted `!coin.balance` in order to force sorting of coins to be + // from greatest to least + pub inverted_balance: u64, + pub object_id: ObjectID, +} + +impl CoinIndexKey2 { + pub fn new_from_cursor( + owner: SuiAddress, + coin_type: String, + inverted_balance: u64, + object_id: ObjectID, + ) -> Self { + Self { + owner, + coin_type, + inverted_balance, + object_id, + } + } + + pub fn new(owner: SuiAddress, coin_type: String, balance: u64, object_id: ObjectID) -> Self { + Self { + owner, + coin_type, + inverted_balance: !balance, + object_id, + } + } +} + +const CURRENT_DB_VERSION: u64 = 0; +const CURRENT_COIN_INDEX_VERSION: u64 = 1; + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +struct MetadataInfo { + /// Version of the Database + version: u64, + /// Version of each of the column families + /// + /// This is used to version individual column families to determine if a CF needs to be + /// (re)initialized on startup. + column_families: BTreeMap, +} + +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] +struct ColumnFamilyInfo { + version: u64, +} + pub const MAX_TX_RANGE_SIZE: u64 = 4096; pub const MAX_GET_OWNED_OBJECT_SIZE: usize = 256; @@ -128,7 +186,7 @@ impl IndexStoreMetrics { pub struct IndexStoreCaches { per_coin_type_balance: ShardedLruCache<(SuiAddress, TypeTag), SuiResult>, all_balances: ShardedLruCache>>>, - pub locks: MutexTable, + locks: MutexTable, } #[derive(Default)] @@ -140,6 +198,14 @@ pub struct IndexStoreCacheUpdates { #[derive(DBMapUtils)] pub struct IndexStoreTables { + /// A singleton that store metadata information on the DB. + /// + /// A few uses for this singleton: + /// - determining if the DB has been initialized (as some tables could still be empty post + /// initialization) + /// - version of each column family and their respective initialization status + meta: DBMap<(), MetadataInfo>, + /// Index from sui address to transactions initiated by that address. #[default_options_override_fn = "transactions_from_addr_table_default_config"] transactions_from_addr: DBMap<(SuiAddress, TxSequenceNumber), TransactionDigest>, @@ -188,7 +254,11 @@ pub struct IndexStoreTables { owner_index: DBMap, #[default_options_override_fn = "coin_index_table_default_config"] + #[deprecated] + #[allow(unused)] coin_index: DBMap, + #[default_options_override_fn = "coin_index_table_default_config"] + coin_index_2: DBMap, /// This is an index of object references to currently existing dynamic field object, indexed by the /// composite key of the object ID of their parent and the object ID of the dynamic field object. @@ -199,6 +269,7 @@ pub struct IndexStoreTables { /// This is an index of all the versions of loaded child objects #[deprecated] + #[allow(unused)] loaded_child_object_versions: DBMap>, #[default_options_override_fn = "index_table_default_config"] @@ -220,15 +291,64 @@ impl IndexStoreTables { &self.owner_index } - pub fn coin_index(&self) -> &DBMap { - &self.coin_index + pub fn coin_index(&self) -> &DBMap { + &self.coin_index_2 + } + + #[allow(deprecated)] + fn init(&mut self, authority_store: &AuthorityStore) -> Result<(), StorageError> { + let mut metadata = { + match self.meta.get(&()) { + Ok(Some(metadata)) => metadata, + Ok(None) | Err(_) => MetadataInfo { + version: CURRENT_DB_VERSION, + column_families: BTreeMap::new(), + }, + } + }; + + // If the new coin index hasn't already been initialized, populate it + if !metadata + .column_families + .get(self.coin_index_2.cf_name()) + .is_some_and(|cf_info| cf_info.version == CURRENT_COIN_INDEX_VERSION) + || self.coin_index_2.is_empty() + { + info!("Initializing JSON-RPC coin index"); + + // clear the index so we're starting with a fresh table + self.coin_index_2.unsafe_clear()?; + + let make_live_object_indexer = CoinParLiveObjectSetIndexer { tables: self }; + + crate::par_index_live_object_set::par_index_live_object_set( + authority_store, + &make_live_object_indexer, + )?; + + info!("Finished initializing JSON-RPC coin index"); + + // Once the coin_index_2 table has been finished, update the metadata indicating that + // its been initialized + metadata.column_families.insert( + self.coin_index_2.cf_name().to_owned(), + ColumnFamilyInfo { + version: CURRENT_COIN_INDEX_VERSION, + }, + ); + } + + // Commit to the DB that the indexes have been initialized + self.meta.insert(&(), &metadata)?; + + Ok(()) } } pub struct IndexStore { next_sequence_number: AtomicU64, tables: IndexStoreTables, - pub caches: IndexStoreCaches, + caches: IndexStoreCaches, metrics: Arc, max_type_length: u64, remove_deprecated_tables: bool, @@ -274,7 +394,7 @@ fn coin_index_table_default_config() -> DBOptions { } impl IndexStore { - pub fn new( + pub fn new_without_init( path: PathBuf, registry: &Registry, max_type_length: Option, @@ -287,6 +407,7 @@ impl IndexStore { None, remove_deprecated_tables, ); + let metrics = IndexStoreMetrics::new(registry); let caches = IndexStoreCaches { per_coin_type_balance: ShardedLruCache::new(1_000_000, 1000), @@ -312,10 +433,24 @@ impl IndexStore { } } + pub fn new( + path: PathBuf, + registry: &Registry, + max_type_length: Option, + remove_deprecated_tables: bool, + authority_store: &AuthorityStore, + ) -> Self { + let mut store = + Self::new_without_init(path, registry, max_type_length, remove_deprecated_tables); + store.tables.init(authority_store).unwrap(); + store + } + pub fn tables(&self) -> &IndexStoreTables { &self.tables } + #[instrument(skip_all)] pub async fn index_coin( &self, digest: &TransactionDigest, @@ -348,84 +483,90 @@ impl IndexStore { HashMap::new(); // Index coin info let (input_coins, written_coins) = tx_coins.unwrap(); - // 1. Delete old owner if the object is deleted or transferred to a new owner, - // by looking at `object_index_changes.deleted_owners`. - // Objects in `deleted_owners` must be coin type (see `AuthorityState::commit_certificate`). - let coin_delete_keys = object_index_changes - .deleted_owners - .iter() - .filter_map(|(owner, obj_id)| { - let object = input_coins.get(obj_id).or(written_coins.get(obj_id))?; - let coin_type_tag = object.coin_type_maybe().unwrap_or_else(|| { - panic!( - "object_id: {:?} is not a coin type, input_coins: {:?}, written_coins: {:?}, tx_digest: {:?}", - obj_id, input_coins, written_coins, digest - ) - }); + + // 1. Remove old coins from the DB by looking at the set of input coin objects + let coin_delete_keys = input_coins + .values() + .filter_map(|object| { + // only process address owned coins + let Owner::AddressOwner(owner) = object.owner() else { + return None; + }; + + // only process coin types + let (coin_type, coin) = object + .coin_type_maybe() + .and_then(|coin_type| object.as_coin_maybe().map(|coin| (coin_type, coin)))?; + + let key = CoinIndexKey2::new( + *owner, + coin_type.to_string(), + coin.balance.value(), + object.id(), + ); + let map = balance_changes.entry(*owner).or_default(); - let entry = map.entry(coin_type_tag.clone()).or_insert(TotalBalance { + let entry = map.entry(coin_type).or_insert(TotalBalance { num_coins: 0, - balance: 0 + balance: 0, }); - if let Ok(Some(coin_info)) = &self.tables.coin_index.get(&(*owner, coin_type_tag.to_string(), *obj_id)) { - entry.num_coins -= 1; - entry.balance -= coin_info.balance as i128; - } - Some((*owner, coin_type_tag.to_string(), *obj_id)) - }).collect::>(); + entry.num_coins -= 1; + entry.balance -= coin.balance.value() as i128; + + Some(key) + }) + .collect::>(); trace!( tx_digset=?digest, "coin_delete_keys: {:?}", coin_delete_keys, ); - batch.delete_batch(&self.tables.coin_index, coin_delete_keys.into_iter())?; - - // 2. Upsert new owner, by looking at `object_index_changes.new_owners`. - // For a object to appear in `new_owners`, it must be owned by `Owner::Address` after the tx. - // It also must not be deleted, hence appear in written_coins (see `AuthorityState::commit_certificate`) - // It also must be a coin type (see `AuthorityState::commit_certificate`). - // Here the coin could be transferred to a new address, to simply have the metadata changed (digest, balance etc) - // due to a successful or failed transaction. - let coin_add_keys = object_index_changes - .new_owners - .iter() - .filter_map(|((owner, obj_id), obj_info)| { - // If it's in written_coins, then it's not a coin. Skip it. - let obj = written_coins.get(obj_id)?; - let coin_type_tag = obj.coin_type_maybe().unwrap_or_else(|| { - panic!( - "object_id: {:?} in written_coins is not a coin type, written_coins: {:?}, tx_digest: {:?}", - obj_id, written_coins, digest - ) - }); - let coin = obj.as_coin_maybe().unwrap_or_else(|| { - panic!( - "object_id: {:?} in written_coins cannot be deserialzied as a Coin, written_coins: {:?}, tx_digest: {:?}", - obj_id, written_coins, digest - ) - }); - let map = balance_changes.entry(*owner).or_default(); - let entry = map.entry(coin_type_tag.clone()).or_insert(TotalBalance { - num_coins: 0, - balance: 0 - }); - let result = self.tables.coin_index.get(&(*owner, coin_type_tag.to_string(), *obj_id)); - if let Ok(Some(coin_info)) = &result { - entry.balance -= coin_info.balance as i128; - entry.balance += coin.balance.value() as i128; - } else if let Ok(None) = &result { + batch.delete_batch(&self.tables.coin_index_2, coin_delete_keys)?; + + // 2. Insert new coins, or new versions of coins, by looking at `written_coins`. + let coin_add_keys = written_coins + .values() + .filter_map(|object| { + // only process address owned coins + let Owner::AddressOwner(owner) = object.owner() else { + return None; + }; + + // only process coin types + let (coin_type, coin) = object + .coin_type_maybe() + .and_then(|coin_type| object.as_coin_maybe().map(|coin| (coin_type, coin)))?; + + let key = CoinIndexKey2::new( + *owner, + coin_type.to_string(), + coin.balance.value(), + object.id(), + ); + let value = CoinInfo { + version: object.version(), + digest: object.digest(), + balance: coin.balance.value(), + previous_transaction: object.previous_transaction, + }; + let map = balance_changes.entry(*owner).or_default(); + let entry = map.entry(coin_type).or_insert(TotalBalance { + num_coins: 0, + balance: 0, + }); entry.num_coins += 1; entry.balance += coin.balance.value() as i128; - } - Some(((*owner, coin_type_tag.to_string(), *obj_id), (CoinInfo {version: obj_info.version, digest: obj_info.digest, balance: coin.balance.value(), previous_transaction: *digest}))) - }).collect::>(); + + Some((key, value)) + }) + .collect::>(); trace!( tx_digset=?digest, "coin_add_keys: {:?}", coin_add_keys, ); - batch.insert_batch(&self.tables.coin_index, coin_add_keys.into_iter())?; + batch.insert_batch(&self.tables.coin_index_2, coin_add_keys)?; let per_coin_type_balance_changes: Vec<_> = balance_changes .iter() @@ -455,6 +596,7 @@ impl IndexStore { Ok(cache_updates) } + #[instrument(skip_all)] pub async fn index_tx( &self, sender: SuiAddress, @@ -644,6 +786,7 @@ impl IndexStore { self.next_sequence_number.load(Ordering::SeqCst) + 1 } + #[instrument(skip(self))] pub fn get_transactions( &self, filter: Option, @@ -714,6 +857,7 @@ impl IndexStore { } } + #[instrument(skip_all)] fn get_transactions_from_index( index: &DBMap<(KeyT, TxSequenceNumber), TransactionDigest>, key: KeyT, @@ -751,6 +895,7 @@ impl IndexStore { }) } + #[instrument(skip(self))] pub fn get_transactions_by_input_object( &self, input_object: ObjectID, @@ -771,6 +916,7 @@ impl IndexStore { ) } + #[instrument(skip(self))] pub fn get_transactions_by_mutated_object( &self, mutated_object: ObjectID, @@ -791,6 +937,7 @@ impl IndexStore { ) } + #[instrument(skip(self))] pub fn get_transactions_from_addr( &self, addr: SuiAddress, @@ -807,6 +954,7 @@ impl IndexStore { ) } + #[instrument(skip(self))] pub fn get_transactions_by_move_function( &self, package: ObjectID, @@ -890,6 +1038,7 @@ impl IndexStore { }) } + #[instrument(skip(self))] pub fn get_transactions_to_addr( &self, addr: SuiAddress, @@ -906,6 +1055,7 @@ impl IndexStore { ) } + #[instrument(skip(self))] pub fn get_transaction_seq( &self, digest: &TransactionDigest, @@ -913,6 +1063,7 @@ impl IndexStore { Ok(self.tables.transactions_seq.get(digest)?) } + #[instrument(skip(self))] pub fn all_events( &self, tx_seq: TxSequenceNumber, @@ -944,6 +1095,7 @@ impl IndexStore { }) } + #[instrument(skip(self))] pub fn events_by_transaction( &self, digest: &TransactionDigest, @@ -981,6 +1133,7 @@ impl IndexStore { }) } + #[instrument(skip_all)] fn get_event_from_index( index: &DBMap<(KeyT, EventId), (TransactionEventsDigest, TransactionDigest, u64)>, key: &KeyT, @@ -1013,6 +1166,7 @@ impl IndexStore { }) } + #[instrument(skip(self))] pub fn events_by_module_id( &self, module: &ModuleId, @@ -1031,6 +1185,7 @@ impl IndexStore { ) } + #[instrument(skip(self))] pub fn events_by_move_event_struct_name( &self, struct_name: &StructTag, @@ -1049,6 +1204,7 @@ impl IndexStore { ) } + #[instrument(skip(self))] pub fn events_by_move_event_module( &self, module_id: &ModuleId, @@ -1067,6 +1223,7 @@ impl IndexStore { ) } + #[instrument(skip(self))] pub fn events_by_sender( &self, sender: &SuiAddress, @@ -1085,6 +1242,7 @@ impl IndexStore { ) } + #[instrument(skip(self))] pub fn event_iterator( &self, start_time: u64, @@ -1141,6 +1299,7 @@ impl IndexStore { .map_ok(|((_, c), object_info)| (c, object_info))) } + #[instrument(skip(self))] pub fn get_dynamic_field_object_id( &self, object: ObjectID, @@ -1193,6 +1352,7 @@ impl IndexStore { Ok(None) } + #[instrument(skip(self))] pub fn get_owner_objects( &self, owner: SuiAddress, @@ -1211,56 +1371,63 @@ impl IndexStore { } pub fn get_owned_coins_iterator( - coin_index: &DBMap, + coin_index: &DBMap, owner: SuiAddress, coin_type_tag: Option, - ) -> SuiResult + '_> { + ) -> SuiResult + '_> { let all_coins = coin_type_tag.is_none(); let starting_coin_type = coin_type_tag.unwrap_or_else(|| String::from_utf8([0u8].to_vec()).unwrap()); + let start_key = + CoinIndexKey2::new(owner, starting_coin_type.clone(), u64::MAX, ObjectID::ZERO); Ok(coin_index .unbounded_iter() - .skip_to(&(owner, starting_coin_type.clone(), ObjectID::ZERO))? - .take_while(move |((addr, coin_type, _), _)| { - if addr != &owner { + .skip_to(&start_key)? + .take_while(move |(key, _)| { + if key.owner != owner { return false; } - if !all_coins && &starting_coin_type != coin_type { + if !all_coins && starting_coin_type != key.coin_type { return false; } true - }) - .map(|((_, coin_type, obj_id), coin)| (coin_type, obj_id, coin))) + })) } pub fn get_owned_coins_iterator_with_cursor( &self, owner: SuiAddress, - cursor: (String, ObjectID), + cursor: (String, u64, ObjectID), limit: usize, one_coin_type_only: bool, - ) -> SuiResult + '_> { - let (starting_coin_type, starting_object_id) = cursor; + ) -> SuiResult + '_> { + let (starting_coin_type, inverted_balance, starting_object_id) = cursor; + let start_key = CoinIndexKey2::new_from_cursor( + owner, + starting_coin_type.clone(), + inverted_balance, + starting_object_id, + ); Ok(self .tables - .coin_index + .coin_index_2 .unbounded_iter() - .skip_to(&(owner, starting_coin_type.clone(), starting_object_id))? - .filter(move |((_, _, obj_id), _)| obj_id != &starting_object_id) + .skip_to(&start_key)? + .filter(move |(key, _)| key.object_id != starting_object_id) .enumerate() - .take_while(move |(index, ((addr, coin_type, _), _))| { + .take_while(move |(index, (key, _))| { if *index >= limit { return false; } - if addr != &owner { + if key.owner != owner { return false; } - if one_coin_type_only && &starting_coin_type != coin_type { + if one_coin_type_only && starting_coin_type != key.coin_type { return false; } true }) - .map(|(_, ((_, coin_type, obj_id), coin))| (coin_type, obj_id, coin))) + .map(|(_index, (key, info))| (key, info))) } /// starting_object_id can be used to implement pagination, where a client remembers the last @@ -1319,6 +1486,7 @@ impl IndexStore { /// gets the balance for passed in `coin_type` from the `all_balance` cache. Only on the second /// cache miss, we go to the database (expensive) and update the cache. Notice that db read is /// done with `spawn_blocking` as that is expected to block + #[instrument(skip(self))] pub async fn get_balance( &self, owner: SuiAddress, @@ -1327,7 +1495,7 @@ impl IndexStore { let force_disable_cache = read_size_from_env(ENV_VAR_DISABLE_INDEX_CACHE).unwrap_or(0) > 0; let cloned_coin_type = coin_type.clone(); let metrics_cloned = self.metrics.clone(); - let coin_index_cloned = self.tables.coin_index.clone(); + let coin_index_cloned = self.tables.coin_index_2.clone(); if force_disable_cache { return spawn_blocking(move || { Self::get_balance_from_db( @@ -1363,7 +1531,7 @@ impl IndexStore { } let cloned_coin_type = coin_type.clone(); let metrics_cloned = self.metrics.clone(); - let coin_index_cloned = self.tables.coin_index.clone(); + let coin_index_cloned = self.tables.coin_index_2.clone(); self.caches .per_coin_type_balance .get_with((owner, coin_type), async move { @@ -1389,13 +1557,14 @@ impl IndexStore { /// sense that it not only serves `get_AllBalance()` calls but is also used for serving /// `get_Balance()` queries. Notice that db read is performed with `spawn_blocking` as that is /// expected to block + #[instrument(skip(self))] pub async fn get_all_balance( &self, owner: SuiAddress, ) -> SuiResult>> { let force_disable_cache = read_size_from_env(ENV_VAR_DISABLE_INDEX_CACHE).unwrap_or(0) > 0; let metrics_cloned = self.metrics.clone(); - let coin_index_cloned = self.tables.coin_index.clone(); + let coin_index_cloned = self.tables.coin_index_2.clone(); if force_disable_cache { return spawn_blocking(move || { Self::get_all_balances_from_db(metrics_cloned, coin_index_cloned, owner) @@ -1409,7 +1578,7 @@ impl IndexStore { self.metrics.all_balance_lookup_from_total.inc(); let metrics_cloned = self.metrics.clone(); - let coin_index_cloned = self.tables.coin_index.clone(); + let coin_index_cloned = self.tables.coin_index_2.clone(); self.caches .all_balances .get_with(owner, async move { @@ -1426,21 +1595,21 @@ impl IndexStore { } /// Read balance for a `SuiAddress` and `CoinType` from the backend database + #[instrument(skip_all)] pub fn get_balance_from_db( metrics: Arc, - coin_index: DBMap, + coin_index: DBMap, owner: SuiAddress, coin_type: TypeTag, ) -> SuiResult { metrics.balance_lookup_from_db.inc(); let coin_type_str = coin_type.to_string(); let coins = - Self::get_owned_coins_iterator(&coin_index, owner, Some(coin_type_str.clone()))? - .map(|(_coin_type, obj_id, coin)| (coin_type_str.clone(), obj_id, coin)); + Self::get_owned_coins_iterator(&coin_index, owner, Some(coin_type_str.clone()))?; let mut balance = 0i128; let mut num_coins = 0; - for (_coin_type, _obj_id, coin_info) in coins { + for (_key, coin_info) in coins { balance += coin_info.balance as i128; num_coins += 1; } @@ -1448,19 +1617,20 @@ impl IndexStore { } /// Read all balances for a `SuiAddress` from the backend database + #[instrument(skip_all)] pub fn get_all_balances_from_db( metrics: Arc, - coin_index: DBMap, + coin_index: DBMap, owner: SuiAddress, ) -> SuiResult>> { metrics.all_balance_lookup_from_db.inc(); let mut balances: HashMap = HashMap::new(); let coins = Self::get_owned_coins_iterator(&coin_index, owner, None)? - .chunk_by(|(coin_type, _obj_id, _coin)| coin_type.clone()); + .chunk_by(|(key, _coin)| key.coin_type.clone()); for (coin_type, coins) in &coins { let mut total_balance = 0i128; let mut coin_object_count = 0; - for (_coin_type, _obj_id, coin_info) in coins { + for (_key, coin_info) in coins { total_balance += coin_info.balance as i128; coin_object_count += 1; } @@ -1572,10 +1742,75 @@ impl IndexStore { } } +struct CoinParLiveObjectSetIndexer<'a> { + tables: &'a IndexStoreTables, +} + +struct CoinLiveObjectIndexer<'a> { + tables: &'a IndexStoreTables, + batch: typed_store::rocks::DBBatch, +} + +impl<'a> ParMakeLiveObjectIndexer for CoinParLiveObjectSetIndexer<'a> { + type ObjectIndexer = CoinLiveObjectIndexer<'a>; + + fn make_live_object_indexer(&self) -> Self::ObjectIndexer { + CoinLiveObjectIndexer { + tables: self.tables, + batch: self.tables.coin_index_2.batch(), + } + } +} + +impl<'a> LiveObjectIndexer for CoinLiveObjectIndexer<'a> { + fn index_object(&mut self, object: Object) -> Result<(), StorageError> { + let Owner::AddressOwner(owner) = object.owner() else { + return Ok(()); + }; + + // only process coin types + let Some((coin_type, coin)) = object + .coin_type_maybe() + .and_then(|coin_type| object.as_coin_maybe().map(|coin| (coin_type, coin))) + else { + return Ok(()); + }; + + let key = CoinIndexKey2::new( + *owner, + coin_type.to_string(), + coin.balance.value(), + object.id(), + ); + let value = CoinInfo { + version: object.version(), + digest: object.digest(), + balance: coin.balance.value(), + previous_transaction: object.previous_transaction, + }; + + self.batch + .insert_batch(&self.tables.coin_index_2, [(key, value)])?; + + // If the batch size grows to greater that 128MB then write out to the DB so that the + // data we need to hold in memory doesn't grown unbounded. + if self.batch.size_in_bytes() >= 1 << 27 { + std::mem::replace(&mut self.batch, self.tables.coin_index_2.batch()).write()?; + } + + Ok(()) + } + + fn finish(self) -> Result<(), StorageError> { + self.batch.write()?; + Ok(()) + } +} + #[cfg(test)] mod tests { - use crate::indexes::ObjectIndexChanges; - use crate::IndexStore; + use super::IndexStore; + use super::ObjectIndexChanges; use move_core_types::account_address::AccountAddress; use prometheus::Registry; use std::collections::BTreeMap; @@ -1596,9 +1831,11 @@ mod tests { // and verified from both db and cache. // This tests make sure we are invalidating entries in the cache and always reading latest // balance. - let index_store = IndexStore::new(temp_dir(), &Registry::default(), Some(128), false); + let index_store = + IndexStore::new_without_init(temp_dir(), &Registry::default(), Some(128), false); let address: SuiAddress = AccountAddress::random().into(); let mut written_objects = BTreeMap::new(); + let mut input_objects = BTreeMap::new(); let mut object_map = BTreeMap::new(); let mut new_objects = vec![]; @@ -1625,7 +1862,7 @@ mod tests { new_dynamic_fields: vec![], }; - let tx_coins = (object_map.clone(), written_objects.clone()); + let tx_coins = (input_objects.clone(), written_objects.clone()); index_store .index_tx( address, @@ -1642,7 +1879,7 @@ mod tests { let balance_from_db = IndexStore::get_balance_from_db( index_store.metrics.clone(), - index_store.tables.coin_index.clone(), + index_store.tables.coin_index_2.clone(), address, GAS::type_tag(), )?; @@ -1661,15 +1898,15 @@ mod tests { let mut deleted_objects = vec![]; for (id, object) in object_map.iter().take(3) { deleted_objects.push((address, *id)); - written_objects.insert(object.data.id(), object.clone()); + input_objects.insert(*id, object.to_owned()); } let object_index_changes = ObjectIndexChanges { - deleted_owners: deleted_objects, + deleted_owners: deleted_objects.clone(), deleted_dynamic_fields: vec![], new_owners: vec![], new_dynamic_fields: vec![], }; - let tx_coins = (object_map, written_objects); + let tx_coins = (input_objects, written_objects); index_store .index_tx( address, @@ -1685,7 +1922,7 @@ mod tests { .await?; let balance_from_db = IndexStore::get_balance_from_db( index_store.metrics.clone(), - index_store.tables.coin_index.clone(), + index_store.tables.coin_index_2.clone(), address, GAS::type_tag(), )?; diff --git a/crates/sui-core/src/lib.rs b/crates/sui-core/src/lib.rs index b610e94a7188c..2260f210a509c 100644 --- a/crates/sui-core/src/lib.rs +++ b/crates/sui-core/src/lib.rs @@ -19,12 +19,14 @@ pub mod db_checkpoint_handler; pub mod epoch; pub mod execution_cache; mod execution_driver; +pub mod jsonrpc_index; pub mod metrics; #[cfg(any(test, feature = "test-utils"))] pub mod mock_consensus; pub mod module_cache_metrics; pub mod mysticeti_adapter; pub mod overload_monitor; +mod par_index_live_object_set; pub(crate) mod post_consensus_tx_reorder; pub mod quorum_driver; pub mod rest_index; diff --git a/crates/sui-core/src/par_index_live_object_set.rs b/crates/sui-core/src/par_index_live_object_set.rs new file mode 100644 index 0000000000000..0d505afc10bd0 --- /dev/null +++ b/crates/sui-core/src/par_index_live_object_set.rs @@ -0,0 +1,104 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::authority::authority_store_tables::LiveObject; +use crate::authority::AuthorityStore; +use std::time::Instant; +use sui_types::base_types::ObjectID; +use sui_types::object::Object; +use sui_types::storage::error::Error as StorageError; +use tracing::info; + +/// Make `LiveObjectIndexer`s for parallel indexing of the live object set +pub trait ParMakeLiveObjectIndexer: Sync { + type ObjectIndexer: LiveObjectIndexer; + + fn make_live_object_indexer(&self) -> Self::ObjectIndexer; +} + +/// Represents an instance of a indexer that operates on a subset of the live object set +pub trait LiveObjectIndexer { + /// Called on each object in the range of the live object set this indexer task is responsible + /// for. + fn index_object(&mut self, object: Object) -> Result<(), StorageError>; + + /// Called once the range of objects this indexer task is responsible for have been processed + /// by calling `index_object`. + fn finish(self) -> Result<(), StorageError>; +} + +/// Utility for iterating over, and indexing, the live object set in parallel +/// +/// This is done by dividing the addressable ObjectID space into smaller, disjoint sets and +/// operating on each set in parallel in a separate thread. User's will need to implement the +/// `ParMakeLiveObjectIndexer` trait which will be used to make N `LiveObjectIndexer`s which will +/// then process one of the disjoint parts of the live object set. +pub fn par_index_live_object_set( + authority_store: &AuthorityStore, + make_indexer: &T, +) -> Result<(), StorageError> { + info!("Indexing Live Object Set"); + let start_time = Instant::now(); + std::thread::scope(|s| -> Result<(), StorageError> { + let mut threads = Vec::new(); + const BITS: u8 = 5; + for index in 0u8..(1 << BITS) { + threads.push(s.spawn(move || { + let object_indexer = make_indexer.make_live_object_indexer(); + live_object_set_index_task(index, BITS, authority_store, object_indexer) + })); + } + + // join threads + for thread in threads { + thread.join().unwrap()?; + } + + Ok(()) + })?; + + info!( + "Indexing Live Object Set took {} seconds", + start_time.elapsed().as_secs() + ); + + Ok(()) +} + +fn live_object_set_index_task( + task_id: u8, + bits: u8, + authority_store: &AuthorityStore, + mut object_indexer: T, +) -> Result<(), StorageError> { + let mut id_bytes = [0; ObjectID::LENGTH]; + id_bytes[0] = task_id << (8 - bits); + let start_id = ObjectID::new(id_bytes); + + id_bytes[0] |= (1 << (8 - bits)) - 1; + for element in id_bytes.iter_mut().skip(1) { + *element = u8::MAX; + } + let end_id = ObjectID::new(id_bytes); + + let mut object_scanned: u64 = 0; + for object in authority_store + .perpetual_tables + .range_iter_live_object_set(Some(start_id), Some(end_id), false) + .filter_map(LiveObject::to_normal) + { + object_scanned += 1; + if object_scanned % 2_000_000 == 0 { + info!( + "[Index] Task {}: object scanned: {}", + task_id, object_scanned + ); + } + + object_indexer.index_object(object)? + } + + object_indexer.finish()?; + + Ok(()) +} diff --git a/crates/sui-core/src/rest_index.rs b/crates/sui-core/src/rest_index.rs index 7e16553172beb..20766d1855baf 100644 --- a/crates/sui-core/src/rest_index.rs +++ b/crates/sui-core/src/rest_index.rs @@ -2,9 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::authority::authority_per_epoch_store::AuthorityPerEpochStore; -use crate::authority::authority_store_tables::LiveObject; use crate::authority::AuthorityStore; use crate::checkpoints::CheckpointStore; +use crate::par_index_live_object_set::LiveObjectIndexer; +use crate::par_index_live_object_set::ParMakeLiveObjectIndexer; use move_core_types::language_storage::StructTag; use rayon::iter::IntoParallelIterator; use rayon::iter::ParallelIterator; @@ -20,7 +21,7 @@ use sui_types::base_types::ObjectID; use sui_types::base_types::SequenceNumber; use sui_types::base_types::SuiAddress; use sui_types::digests::TransactionDigest; -use sui_types::dynamic_field::{DynamicFieldInfo, DynamicFieldType}; +use sui_types::dynamic_field::visitor as DFV; use sui_types::full_checkpoint_content::CheckpointData; use sui_types::layout_resolver::LayoutResolver; use sui_types::messages_checkpoint::CheckpointContents; @@ -223,41 +224,20 @@ impl IndexStoreTables { let coin_index = Mutex::new(HashMap::new()); - info!("Indexing Live Object Set"); - let start_time = Instant::now(); - std::thread::scope(|s| -> Result<(), StorageError> { - let mut threads = Vec::new(); - const BITS: u8 = 5; - for index in 0u8..(1 << BITS) { - let this = &self; - let coin_index = &coin_index; - threads.push(s.spawn(move || { - this.live_object_set_index_task( - index, - BITS, - authority_store, - coin_index, - epoch_store, - package_store, - ) - })); - } - - // join threads - for thread in threads { - thread.join().unwrap()?; - } + let make_live_object_indexer = RestParLiveObjectSetIndexer { + tables: self, + coin_index: &coin_index, + epoch_store, + package_store, + }; - Ok(()) - })?; + crate::par_index_live_object_set::par_index_live_object_set( + authority_store, + &make_live_object_indexer, + )?; self.coin.multi_insert(coin_index.into_inner().unwrap())?; - info!( - "Indexing Live Object Set took {} seconds", - start_time.elapsed().as_secs() - ); - self.meta.insert( &(), &MetadataInfo { @@ -270,92 +250,6 @@ impl IndexStoreTables { Ok(()) } - fn live_object_set_index_task( - &self, - task_id: u8, - bits: u8, - authority_store: &AuthorityStore, - coin_index: &Mutex>, - epoch_store: &AuthorityPerEpochStore, - package_store: &Arc, - ) -> Result<(), StorageError> { - let mut id_bytes = [0; ObjectID::LENGTH]; - id_bytes[0] = task_id << (8 - bits); - let start_id = ObjectID::new(id_bytes); - - id_bytes[0] |= (1 << (8 - bits)) - 1; - for element in id_bytes.iter_mut().skip(1) { - *element = u8::MAX; - } - let end_id = ObjectID::new(id_bytes); - - let mut resolver = epoch_store - .executor() - .type_layout_resolver(Box::new(package_store)); - let mut batch = self.owner.batch(); - let mut object_scanned: u64 = 0; - for object in authority_store - .perpetual_tables - .range_iter_live_object_set(Some(start_id), Some(end_id), false) - .filter_map(LiveObject::to_normal) - { - object_scanned += 1; - if object_scanned % 2_000_000 == 0 { - info!( - "[Index] Task {}: object scanned: {}", - task_id, object_scanned - ); - } - match object.owner { - // Owner Index - Owner::AddressOwner(owner) => { - let owner_key = OwnerIndexKey::new(owner, object.id()); - let owner_info = OwnerIndexInfo::new(&object); - batch.insert_batch(&self.owner, [(owner_key, owner_info)])?; - } - - // Dynamic Field Index - Owner::ObjectOwner(parent) => { - if let Some(field_info) = - try_create_dynamic_field_info(&object, resolver.as_mut())? - { - let field_key = DynamicFieldKey::new(parent, object.id()); - - batch.insert_batch(&self.dynamic_field, [(field_key, field_info)])?; - } - } - - Owner::Shared { .. } | Owner::Immutable => {} - } - - // Look for CoinMetadata and TreasuryCap objects - if let Some((key, value)) = try_create_coin_index_info(&object) { - use std::collections::hash_map::Entry; - - match coin_index.lock().unwrap().entry(key) { - Entry::Occupied(o) => { - let (key, v) = o.remove_entry(); - let value = value.merge(v); - batch.insert_batch(&self.coin, [(key, value)])?; - } - Entry::Vacant(v) => { - v.insert(value); - } - } - } - - // If the batch size grows to greater that 256MB then write out to the DB so that the - // data we need to hold in memory doesn't grown unbounded. - if batch.size_in_bytes() >= 1 << 28 { - batch.write()?; - batch = self.owner.batch(); - } - } - - batch.write()?; - Ok(()) - } - /// Prune data from this Index fn prune( &self, @@ -671,45 +565,26 @@ fn try_create_dynamic_field_info( return Ok(None); } - let (name_value, dynamic_field_type, object_id) = { - let layout = sui_types::layout_resolver::into_struct_layout( - resolver - .get_annotated_layout(&move_object.type_().clone().into()) - .map_err(StorageError::custom)?, - ) - .map_err(StorageError::custom)?; - - let move_struct = move_object - .to_move_struct(&layout) - .map_err(StorageError::serialization)?; - - // SAFETY: move struct has already been validated to be of type DynamicField - DynamicFieldInfo::parse_move_object(&move_struct).unwrap() - }; - - let name_type = move_object - .type_() - .try_extract_field_name(&dynamic_field_type) - .expect("object is of type Field"); + let layout = resolver + .get_annotated_layout(&move_object.type_().clone().into()) + .map_err(StorageError::custom)? + .into_layout(); - let name_value = name_value - .undecorate() - .simple_serialize() - .expect("serialization cannot fail"); - - let dynamic_object_id = match dynamic_field_type { - DynamicFieldType::DynamicObject => Some(object_id), - DynamicFieldType::DynamicField => None, - }; - - let field_info = DynamicFieldIndexInfo { - name_type, - name_value, - dynamic_field_type, - dynamic_object_id, - }; + let field = DFV::FieldVisitor::deserialize(move_object.contents(), &layout) + .map_err(StorageError::custom)?; - Ok(Some(field_info)) + let value_metadata = field.value_metadata().map_err(StorageError::custom)?; + + Ok(Some(DynamicFieldIndexInfo { + name_type: field.name_layout.into(), + name_value: field.name_bytes.to_owned(), + dynamic_field_type: field.kind, + dynamic_object_id: if let DFV::ValueMetadata::DynamicObjectField(id) = value_metadata { + Some(id) + } else { + None + }, + })) } fn try_create_coin_index_info(object: &Object) -> Option<(CoinIndexKey, CoinIndexInfo)> { @@ -746,3 +621,90 @@ fn try_create_coin_index_info(object: &Object) -> Option<(CoinIndexKey, CoinInde }) }) } + +struct RestParLiveObjectSetIndexer<'a> { + tables: &'a IndexStoreTables, + coin_index: &'a Mutex>, + epoch_store: &'a AuthorityPerEpochStore, + package_store: &'a Arc, +} + +struct RestLiveObjectIndexer<'a> { + tables: &'a IndexStoreTables, + batch: typed_store::rocks::DBBatch, + coin_index: &'a Mutex>, + resolver: Box, +} + +impl<'a> ParMakeLiveObjectIndexer for RestParLiveObjectSetIndexer<'a> { + type ObjectIndexer = RestLiveObjectIndexer<'a>; + + fn make_live_object_indexer(&self) -> Self::ObjectIndexer { + RestLiveObjectIndexer { + tables: self.tables, + batch: self.tables.owner.batch(), + coin_index: self.coin_index, + resolver: self + .epoch_store + .executor() + .type_layout_resolver(Box::new(self.package_store)), + } + } +} + +impl<'a> LiveObjectIndexer for RestLiveObjectIndexer<'a> { + fn index_object(&mut self, object: Object) -> Result<(), StorageError> { + match object.owner { + // Owner Index + Owner::AddressOwner(owner) => { + let owner_key = OwnerIndexKey::new(owner, object.id()); + let owner_info = OwnerIndexInfo::new(&object); + self.batch + .insert_batch(&self.tables.owner, [(owner_key, owner_info)])?; + } + + // Dynamic Field Index + Owner::ObjectOwner(parent) => { + if let Some(field_info) = + try_create_dynamic_field_info(&object, self.resolver.as_mut())? + { + let field_key = DynamicFieldKey::new(parent, object.id()); + + self.batch + .insert_batch(&self.tables.dynamic_field, [(field_key, field_info)])?; + } + } + + Owner::Shared { .. } | Owner::Immutable => {} + } + + // Look for CoinMetadata and TreasuryCap objects + if let Some((key, value)) = try_create_coin_index_info(&object) { + use std::collections::hash_map::Entry; + + match self.coin_index.lock().unwrap().entry(key) { + Entry::Occupied(o) => { + let (key, v) = o.remove_entry(); + let value = value.merge(v); + self.batch.insert_batch(&self.tables.coin, [(key, value)])?; + } + Entry::Vacant(v) => { + v.insert(value); + } + } + } + + // If the batch size grows to greater that 128MB then write out to the DB so that the + // data we need to hold in memory doesn't grown unbounded. + if self.batch.size_in_bytes() >= 1 << 27 { + std::mem::replace(&mut self.batch, self.tables.owner.batch()).write()?; + } + + Ok(()) + } + + fn finish(self) -> Result<(), StorageError> { + self.batch.write()?; + Ok(()) + } +} diff --git a/crates/sui-core/src/scoring_decision.rs b/crates/sui-core/src/scoring_decision.rs index 486da40c786bc..fd48e5bfddccb 100644 --- a/crates/sui-core/src/scoring_decision.rs +++ b/crates/sui-core/src/scoring_decision.rs @@ -4,10 +4,12 @@ use std::{collections::HashMap, sync::Arc}; use arc_swap::ArcSwap; use consensus_config::Committee as ConsensusCommittee; -use sui_types::{base_types::AuthorityName, committee::Committee}; +use sui_types::{ + base_types::AuthorityName, committee::Committee, messages_consensus::AuthorityIndex, +}; use tracing::debug; -use crate::{authority::AuthorityMetrics, consensus_types::AuthorityIndex}; +use crate::authority::AuthorityMetrics; /// Updates list of authorities that are deemed to have low reputation scores by consensus /// these may be lagging behind the network, byzantine, or not reliably participating for any reason. diff --git a/crates/sui-core/src/test_authority_clients.rs b/crates/sui-core/src/test_authority_clients.rs index a1480050e1c1f..f337f564673bc 100644 --- a/crates/sui-core/src/test_authority_clients.rs +++ b/crates/sui-core/src/test_authority_clients.rs @@ -219,7 +219,7 @@ impl LocalAuthorityClient { .await?; //let certificate = certificate.verify(epoch_store.committee())?; state.enqueue_certificates_for_execution(vec![certificate.clone()], &epoch_store); - let effects = state.notify_read_effects(&certificate).await?; + let effects = state.notify_read_effects(*certificate.digest()).await?; state.sign_effects(effects, &epoch_store)? } } diff --git a/crates/sui-core/src/test_utils.rs b/crates/sui-core/src/test_utils.rs index 190e30caedd60..c9fcc1033fe0c 100644 --- a/crates/sui-core/src/test_utils.rs +++ b/crates/sui-core/src/test_utils.rs @@ -85,9 +85,11 @@ pub async fn send_and_confirm_transaction( .epoch_store_for_testing() .protocol_config() .simplified_unwrap_then_delete(); - let mut state = state_acc.accumulate_live_object_set(include_wrapped_tombstone); + let mut state = + state_acc.accumulate_cached_live_object_set_for_testing(include_wrapped_tombstone); let (result, _execution_error_opt) = authority.try_execute_for_test(&certificate).await?; - let state_after = state_acc.accumulate_live_object_set(include_wrapped_tombstone); + let state_after = + state_acc.accumulate_cached_live_object_set_for_testing(include_wrapped_tombstone); let effects_acc = state_acc.accumulate_effects( vec![result.inner().data().clone()], epoch_store.protocol_config(), diff --git a/crates/sui-core/src/traffic_controller/mod.rs b/crates/sui-core/src/traffic_controller/mod.rs index 1dd940c5d4e04..cacec53b5cc7c 100644 --- a/crates/sui-core/src/traffic_controller/mod.rs +++ b/crates/sui-core/src/traffic_controller/mod.rs @@ -10,7 +10,7 @@ use dashmap::DashMap; use fs::File; use prometheus::IntGauge; use std::fs; -use std::net::{IpAddr, Ipv4Addr}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::ops::Add; use std::sync::Arc; @@ -39,10 +39,20 @@ struct Blocklists { proxied_clients: Blocklist, } +#[derive(Clone)] +enum Acl { + Blocklists(Blocklists), + /// If this variant is set, then we do no tallying or running + /// of background tasks, and instead simply block all IPs not + /// in the allowlist on calls to `check`. The allowlist should + /// only be populated once at initialization. + Allowlist(Vec), +} + #[derive(Clone)] pub struct TrafficController { - tally_channel: mpsc::Sender, - blocklists: Blocklists, + tally_channel: Option>, + acl: Acl, metrics: Arc, dry_run_mode: bool, } @@ -68,7 +78,33 @@ impl Debug for TrafficController { } impl TrafficController { - pub fn spawn( + pub fn init( + policy_config: PolicyConfig, + metrics: TrafficControllerMetrics, + fw_config: Option, + ) -> Self { + match policy_config.allow_list { + Some(allow_list) => { + let allowlist = allow_list + .into_iter() + .map(|ip_str| { + parse_ip(&ip_str).unwrap_or_else(|| { + panic!("Failed to parse allowlist IP address: {:?}", ip_str) + }) + }) + .collect(); + Self { + tally_channel: None, + acl: Acl::Allowlist(allowlist), + metrics: Arc::new(metrics), + dry_run_mode: policy_config.dry_run, + } + } + None => Self::spawn(policy_config, metrics, fw_config), + } + } + + fn spawn( policy_config: PolicyConfig, metrics: TrafficControllerMetrics, fw_config: Option, @@ -86,20 +122,15 @@ impl TrafficController { metrics .deadmans_switch_enabled .set(mem_drainfile_present as i64); - - let ret = Self { - tally_channel: tx, - blocklists: Blocklists { - clients: Arc::new(DashMap::new()), - proxied_clients: Arc::new(DashMap::new()), - }, - metrics: metrics.clone(), - dry_run_mode: policy_config.dry_run, + let blocklists = Blocklists { + clients: Arc::new(DashMap::new()), + proxied_clients: Arc::new(DashMap::new()), }; - let tally_loop_blocklists = ret.blocklists.clone(); - let clear_loop_blocklists = ret.blocklists.clone(); + let tally_loop_blocklists = blocklists.clone(); + let clear_loop_blocklists = blocklists.clone(); let tally_loop_metrics = metrics.clone(); let clear_loop_metrics = metrics.clone(); + let dry_run_mode = policy_config.dry_run; spawn_monitored_task!(run_tally_loop( rx, policy_config, @@ -112,74 +143,91 @@ impl TrafficController { clear_loop_blocklists, clear_loop_metrics, )); - ret + Self { + tally_channel: Some(tx), + acl: Acl::Blocklists(blocklists), + metrics: metrics.clone(), + dry_run_mode, + } } - pub fn spawn_for_test( + pub fn init_for_test( policy_config: PolicyConfig, fw_config: Option, ) -> Self { let metrics = TrafficControllerMetrics::new(&prometheus::Registry::new()); - Self::spawn(policy_config, metrics, fw_config) + Self::init(policy_config, metrics, fw_config) } pub fn tally(&self, tally: TrafficTally) { - // Use try_send rather than send mainly to avoid creating backpressure - // on the caller if the channel is full, which may slow down the critical - // path. Dropping the tally on the floor should be ok, as in this case - // we are effectively sampling traffic, which we would need to do anyway - // if we are overloaded - match self.tally_channel.try_send(tally) { - Err(TrySendError::Full(_)) => { - warn!("TrafficController tally channel full, dropping tally"); - self.metrics.tally_channel_overflow.inc(); - // TODO: once we've verified this doesn't happen under normal - // conditions, we can consider dropping the request itself given - // that clearly the system is overloaded - } - Err(TrySendError::Closed(_)) => { - panic!("TrafficController tally channel closed unexpectedly"); + if let Some(channel) = self.tally_channel.as_ref() { + // Use try_send rather than send mainly to avoid creating backpressure + // on the caller if the channel is full, which may slow down the critical + // path. Dropping the tally on the floor should be ok, as in this case + // we are effectively sampling traffic, which we would need to do anyway + // if we are overloaded + match channel.try_send(tally) { + Err(TrySendError::Full(_)) => { + warn!("TrafficController tally channel full, dropping tally"); + self.metrics.tally_channel_overflow.inc(); + // TODO: once we've verified this doesn't happen under normal + // conditions, we can consider dropping the request itself given + // that clearly the system is overloaded + } + Err(TrySendError::Closed(_)) => { + panic!("TrafficController tally channel closed unexpectedly"); + } + Ok(_) => {} } - Ok(_) => {} } } /// Handle check with dry-run mode considered pub async fn check(&self, client: &Option, proxied_client: &Option) -> bool { - match ( - self.check_impl(client, proxied_client).await, - self.dry_run_mode(), - ) { - // check succeeded - (true, _) => true, - // check failed while in dry-run mode - (false, true) => { - debug!( - "Dry run mode: Blocked request from client {:?}, proxied client: {:?}", - client, proxied_client - ); - self.metrics.num_dry_run_blocked_requests.inc(); - true + let check_with_dry_run_maybe = |allowed| -> bool { + match (allowed, self.dry_run_mode()) { + // check succeeded + (true, _) => true, + // check failed while in dry-run mode + (false, true) => { + debug!("Dry run mode: Blocked request from client {:?}", client); + self.metrics.num_dry_run_blocked_requests.inc(); + true + } + // check failed + (false, false) => false, + } + }; + + match &self.acl { + Acl::Allowlist(allowlist) => { + let allowed = client.is_none() || allowlist.contains(&client.unwrap()); + check_with_dry_run_maybe(allowed) + } + Acl::Blocklists(blocklists) => { + let allowed = self + .check_blocklists(blocklists, client, proxied_client) + .await; + check_with_dry_run_maybe(allowed) } - // check failed - (false, false) => false, } } - /// Returns true if the connection is allowed, false if it is blocked - pub async fn check_impl( + /// Returns true if the connection is in blocklist, false otherwise + async fn check_blocklists( &self, + blocklists: &Blocklists, client: &Option, proxied_client: &Option, ) -> bool { let client_check = self.check_and_clear_blocklist( client, - self.blocklists.clients.clone(), + blocklists.clients.clone(), &self.metrics.connection_ip_blocklist_len, ); let proxied_client_check = self.check_and_clear_blocklist( proxied_client, - self.blocklists.proxied_clients.clone(), + blocklists.proxied_clients.clone(), &self.metrics.proxy_ip_blocklist_len, ); let (client_check, proxied_client_check) = @@ -604,7 +652,7 @@ impl TrafficSim { assert!(per_client_tps > 0); assert!(duration.as_secs() > 0); - let controller = TrafficController::spawn_for_test(policy.clone(), None); + let controller = TrafficController::init_for_test(policy.clone(), None); let tasks = (0..num_clients).map(|task_num| { tokio::spawn(Self::run_single_client( controller.clone(), @@ -772,3 +820,15 @@ impl TrafficSim { ); } } + +pub fn parse_ip(ip: &str) -> Option { + ip.parse::().ok().or_else(|| { + ip.parse::() + .ok() + .map(|socket_addr| socket_addr.ip()) + .or_else(|| { + error!("Failed to parse value of {:?} to ip address or socket.", ip,); + None + }) + }) +} diff --git a/crates/sui-core/src/traffic_controller/nodefw_client.rs b/crates/sui-core/src/traffic_controller/nodefw_client.rs index d4fae4dc82210..ed60938f8e262 100644 --- a/crates/sui-core/src/traffic_controller/nodefw_client.rs +++ b/crates/sui-core/src/traffic_controller/nodefw_client.rs @@ -32,7 +32,7 @@ impl NodeFWClient { pub async fn block_addresses(&self, addresses: BlockAddresses) -> Result<(), reqwest::Error> { let response = self .client - .post(&format!("{}/block_addresses", self.remote_fw_url)) + .post(format!("{}/block_addresses", self.remote_fw_url)) .json(&addresses) .send() .await?; @@ -44,7 +44,7 @@ impl NodeFWClient { pub async fn list_addresses(&self) -> Result { self.client - .get(&format!("{}/list_addresses", self.remote_fw_url)) + .get(format!("{}/list_addresses", self.remote_fw_url)) .send() .await? .error_for_status()? diff --git a/crates/sui-core/src/transaction_manager.rs b/crates/sui-core/src/transaction_manager.rs index 368e259c2dbcd..0ffd0c7a92b51 100644 --- a/crates/sui-core/src/transaction_manager.rs +++ b/crates/sui-core/src/transaction_manager.rs @@ -863,7 +863,7 @@ impl TransactionManager { // Verify TM has no pending item for tests. #[cfg(test)] - fn check_empty_for_testing(&self) { + pub(crate) fn check_empty_for_testing(&self) { let reconfig_lock = self.inner.read(); let inner = reconfig_lock.read(); assert!( diff --git a/crates/sui-core/src/transaction_orchestrator.rs b/crates/sui-core/src/transaction_orchestrator.rs index c9b903f0762f5..9dd2af551bff3 100644 --- a/crates/sui-core/src/transaction_orchestrator.rs +++ b/crates/sui-core/src/transaction_orchestrator.rs @@ -31,16 +31,15 @@ use std::sync::Arc; use std::time::Duration; use sui_storage::write_path_pending_tx_log::WritePathPendingTransactionLog; use sui_types::base_types::TransactionDigest; -use sui_types::effects::{TransactionEffectsAPI, VerifiedCertifiedTransactionEffects}; use sui_types::error::{SuiError, SuiResult}; -use sui_types::executable_transaction::VerifiedExecutableTransaction; use sui_types::quorum_driver_types::{ ExecuteTransactionRequestType, ExecuteTransactionRequestV3, ExecuteTransactionResponseV3, FinalizedEffects, IsTransactionExecutedLocally, QuorumDriverEffectsQueueResult, QuorumDriverError, QuorumDriverResponse, QuorumDriverResult, }; use sui_types::sui_system_state::SuiSystemState; -use sui_types::transaction::VerifiedTransaction; +use sui_types::transaction::{TransactionData, VerifiedTransaction}; +use sui_types::transaction_executor::SimulateTransactionResult; use tokio::sync::broadcast::error::RecvError; use tokio::sync::broadcast::Receiver; use tokio::task::JoinHandle; @@ -118,8 +117,7 @@ where let pending_tx_log_clone = pending_tx_log.clone(); let _local_executor_handle = { spawn_monitored_task!(async move { - Self::loop_execute_finalized_tx_locally(effects_receiver, pending_tx_log_clone) - .await; + Self::loop_pending_transaction_log(effects_receiver, pending_tx_log_clone).await; }) }; Self::schedule_txes_in_log(pending_tx_log.clone(), quorum_driver_handler.clone()); @@ -161,15 +159,9 @@ where request_type, ExecuteTransactionRequestType::WaitForLocalExecution ) { - let executable_tx = VerifiedExecutableTransaction::new_from_quorum_execution( - transaction, - response.effects_cert.executed_epoch(), - ); - let executed_locally = Self::execute_finalized_tx_locally_with_timeout( + let executed_locally = Self::wait_for_finalized_tx_executed_locally_with_timeout( &self.validator_state, - &epoch_store, - &executable_tx, - &response.effects_cert, + &transaction, &self.metrics, ) .await @@ -350,27 +342,13 @@ where }) } - #[instrument(name = "tx_orchestrator_execute_finalized_tx_locally_with_timeout", level = "debug", skip_all, fields(tx_digest = ?transaction.digest()), err)] - async fn execute_finalized_tx_locally_with_timeout( + #[instrument(name = "tx_orchestrator_wait_for_finalized_tx_executed_locally_with_timeout", level = "debug", skip_all, fields(tx_digest = ?transaction.digest()), err)] + async fn wait_for_finalized_tx_executed_locally_with_timeout( validator_state: &Arc, - epoch_store: &Arc, - transaction: &VerifiedExecutableTransaction, - effects_cert: &VerifiedCertifiedTransactionEffects, + transaction: &VerifiedTransaction, metrics: &TransactionOrchestratorMetrics, ) -> SuiResult { - // TODO: attempt a finalized tx at most once per request. - // Every WaitForLocalExecution request will be attempted to execute twice, - // one from the subscriber queue, one from the proactive execution before - // returning results to clients. This is not insanely bad because: - // 1. it's possible that one attempt finishes before the other, so there's - // zero extra work except DB checks - // 2. an up-to-date fullnode should have minimal overhead to sync parents - // (for one extra time) - // 3. at the end of day, the tx will be executed at most once per lock guard. - let tx_digest = transaction.digest(); - if validator_state.is_tx_already_executed(tx_digest)? { - return Ok(()); - } + let tx_digest = *transaction.digest(); metrics.local_execution_in_flight.inc(); let _metrics_guard = scopeguard::guard(metrics.local_execution_in_flight.clone(), |in_flight| { @@ -382,14 +360,15 @@ where } else { metrics.local_execution_latency_single_writer.start_timer() }; - debug!(?tx_digest, "Executing finalized tx locally."); + debug!( + ?tx_digest, + "Waiting for finalized tx to be executed locally." + ); match timeout( LOCAL_EXECUTION_TIMEOUT, - validator_state.fullnode_execute_certificate_with_effects( - transaction, - effects_cert, - epoch_store, - ), + validator_state + .get_transaction_cache_reader() + .notify_read_executed_effects_digests(&[tx_digest]), ) .instrument(error_span!( "transaction_orchestrator::local_execution", @@ -400,7 +379,7 @@ where Err(_elapsed) => { debug!( ?tx_digest, - "Executing tx locally by orchestrator timed out within {:?}.", + "Waiting for finalized tx to be executed locally timed out within {:?}.", LOCAL_EXECUTION_TIMEOUT ); metrics.local_execution_timeout.inc(); @@ -409,7 +388,7 @@ where Ok(Err(err)) => { debug!( ?tx_digest, - "Executing tx locally by orchestrator failed with error: {:?}", err + "Waiting for finalized tx to be executed locally failed with error: {:?}", err ); metrics.local_execution_failure.inc(); Err(SuiError::TransactionOrchestratorLocalExecutionError { @@ -423,7 +402,8 @@ where } } - async fn loop_execute_finalized_tx_locally( + // TODO: Potentially cleanup this function and pending transaction log. + async fn loop_pending_transaction_log( mut effects_receiver: Receiver, pending_transaction_log: Arc, ) { @@ -731,4 +711,11 @@ where ) -> Result { self.execute_transaction_v3(request, client_addr).await } + + fn simulate_transaction( + &self, + transaction: TransactionData, + ) -> Result { + self.validator_state.simulate_transaction(transaction) + } } diff --git a/crates/sui-core/src/unit_tests/authority_aggregator_tests.rs b/crates/sui-core/src/unit_tests/authority_aggregator_tests.rs index ff43d0cac5a3e..e6aa4d72ffed7 100644 --- a/crates/sui-core/src/unit_tests/authority_aggregator_tests.rs +++ b/crates/sui-core/src/unit_tests/authority_aggregator_tests.rs @@ -504,11 +504,7 @@ async fn test_map_reducer() { |mut accumulated_state, authority_name, _authority_weight, _result| { Box::pin(async move { accumulated_state.insert(authority_name); - if accumulated_state.len() <= 3 { - ReduceOutput::Continue(accumulated_state) - } else { - ReduceOutput::ContinueWithTimeout(accumulated_state, Duration::from_millis(10)) - } + ReduceOutput::Continue(accumulated_state) }) }, // large delay @@ -1355,6 +1351,50 @@ async fn test_handle_transaction_response() { ) .await; + println!("Case 8.3 - Retryable Transaction (EpochEnded Error)"); + + set_tx_info_response_with_signed_tx(&mut clients, &authority_keys, &tx, 0); + + // 2 out 4 validators return epoch ended error + for (name, _) in authority_keys.iter().skip(2) { + clients + .get_mut(name) + .unwrap() + .set_tx_info_response_error(SuiError::EpochEnded(0)); + } + let agg = get_genesis_agg(authorities.clone(), clients.clone()); + assert_resp_err( + &agg, + tx.clone().into(), + |e| { + matches!( + e, + AggregatorProcessTransactionError::RetryableTransaction { .. } + ) + }, + |e| matches!(e, SuiError::EpochEnded(0)), + ) + .await; + + println!("Case 8.4 - Retryable Transaction (EpochEnded Error) eventually succeeds"); + + set_tx_info_response_with_signed_tx(&mut clients, &authority_keys, &tx, 0); + + // 1 out 4 validators return epoch ended error + for (name, _) in authority_keys.iter().take(1) { + clients + .get_mut(name) + .unwrap() + .set_tx_info_response_error(SuiError::EpochEnded(0)); + } + + let agg = get_genesis_agg(authorities.clone(), clients.clone()); + let cert = agg + .process_transaction(tx.clone().into(), Some(client_ip)) + .await + .unwrap(); + matches!(cert, ProcessTransactionResult::Certified { .. }); + println!("Case 9 - Non-Retryable Transaction (>=2f+1 ObjectNotFound Error)"); // >= 2f+1 object not found errors set_retryable_tx_info_response_error(&mut clients, &authority_keys); diff --git a/crates/sui-core/src/unit_tests/authority_tests.rs b/crates/sui-core/src/unit_tests/authority_tests.rs index 3ea94cb10bfe7..70ec3e454c5de 100644 --- a/crates/sui-core/src/unit_tests/authority_tests.rs +++ b/crates/sui-core/src/unit_tests/authority_tests.rs @@ -3363,23 +3363,25 @@ async fn test_store_revert_transfer_sui() { .await .unwrap(); - let db = &authority_state.database_for_testing(); - db.revert_state_update(&tx_digest).unwrap(); + let cache = authority_state.get_object_cache_reader(); + let tx_cache = authority_state.get_transaction_cache_reader(); + let reconfig_api = authority_state.get_reconfig_api(); + reconfig_api.revert_state_update(&tx_digest).unwrap(); + reconfig_api + .clear_state_end_of_epoch(&authority_state.execution_lock_for_reconfiguration().await); assert_eq!( - db.get_object(&gas_object_id).unwrap().unwrap().owner, + cache.get_object(&gas_object_id).unwrap().unwrap().owner, Owner::AddressOwner(sender), ); assert_eq!( - db.get_latest_object_ref_or_tombstone(gas_object_id) + cache + .get_latest_object_ref_or_tombstone(gas_object_id) .unwrap() .unwrap(), gas_object_ref ); - // Transaction should not be deleted on revert in case it's needed - // to execute a future state sync checkpoint. - assert!(db.get_transaction_block(&tx_digest).unwrap().is_some()); - assert!(!db.is_tx_already_executed(&tx_digest).unwrap()); + assert!(!tx_cache.is_tx_already_executed(&tx_digest).unwrap()); } #[tokio::test] @@ -3400,6 +3402,15 @@ async fn test_store_revert_wrap_move_call() { .await .unwrap(); + authority_state + .get_cache_commit() + .commit_transaction_outputs( + authority_state.epoch_store_for_testing().epoch(), + &[*create_effects.transaction_digest()], + ) + .await + .unwrap(); + assert!(create_effects.status().is_ok()); assert_eq!(create_effects.created().len(), 1); @@ -3436,18 +3447,21 @@ async fn test_store_revert_wrap_move_call() { let wrapper_v0 = wrap_effects.created()[0].0; - let db = &authority_state.database_for_testing(); - db.revert_state_update(&wrap_digest).unwrap(); + let cache = &authority_state.get_object_cache_reader(); + let reconfig_api = authority_state.get_reconfig_api(); + reconfig_api.revert_state_update(&wrap_digest).unwrap(); + reconfig_api + .clear_state_end_of_epoch(&authority_state.execution_lock_for_reconfiguration().await); // The wrapped object is unwrapped once again (accessible from storage). - let object = db.get_object(&object_v0.0).unwrap().unwrap(); + let object = cache.get_object(&object_v0.0).unwrap().unwrap(); assert_eq!(object.version(), object_v0.1); // The wrapper doesn't exist - assert!(db.get_object(&wrapper_v0.0).unwrap().is_none()); + assert!(cache.get_object(&wrapper_v0.0).unwrap().is_none()); // The gas is uncharged - let gas = db.get_object(&gas_object_id).unwrap().unwrap(); + let gas = cache.get_object(&gas_object_id).unwrap().unwrap(); assert_eq!(gas.version(), create_effects.gas_object().0 .1); } @@ -3485,6 +3499,18 @@ async fn test_store_revert_unwrap_move_call() { .await .unwrap(); + authority_state + .get_cache_commit() + .commit_transaction_outputs( + authority_state.epoch_store_for_testing().epoch(), + &[ + *create_effects.transaction_digest(), + *wrap_effects.transaction_digest(), + ], + ) + .await + .unwrap(); + assert!(wrap_effects.status().is_ok()); assert_eq!(wrap_effects.created().len(), 1); assert_eq!(wrap_effects.wrapped().len(), 1); @@ -3522,21 +3548,25 @@ async fn test_store_revert_unwrap_move_call() { assert_eq!(unwrap_effects.unwrapped().len(), 1); assert_eq!(unwrap_effects.unwrapped()[0].0 .0, object_v0.0); - let db = &authority_state.database_for_testing(); + let cache = &authority_state.get_object_cache_reader(); + let reconfig_api = authority_state.get_reconfig_api(); - db.revert_state_update(&unwrap_digest).unwrap(); + reconfig_api.revert_state_update(&unwrap_digest).unwrap(); + reconfig_api + .clear_state_end_of_epoch(&authority_state.execution_lock_for_reconfiguration().await); // The unwrapped object is wrapped once again - assert!(db.get_object(&object_v0.0).unwrap().is_none()); + assert!(cache.get_object(&object_v0.0).unwrap().is_none()); // The wrapper exists - let wrapper = db.get_object(&wrapper_v0.0).unwrap().unwrap(); + let wrapper = cache.get_object(&wrapper_v0.0).unwrap().unwrap(); assert_eq!(wrapper.version(), wrapper_v0.1); // The gas is uncharged - let gas = db.get_object(&gas_object_id).unwrap().unwrap(); + let gas = cache.get_object(&gas_object_id).unwrap().unwrap(); assert_eq!(gas.version(), wrap_effects.gas_object().0 .1); } + #[tokio::test] async fn test_store_get_dynamic_object() { let (_, fields) = create_and_retrieve_df_info(ident_str!("add_ofield")).await; @@ -3743,6 +3773,18 @@ async fn test_store_revert_add_ofield() { let outer_v0 = create_outer_effects.created()[0].0; let inner_v0 = create_inner_effects.created()[0].0; + authority_state + .get_cache_commit() + .commit_transaction_outputs( + authority_state.epoch_store_for_testing().epoch(), + &[ + *create_outer_effects.transaction_digest(), + *create_inner_effects.transaction_digest(), + ], + ) + .await + .unwrap(); + let add_txn = to_sender_signed_transaction( TransactionData::new_move_call( sender, @@ -3777,27 +3819,31 @@ async fn test_store_revert_add_ofield() { let outer_v1 = find_by_id(&add_effects.mutated(), outer_v0.0).unwrap(); let inner_v1 = find_by_id(&add_effects.mutated(), inner_v0.0).unwrap(); - let db = &authority_state.database_for_testing(); + let cache = authority_state.get_object_cache_reader(); + let reconfig_api = &authority_state.get_reconfig_api(); - let outer = db.get_object(&outer_v0.0).unwrap().unwrap(); + let outer = cache.get_object(&outer_v0.0).unwrap().unwrap(); assert_eq!(outer.version(), outer_v1.1); - let field = db.get_object(&field_v0.0).unwrap().unwrap(); + let field = cache.get_object(&field_v0.0).unwrap().unwrap(); assert_eq!(field.owner, Owner::ObjectOwner(outer_v0.0.into())); - let inner = db.get_object(&inner_v0.0).unwrap().unwrap(); + let inner = cache.get_object(&inner_v0.0).unwrap().unwrap(); assert_eq!(inner.version(), inner_v1.1); assert_eq!(inner.owner, Owner::ObjectOwner(field_v0.0.into())); - db.revert_state_update(&add_digest).unwrap(); + reconfig_api.revert_state_update(&add_digest).unwrap(); + + reconfig_api + .clear_state_end_of_epoch(&authority_state.execution_lock_for_reconfiguration().await); - let outer = db.get_object(&outer_v0.0).unwrap().unwrap(); + let outer = cache.get_object(&outer_v0.0).unwrap().unwrap(); assert_eq!(outer.version(), outer_v0.1); // Field no longer exists - assert!(db.get_object(&field_v0.0).unwrap().is_none()); + assert!(cache.get_object(&field_v0.0).unwrap().is_none()); - let inner = db.get_object(&inner_v0.0).unwrap().unwrap(); + let inner = cache.get_object(&inner_v0.0).unwrap().unwrap(); assert_eq!(inner.version(), inner_v0.1); assert_eq!(inner.owner, Owner::AddressOwner(sender)); } @@ -3854,6 +3900,19 @@ async fn test_store_revert_remove_ofield() { assert!(add_effects.status().is_ok()); assert_eq!(add_effects.created().len(), 1); + authority_state + .get_cache_commit() + .commit_transaction_outputs( + authority_state.epoch_store_for_testing().epoch(), + &[ + *create_outer_effects.transaction_digest(), + *create_inner_effects.transaction_digest(), + *add_effects.transaction_digest(), + ], + ) + .await + .unwrap(); + let field_v0 = add_effects.created()[0].0; let outer_v1 = find_by_id(&add_effects.mutated(), outer_v0.0).unwrap(); let inner_v1 = find_by_id(&add_effects.mutated(), inner_v0.0).unwrap(); @@ -3889,24 +3948,29 @@ async fn test_store_revert_remove_ofield() { let outer_v2 = find_by_id(&remove_effects.mutated(), outer_v0.0).unwrap(); let inner_v2 = find_by_id(&remove_effects.mutated(), inner_v0.0).unwrap(); - let db = &authority_state.database_for_testing(); + let cache = &authority_state.get_object_cache_reader(); + let reconfig_api = &authority_state.get_reconfig_api(); - let outer = db.get_object(&outer_v0.0).unwrap().unwrap(); + let outer = cache.get_object(&outer_v0.0).unwrap().unwrap(); assert_eq!(outer.version(), outer_v2.1); - let inner = db.get_object(&inner_v0.0).unwrap().unwrap(); + let inner = cache.get_object(&inner_v0.0).unwrap().unwrap(); assert_eq!(inner.owner, Owner::AddressOwner(sender)); assert_eq!(inner.version(), inner_v2.1); - db.revert_state_update(&remove_ofield_digest).unwrap(); + reconfig_api + .revert_state_update(&remove_ofield_digest) + .unwrap(); + reconfig_api + .clear_state_end_of_epoch(&authority_state.execution_lock_for_reconfiguration().await); - let outer = db.get_object(&outer_v0.0).unwrap().unwrap(); + let outer = cache.get_object(&outer_v0.0).unwrap().unwrap(); assert_eq!(outer.version(), outer_v1.1); - let field = db.get_object(&field_v0.0).unwrap().unwrap(); + let field = cache.get_object(&field_v0.0).unwrap().unwrap(); assert_eq!(field.owner, Owner::ObjectOwner(outer_v0.0.into())); - let inner = db.get_object(&inner_v0.0).unwrap().unwrap(); + let inner = cache.get_object(&inner_v0.0).unwrap().unwrap(); assert_eq!(inner.owner, Owner::ObjectOwner(field_v0.0.into())); assert_eq!(inner.version(), inner_v1.1); } @@ -4693,7 +4757,10 @@ async fn test_shared_object_transaction_ok() { authority.try_execute_for_test(&certificate).await.unwrap(); // Ensure transaction effects are available. - authority.notify_read_effects(&certificate).await.unwrap(); + authority + .notify_read_effects(*certificate.digest()) + .await + .unwrap(); // Ensure shared object sequence number increased. let shared_object_version = authority @@ -5799,6 +5866,7 @@ async fn test_consensus_handler_per_object_congestion_control( PerObjectCongestionControlMode::None => unreachable!(), PerObjectCongestionControlMode::TotalGasBudget => 5, PerObjectCongestionControlMode::TotalTxCount => 2, + PerObjectCongestionControlMode::TotalGasBudgetWithCap => 5, }; let gas_objects_commit_1 = create_gas_objects(5 + non_congested_tx_count, sender); let gas_objects_commit_2 = create_gas_objects(non_congested_tx_count, sender); @@ -5824,6 +5892,15 @@ async fn test_consensus_handler_per_object_congestion_control( protocol_config .set_max_accumulated_txn_cost_per_object_in_mysticeti_commit_for_testing(2); } + PerObjectCongestionControlMode::TotalGasBudgetWithCap => { + protocol_config + .set_max_accumulated_txn_cost_per_object_in_narwhal_commit_for_testing(200_000_000); + protocol_config + .set_max_accumulated_txn_cost_per_object_in_mysticeti_commit_for_testing( + 200_000_000, + ); + protocol_config.set_gas_budget_based_txn_cost_cap_factor_for_testing(100_000_000); + } } protocol_config.set_max_deferral_rounds_for_congestion_control_for_testing(1000); // Set to a large number so that we don't hit this limit. let authority = TestAuthorityBuilder::new() @@ -6015,6 +6092,14 @@ async fn test_consensus_handler_per_object_congestion_control_using_tx_count() { .await; } +#[sim_test] +async fn test_consensus_handler_per_object_congestion_control_using_budget_with_cap() { + test_consensus_handler_per_object_congestion_control( + PerObjectCongestionControlMode::TotalGasBudgetWithCap, + ) + .await; +} + // Tests congestion control triggered transaction cancellation in consensus handler: // 1. Consensus handler cancels transactions that are deferred for too many rounds. // 2. Shared locks for cancelled transaction are set correctly. diff --git a/crates/sui-core/src/unit_tests/congestion_control_tests.rs b/crates/sui-core/src/unit_tests/congestion_control_tests.rs index 4890b2006644b..24a6defc2b407 100644 --- a/crates/sui-core/src/unit_tests/congestion_control_tests.rs +++ b/crates/sui-core/src/unit_tests/congestion_control_tests.rs @@ -301,6 +301,7 @@ async fn test_congestion_control_execution_cancellation() { SharedObjectCongestionTracker::new_with_initial_value_for_test( &[(shared_object_1.0, 10)], PerObjectCongestionControlMode::TotalGasBudget, + Some(1000), // Not used. ), ) }); diff --git a/crates/sui-core/src/unit_tests/consensus_tests.rs b/crates/sui-core/src/unit_tests/consensus_tests.rs index 59516fe5f318f..46247ae04edf1 100644 --- a/crates/sui-core/src/unit_tests/consensus_tests.rs +++ b/crates/sui-core/src/unit_tests/consensus_tests.rs @@ -7,20 +7,27 @@ use super::*; use crate::authority::{authority_tests::init_state_with_objects, AuthorityState}; use crate::checkpoints::CheckpointServiceNoop; use crate::consensus_handler::SequencedConsensusTransaction; +use fastcrypto::traits::KeyPair; use move_core_types::{account_address::AccountAddress, ident_str}; use narwhal_types::Transactions; use narwhal_types::TransactionsServer; use narwhal_types::{Empty, TransactionProto}; +use rand::rngs::StdRng; +use rand::SeedableRng; use sui_network::tonic; -use sui_types::crypto::deterministic_random_account_key; +use sui_types::crypto::{deterministic_random_account_key, AccountKeyPair}; +use sui_types::gas::GasCostSummary; +use sui_types::messages_checkpoint::{ + CheckpointContents, CheckpointSignatureMessage, CheckpointSummary, SignedCheckpointSummary, +}; use sui_types::multiaddr::Multiaddr; use sui_types::transaction::TEST_ONLY_GAS_UNIT_FOR_OBJECT_BASICS; -use sui_types::utils::to_sender_signed_transaction; +use sui_types::utils::{make_committee_key, to_sender_signed_transaction}; use sui_types::SUI_FRAMEWORK_PACKAGE_ID; use sui_types::{ - base_types::ObjectID, + base_types::{ExecutionDigests, ObjectID, SuiAddress}, object::Object, - transaction::{CallArg, CertifiedTransaction, ObjectArg, TransactionData}, + transaction::{CallArg, CertifiedTransaction, ObjectArg, TransactionData, VerifiedTransaction}, }; use tokio::sync::mpsc::channel; use tokio::sync::mpsc::{Receiver, Sender}; @@ -105,6 +112,70 @@ pub async fn test_certificates( certificates } +/// Fixture: creates a transaction using the specified gas and input objects. +pub async fn test_user_transaction( + authority: &AuthorityState, + sender: SuiAddress, + keypair: &AccountKeyPair, + gas_object: Object, + input_objects: Vec, +) -> VerifiedTransaction { + let epoch_store = authority.load_epoch_store_one_call_per_task(); + let rgp = epoch_store.reference_gas_price(); + + // Object digest may be different in genesis than originally generated. + let gas_object = authority + .get_object(&gas_object.id()) + .await + .unwrap() + .unwrap(); + let mut input_objs = vec![]; + for obj in input_objects { + input_objs.push(authority.get_object(&obj.id()).await.unwrap().unwrap()); + } + + let mut object_args: Vec<_> = input_objs + .into_iter() + .map(|obj| { + if obj.is_shared() { + ObjectArg::SharedObject { + id: obj.id(), + initial_shared_version: obj.version(), + mutable: true, + } + } else { + ObjectArg::ImmOrOwnedObject(obj.compute_object_reference()) + } + }) + .map(CallArg::Object) + .collect(); + object_args.extend(vec![ + CallArg::Pure(16u64.to_le_bytes().to_vec()), + CallArg::Pure(bcs::to_bytes(&AccountAddress::from(sender)).unwrap()), + ]); + + // Make a sample transaction. + let module = "object_basics"; + let function = "create"; + + let data = TransactionData::new_move_call( + sender, + SUI_FRAMEWORK_PACKAGE_ID, + ident_str!(module).to_owned(), + ident_str!(function).to_owned(), + /* type_args */ vec![], + gas_object.compute_object_reference(), + object_args, + rgp * TEST_ONLY_GAS_UNIT_FOR_OBJECT_BASICS, + rgp, + ) + .unwrap(); + + epoch_store + .verify_transaction(to_sender_signed_transaction(data, keypair)) + .unwrap() +} + pub fn make_consensus_adapter_for_test( state: Arc, process_via_checkpoint: HashSet, @@ -155,6 +226,11 @@ pub fn make_consensus_adapter_for_test( .await?, ); } + } else if let SequencedConsensusTransactionKey::External( + ConsensusTransactionKey::CheckpointSignature(_, checkpoint_sequence_number), + ) = tx.transaction.key() + { + epoch_store.notify_synced_checkpoint(checkpoint_sequence_number); } else { transactions.extend( epoch_store @@ -264,6 +340,58 @@ async fn submit_multiple_transactions_to_consensus_adapter() { .into_iter() .map(|certificate| ConsensusTransaction::new_certificate_message(&state.name, certificate)) .collect::>(); + + let waiter = adapter + .submit_batch( + &transactions, + Some(&epoch_store.get_reconfig_state_read_lock_guard()), + &epoch_store, + ) + .unwrap(); + waiter.await.unwrap(); +} + +#[tokio::test] +async fn submit_checkpoint_signature_to_consensus_adapter() { + telemetry_subscribers::init_for_testing(); + + let mut rng = StdRng::seed_from_u64(1_100); + let (keys, committee) = make_committee_key(&mut rng); + + // Initialize an authority + let state = init_state_with_objects(vec![]).await; + let epoch_store = state.epoch_store_for_testing(); + + // Make a new consensus adapter instance. + let adapter = make_consensus_adapter_for_test(state, HashSet::new(), false); + + let checkpoint_summary = CheckpointSummary::new( + &ProtocolConfig::get_for_max_version_UNSAFE(), + 1, + 2, + 10, + &CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]), + None, + GasCostSummary::default(), + None, + 100, + Vec::new(), + ); + + let authority_key = &keys[0]; + let authority = authority_key.public().into(); + let signed_checkpoint_summary = SignedCheckpointSummary::new( + committee.epoch, + checkpoint_summary, + authority_key, + authority, + ); + + let transactions = vec![ConsensusTransaction::new_checkpoint_signature_message( + CheckpointSignatureMessage { + summary: signed_checkpoint_summary, + }, + )]; let waiter = adapter .submit_batch( &transactions, diff --git a/crates/sui-core/src/unit_tests/move_package_management_tests.rs b/crates/sui-core/src/unit_tests/move_package_management_tests.rs index feb538ca0fb32..f18baef04d94d 100644 --- a/crates/sui-core/src/unit_tests/move_package_management_tests.rs +++ b/crates/sui-core/src/unit_tests/move_package_management_tests.rs @@ -50,7 +50,7 @@ async fn test_manage_package_update() { # @generated by Move, please check-in and do not edit manually. [move] - version = 2 + version = 3 manifest_digest = "919A5B078B47AD46674F36E1605578927D5BC4536A7646D78D1320A25DDD57CC" deps_digest = "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855" diff --git a/crates/sui-core/src/unit_tests/move_package_publish_tests.rs b/crates/sui-core/src/unit_tests/move_package_publish_tests.rs index 46701b411bdb5..84e619282e258 100644 --- a/crates/sui-core/src/unit_tests/move_package_publish_tests.rs +++ b/crates/sui-core/src/unit_tests/move_package_publish_tests.rs @@ -218,32 +218,32 @@ async fn test_generate_lock_file() { # @generated by Move, please check-in and do not edit manually. [move] - version = 2 + version = 3 manifest_digest = "4C5606BF71339416027A58BDB5BA2EF2F5E0929FCE98BAB8AFFCBC447AFE3A23" deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" dependencies = [ - { name = "Examples" }, - { name = "Sui" }, + { id = "Examples", name = "Examples" }, + { id = "Sui", name = "Sui" }, ] [[move.package]] - name = "Examples" + id = "Examples" source = { local = "../object_basics" } dependencies = [ - { name = "Sui" }, + { id = "Sui", name = "Sui" }, ] [[move.package]] - name = "MoveStdlib" + id = "MoveStdlib" source = { local = "../../../../../sui-framework/packages/move-stdlib" } [[move.package]] - name = "Sui" + id = "Sui" source = { local = "../../../../../sui-framework/packages/sui-framework" } dependencies = [ - { name = "MoveStdlib" }, + { id = "MoveStdlib", name = "MoveStdlib" }, ] [move.toolchain-version] diff --git a/crates/sui-core/src/unit_tests/mysticeti_manager_tests.rs b/crates/sui-core/src/unit_tests/mysticeti_manager_tests.rs index 00e8a77436624..27ed57af47b30 100644 --- a/crates/sui-core/src/unit_tests/mysticeti_manager_tests.rs +++ b/crates/sui-core/src/unit_tests/mysticeti_manager_tests.rs @@ -107,9 +107,26 @@ async fn test_mysticeti_manager() { // THEN assert!(manager.is_running().await); + let boot_counter = *manager.boot_counter.lock().await; + if i == 1 || i == 2 { + assert_eq!(boot_counter, 0); + } else { + assert_eq!(boot_counter, 1); + } + // Now try to shut it down sleep(Duration::from_secs(1)).await; + // Simulate a commit by bumping the handled commit index so we can ensure that boot counter increments only after the first run. + // Practically we want to simulate a case where consensus engine restarts when no commits have happened before for first run. + if i > 1 { + let monitor = manager + .consumer_monitor + .load_full() + .expect("A consumer monitor should have been initialised"); + monitor.set_highest_handled_commit(100); + } + // WHEN manager.shutdown().await; diff --git a/crates/sui-core/src/unit_tests/transaction_deny_tests.rs b/crates/sui-core/src/unit_tests/transaction_deny_tests.rs index c8c6ad7c04cb0..d0f9cb6bf2979 100644 --- a/crates/sui-core/src/unit_tests/transaction_deny_tests.rs +++ b/crates/sui-core/src/unit_tests/transaction_deny_tests.rs @@ -289,7 +289,7 @@ async fn test_package_denied() { let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); // Publish 3 packages, where b depends on c, and a depends on b. // Also upgrade c to c', and upgrade b to b' (which will start using c' instead of c as dependency). - let (package_c, cap_c) = publish_package_on_single_authority( + let (tx_c, (package_c, cap_c)) = publish_package_on_single_authority( &path.join("src/unit_tests/data/package_deny/c"), accounts[0].0, &accounts[0].1, @@ -300,7 +300,7 @@ async fn test_package_denied() { ) .await .unwrap(); - let (package_b, cap_b) = publish_package_on_single_authority( + let (tx_b, (package_b, cap_b)) = publish_package_on_single_authority( &path.join("src/unit_tests/data/package_deny/b"), accounts[0].0, &accounts[0].1, @@ -311,7 +311,7 @@ async fn test_package_denied() { ) .await .unwrap(); - let (package_a, cap_a) = publish_package_on_single_authority( + let (tx_a, (package_a, cap_a)) = publish_package_on_single_authority( &path.join("src/unit_tests/data/package_deny/a"), accounts[0].0, &accounts[0].1, @@ -322,7 +322,7 @@ async fn test_package_denied() { ) .await .unwrap(); - let package_c_prime = upgrade_package_on_single_authority( + let (tx_c_prime, package_c_prime) = upgrade_package_on_single_authority( &path.join("src/unit_tests/data/package_deny/c"), accounts[0].0, &accounts[0].1, @@ -335,7 +335,7 @@ async fn test_package_denied() { ) .await .unwrap(); - let package_b_prime = upgrade_package_on_single_authority( + let (tx_b_prime, package_b_prime) = upgrade_package_on_single_authority( &path.join("src/unit_tests/data/package_deny/b"), accounts[0].0, &accounts[0].1, @@ -349,6 +349,15 @@ async fn test_package_denied() { .await .unwrap(); + state + .get_cache_commit() + .commit_transaction_outputs( + state.epoch_store_for_testing().epoch(), + &[tx_c, tx_b, tx_a, tx_c_prime, tx_b_prime], + ) + .await + .unwrap(); + // Re-create the state such that we could deny package c. let state = reload_state_with_new_deny_config( &network_config, diff --git a/crates/sui-core/src/verify_indexes.rs b/crates/sui-core/src/verify_indexes.rs index befec1a5b3809..ed34d88705cea 100644 --- a/crates/sui-core/src/verify_indexes.rs +++ b/crates/sui-core/src/verify_indexes.rs @@ -1,17 +1,14 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::sync::Weak; use std::{collections::BTreeMap, sync::Arc}; +use crate::jsonrpc_index::{CoinIndexKey2, CoinInfo, IndexStore}; use anyhow::{anyhow, bail, Result}; -use sui_storage::indexes::CoinIndexKey; -use sui_storage::{indexes::CoinInfo, IndexStore}; use sui_types::{base_types::ObjectInfo, object::Owner}; use tracing::info; use typed_store::traits::Map; -use crate::authority::AuthorityState; use crate::{authority::authority_store_tables::LiveObject, state_accumulator::AccumulatorStore}; /// This is a very expensive function that verifies some of the secondary indexes. This is done by @@ -41,7 +38,7 @@ pub fn verify_indexes(store: &dyn AccumulatorStore, indexes: Arc) -> if let Some(type_tag) = object.coin_type_maybe() { let info = CoinInfo::from_object(&object).expect("already checked that this is a coin type"); - let key = (owner, type_tag.to_string(), object.id()); + let key = CoinIndexKey2::new(owner, type_tag.to_string(), info.balance, object.id()); coin_index.insert(key, info); } @@ -91,60 +88,3 @@ pub fn verify_indexes(store: &dyn AccumulatorStore, indexes: Arc) -> Ok(()) } - -// temporary code to repair the coin index. This should be removed in the next release -pub async fn fix_indexes(authority_state: Weak) -> Result<()> { - let is_violation = |coin_index_key: &CoinIndexKey, - state: &Arc| - -> anyhow::Result { - if let Some(object) = state.get_object_store().get_object(&coin_index_key.2)? { - if matches!(object.owner, Owner::AddressOwner(real_owner_id) | Owner::ObjectOwner(real_owner_id) if coin_index_key.0 == real_owner_id) - { - return Ok(false); - } - } - Ok(true) - }; - - tracing::info!("Starting fixing coin index"); - // populate candidate list without locking. Some entries are benign - let authority_state_clone = authority_state.clone(); - let candidates = tokio::task::spawn_blocking(move || { - if let Some(authority) = authority_state_clone.upgrade() { - let mut batch = vec![]; - if let Some(indexes) = &authority.indexes { - for (coin_index_key, _) in indexes.tables().coin_index().unbounded_iter() { - if is_violation(&coin_index_key, &authority)? { - batch.push(coin_index_key); - } - } - } - return Ok::, anyhow::Error>(batch); - } - Ok(vec![]) - }) - .await??; - - if let Some(authority) = authority_state.upgrade() { - if let Some(indexes) = &authority.indexes { - for chunk in candidates.chunks(100) { - let _locks = indexes - .caches - .locks - .acquire_locks(chunk.iter().map(|key| key.0)) - .await; - let mut batch = vec![]; - for key in chunk { - if is_violation(key, &authority)? { - batch.push(key); - } - } - let mut wb = indexes.tables().coin_index().batch(); - wb.delete_batch(indexes.tables().coin_index(), batch)?; - wb.write()?; - } - } - } - tracing::info!("Finished fix for the coin index"); - Ok(()) -} diff --git a/crates/sui-core/tests/staged/sui.yaml b/crates/sui-core/tests/staged/sui.yaml index 632a91e645dee..5899c80ea3805 100644 --- a/crates/sui-core/tests/staged/sui.yaml +++ b/crates/sui-core/tests/staged/sui.yaml @@ -42,6 +42,14 @@ AuthenticatorStateUpdate: TYPENAME: SequenceNumber AuthorityPublicKeyBytes: NEWTYPESTRUCT: BYTES +AuthorityQuorumSignInfo: + STRUCT: + - epoch: U64 + - signature: + TUPLEARRAY: + CONTENT: U8 + SIZE: 48 + - signers_map: BYTES CallArg: ENUM: 0: @@ -97,6 +105,15 @@ CheckpointContentsV1: SEQ: SEQ: TYPENAME: GenericSignature +CheckpointData: + STRUCT: + - checkpoint_summary: + TYPENAME: "sui_types::message_envelope::Envelope>" + - checkpoint_contents: + TYPENAME: CheckpointContents + - transactions: + SEQ: + TYPENAME: CheckpointTransaction CheckpointDigest: NEWTYPESTRUCT: TYPENAME: Digest @@ -121,6 +138,21 @@ CheckpointSummary: TYPENAME: EndOfEpochData - version_specific_data: SEQ: U8 +CheckpointTransaction: + STRUCT: + - transaction: + TYPENAME: "sui_types::message_envelope::Envelope" + - effects: + TYPENAME: TransactionEffects + - events: + OPTION: + TYPENAME: TransactionEvents + - input_objects: + SEQ: + TYPENAME: Object + - output_objects: + SEQ: + TYPENAME: Object Command: ENUM: 0: @@ -340,16 +372,20 @@ EndOfEpochTransactionKind: BridgeCommitteeInit: NEWTYPE: TYPENAME: SequenceNumber -Envelope: +Event: STRUCT: - - data: - TYPENAME: SenderSignedData - - auth_signature: - TYPENAME: EmptySignInfo + - package_id: + TYPENAME: ObjectID + - transaction_module: STR + - sender: + TYPENAME: SuiAddress + - type_: + TYPENAME: StructTag + - contents: BYTES ExecutionData: STRUCT: - transaction: - TYPENAME: Envelope + TYPENAME: "sui_types::message_envelope::Envelope" - effects: TYPENAME: TransactionEffects ExecutionDigests: @@ -640,6 +676,15 @@ MultiSigPublicKey: - TYPENAME: PublicKey - U8 - threshold: U16 +Object: + STRUCT: + - data: + TYPENAME: Data + - owner: + TYPENAME: Owner + - previous_transaction: + TYPENAME: TransactionDigest + - storage_rebate: U64 ObjectArg: ENUM: 0: @@ -985,6 +1030,11 @@ TransactionEffectsV2: - aux_data_digest: OPTION: TYPENAME: EffectsAuxDataDigest +TransactionEvents: + STRUCT: + - data: + SEQ: + TYPENAME: Event TransactionEventsDigest: NEWTYPESTRUCT: TYPENAME: Digest @@ -1153,4 +1203,16 @@ ZkLoginAuthenticatorAsBytes: ZkLoginPublicIdentifier: NEWTYPESTRUCT: SEQ: U8 +"sui_types::message_envelope::Envelope>": + STRUCT: + - data: + TYPENAME: CheckpointSummary + - auth_signature: + TYPENAME: AuthorityQuorumSignInfo +"sui_types::message_envelope::Envelope": + STRUCT: + - data: + TYPENAME: SenderSignedData + - auth_signature: + TYPENAME: EmptySignInfo diff --git a/crates/sui-data-ingestion-core/Cargo.toml b/crates/sui-data-ingestion-core/Cargo.toml index 2c14170d76f4a..eb5b040b01d79 100644 --- a/crates/sui-data-ingestion-core/Cargo.toml +++ b/crates/sui-data-ingestion-core/Cargo.toml @@ -20,6 +20,7 @@ object_store.workspace = true prometheus.workspace = true telemetry-subscribers.workspace = true tokio = { workspace = true, features = ["full"] } +tokio-stream.workspace = true tracing.workspace = true sui-storage.workspace = true sui-types.workspace = true diff --git a/crates/sui-data-ingestion-core/src/executor.rs b/crates/sui-data-ingestion-core/src/executor.rs index 1e13d75832f68..5eef85c0920f2 100644 --- a/crates/sui-data-ingestion-core/src/executor.rs +++ b/crates/sui-data-ingestion-core/src/executor.rs @@ -108,6 +108,14 @@ impl IndexerExecutor

{ } Ok(self.progress_store.stats()) } + + pub async fn update_watermark( + &mut self, + task_name: String, + watermark: CheckpointSequenceNumber, + ) -> Result<()> { + self.progress_store.save(task_name, watermark).await + } } pub async fn setup_single_workflow( diff --git a/crates/sui-data-ingestion-core/src/lib.rs b/crates/sui-data-ingestion-core/src/lib.rs index d9b9842921ae1..bfe3adbf49098 100644 --- a/crates/sui-data-ingestion-core/src/lib.rs +++ b/crates/sui-data-ingestion-core/src/lib.rs @@ -5,6 +5,7 @@ mod executor; mod metrics; mod progress_store; mod reader; +mod reducer; #[cfg(test)] mod tests; mod util; @@ -17,25 +18,24 @@ pub use metrics::DataIngestionMetrics; pub use progress_store::{FileProgressStore, ProgressStore, ShimProgressStore}; pub use reader::ReaderOptions; use sui_types::full_checkpoint_content::CheckpointData; -use sui_types::messages_checkpoint::CheckpointSequenceNumber; pub use util::create_remote_store_client; pub use worker_pool::WorkerPool; #[async_trait] pub trait Worker: Send + Sync { - async fn process_checkpoint(&self, checkpoint: &CheckpointData) -> Result<()>; - /// Optional method. Allows controlling when workflow progress is updated in the progress store. - /// For instance, some pipelines may benefit from aggregating checkpoints, thus skipping - /// the saving of updates for intermediate checkpoints. - /// The default implementation is to update the progress store for every processed checkpoint. - async fn save_progress( - &self, - sequence_number: CheckpointSequenceNumber, - ) -> Option { - Some(sequence_number) - } + type Result: Send + Sync; + async fn process_checkpoint(&self, checkpoint: &CheckpointData) -> Result; fn preprocess_hook(&self, _: &CheckpointData) -> Result<()> { Ok(()) } } + +#[async_trait] +pub trait Reducer: Send + Sync { + async fn commit(&self, batch: Vec) -> Result<()>; + + fn should_close_batch(&self, _batch: &[R], next_item: Option<&R>) -> bool { + next_item.is_none() + } +} diff --git a/crates/sui-data-ingestion-core/src/reducer.rs b/crates/sui-data-ingestion-core/src/reducer.rs new file mode 100644 index 0000000000000..b8e3105c00673 --- /dev/null +++ b/crates/sui-data-ingestion-core/src/reducer.rs @@ -0,0 +1,59 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::{Reducer, Worker, MAX_CHECKPOINTS_IN_PROGRESS}; +use anyhow::Result; +use futures::StreamExt; +use std::collections::HashMap; +use sui_types::messages_checkpoint::CheckpointSequenceNumber; +use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; + +pub(crate) async fn reduce( + task_name: String, + mut current_checkpoint_number: CheckpointSequenceNumber, + progress_receiver: mpsc::Receiver<(CheckpointSequenceNumber, W::Result)>, + executor_progress_sender: mpsc::Sender<(String, CheckpointSequenceNumber)>, + reducer: Option>>, +) -> Result<()> { + // convert to a stream of MAX size. This way, each iteration of the loop will process all ready messages + let mut stream = + ReceiverStream::new(progress_receiver).ready_chunks(MAX_CHECKPOINTS_IN_PROGRESS); + let mut unprocessed = HashMap::new(); + let mut batch = vec![]; + let mut progress_update = None; + + while let Some(update_batch) = stream.next().await { + for (checkpoint_number, message) in update_batch { + unprocessed.insert(checkpoint_number, message); + } + while let Some(message) = unprocessed.remove(¤t_checkpoint_number) { + if let Some(ref reducer) = reducer { + if reducer.should_close_batch(&batch, Some(&message)) { + reducer.commit(std::mem::take(&mut batch)).await?; + batch = vec![message]; + progress_update = Some(current_checkpoint_number); + } else { + batch.push(message); + } + } + current_checkpoint_number += 1; + } + match reducer { + Some(ref reducer) => { + if reducer.should_close_batch(&batch, None) { + reducer.commit(std::mem::take(&mut batch)).await?; + progress_update = Some(current_checkpoint_number); + } + } + None => progress_update = Some(current_checkpoint_number), + } + if let Some(watermark) = progress_update { + executor_progress_sender + .send((task_name.clone(), watermark)) + .await?; + progress_update = None; + } + } + Ok(()) +} diff --git a/crates/sui-data-ingestion-core/src/tests.rs b/crates/sui-data-ingestion-core/src/tests.rs index 11468de0d6037..4465b0c31c381 100644 --- a/crates/sui-data-ingestion-core/src/tests.rs +++ b/crates/sui-data-ingestion-core/src/tests.rs @@ -74,6 +74,7 @@ struct TestWorker; #[async_trait] impl Worker for TestWorker { + type Result = (); async fn process_checkpoint(&self, _checkpoint: &CheckpointData) -> Result<()> { Ok(()) } diff --git a/crates/sui-data-ingestion-core/src/worker_pool.rs b/crates/sui-data-ingestion-core/src/worker_pool.rs index c4434fa2b0c4f..ff4b38f47122f 100644 --- a/crates/sui-data-ingestion-core/src/worker_pool.rs +++ b/crates/sui-data-ingestion-core/src/worker_pool.rs @@ -2,9 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::executor::MAX_CHECKPOINTS_IN_PROGRESS; -use crate::Worker; +use crate::reducer::reduce; +use crate::{Reducer, Worker}; use mysten_metrics::spawn_monitored_task; -use std::collections::{BTreeSet, HashMap, VecDeque}; +use std::collections::{BTreeSet, VecDeque}; use std::sync::Arc; use std::time::Instant; use sui_types::full_checkpoint_content::CheckpointData; @@ -17,6 +18,7 @@ pub struct WorkerPool { pub task_name: String, concurrency: usize, worker: Arc, + reducer: Option>>, } impl WorkerPool { @@ -25,21 +27,35 @@ impl WorkerPool { task_name, concurrency, worker: Arc::new(worker), + reducer: None, } } + pub fn new_with_reducer( + worker: W, + task_name: String, + concurrency: usize, + reducer: Box>, + ) -> Self { + Self { + task_name, + concurrency, + worker: Arc::new(worker), + reducer: Some(reducer), + } + } + pub async fn run( - self, - mut current_checkpoint_number: CheckpointSequenceNumber, + mut self, + watermark: CheckpointSequenceNumber, mut checkpoint_receiver: mpsc::Receiver>, executor_progress_sender: mpsc::Sender<(String, CheckpointSequenceNumber)>, ) { info!( "Starting indexing pipeline {} with concurrency {}. Current watermark is {}.", - self.task_name, self.concurrency, current_checkpoint_number + self.task_name, self.concurrency, watermark ); - let mut updates = HashMap::new(); - let (progress_sender, mut progress_receiver) = mpsc::channel(MAX_CHECKPOINTS_IN_PROGRESS); + let (reducer_sender, reducer_receiver) = mpsc::channel(MAX_CHECKPOINTS_IN_PROGRESS); let mut workers = vec![]; let mut idle: BTreeSet<_> = (0..self.concurrency).collect(); let mut checkpoints = VecDeque::new(); @@ -65,7 +81,7 @@ impl WorkerPool { info!("received checkpoint for processing {} for workflow {}", sequence_number, task_name); let start_time = Instant::now(); let backoff = backoff::ExponentialBackoff::default(); - backoff::future::retry(backoff, || async { + let result = backoff::future::retry(backoff, || async { worker .clone() .process_checkpoint(&checkpoint) @@ -78,7 +94,7 @@ impl WorkerPool { .await .expect("checkpoint processing failed for checkpoint"); info!("finished checkpoint processing {} for workflow {} in {:?}", sequence_number, task_name, start_time.elapsed()); - if cloned_progress_sender.send((worker_id, sequence_number, worker.save_progress(sequence_number).await)).await.is_err() { + if cloned_progress_sender.send((worker_id, sequence_number, result)).await.is_err() { // The progress channel closing is a sign we need to exit this loop. break; } @@ -90,29 +106,20 @@ impl WorkerPool { // Keep all join handles to ensure all workers are terminated before exiting join_handles.push(join_handle); } + spawn_monitored_task!(reduce::( + self.task_name.clone(), + watermark, + reducer_receiver, + executor_progress_sender, + std::mem::take(&mut self.reducer), + )); // main worker pool loop loop { tokio::select! { - Some((worker_id, status_update, progress_watermark)) = progress_receiver.recv() => { + Some((worker_id, checkpoint_number, message)) = progress_receiver.recv() => { idle.insert(worker_id); - updates.insert(status_update, progress_watermark); - if status_update == current_checkpoint_number { - let mut executor_status_update = None; - while let Some(progress_watermark) = updates.remove(¤t_checkpoint_number) { - if let Some(watermark) = progress_watermark { - executor_status_update = Some(watermark + 1); - } - current_checkpoint_number += 1; - } - if let Some(update) = executor_status_update { - if executor_progress_sender - .send((self.task_name.clone(), update)) - .await.is_err() { - // The executor progress channel closing is a sign we need to - // exit this loop. - break; - } - } + if reducer_sender.send((checkpoint_number, message)).await.is_err() { + break; } while !checkpoints.is_empty() && !idle.is_empty() { let checkpoint = checkpoints.pop_front().unwrap(); @@ -129,7 +136,7 @@ impl WorkerPool { } let checkpoint = maybe_checkpoint.expect("invariant's checked"); let sequence_number = checkpoint.checkpoint_summary.sequence_number; - if sequence_number < current_checkpoint_number { + if sequence_number < watermark { continue; } self.worker.preprocess_hook(&checkpoint).expect("failed to preprocess task"); diff --git a/crates/sui-data-ingestion/Cargo.toml b/crates/sui-data-ingestion/Cargo.toml index 174202f303878..2450691f4cdd6 100644 --- a/crates/sui-data-ingestion/Cargo.toml +++ b/crates/sui-data-ingestion/Cargo.toml @@ -32,6 +32,7 @@ sui-archival.workspace = true sui-storage.workspace = true sui-data-ingestion-core.workspace = true sui-types.workspace = true +tempfile.workspace = true url.workspace = true [dev-dependencies] diff --git a/crates/sui-data-ingestion/src/bin/archival_ingestion.rs b/crates/sui-data-ingestion/src/bin/archival_ingestion.rs index 28ea1e77ff878..1acabe874dc0b 100644 --- a/crates/sui-data-ingestion/src/bin/archival_ingestion.rs +++ b/crates/sui-data-ingestion/src/bin/archival_ingestion.rs @@ -2,9 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 use anyhow::Result; +use prometheus::Registry; use serde::{Deserialize, Serialize}; -use sui_data_ingestion::{ArchivalConfig, ArchivalWorker}; -use sui_data_ingestion_core::setup_single_workflow; +use sui_data_ingestion::{ArchivalConfig, ArchivalReducer, ArchivalWorker}; +use sui_data_ingestion_core::{ + DataIngestionMetrics, IndexerExecutor, ReaderOptions, ShimProgressStore, WorkerPool, +}; +use tokio::sync::oneshot; #[derive(Debug, Clone, Serialize, Deserialize)] struct Config { @@ -37,17 +41,25 @@ async fn main() -> Result<()> { commit_file_size: config.commit_file_size, commit_duration_seconds: config.commit_duration_seconds, }; - let worker = ArchivalWorker::new(archival_config).await?; - let initial_checkpoint_number = worker.initial_checkpoint_number().await; - - let (executor, _exit_sender) = setup_single_workflow( - worker, - config.remote_store_url, - initial_checkpoint_number, + let (_exit_sender, exit_receiver) = oneshot::channel(); + let reducer = ArchivalReducer::new(archival_config).await?; + let progress_store = ShimProgressStore(reducer.get_watermark().await?); + let mut executor = IndexerExecutor::new( + progress_store, 1, - None, - ) - .await?; - executor.await?; + DataIngestionMetrics::new(&Registry::new()), + ); + let worker_pool = + WorkerPool::new_with_reducer(ArchivalWorker, "archival".to_string(), 1, Box::new(reducer)); + executor.register(worker_pool).await?; + executor + .run( + tempfile::tempdir()?.into_path(), + Some(config.remote_store_url), + vec![], + ReaderOptions::default(), + exit_receiver, + ) + .await?; Ok(()) } diff --git a/crates/sui-data-ingestion/src/lib.rs b/crates/sui-data-ingestion/src/lib.rs index f5a53324ac8e8..3ef48d9ec9713 100644 --- a/crates/sui-data-ingestion/src/lib.rs +++ b/crates/sui-data-ingestion/src/lib.rs @@ -6,5 +6,6 @@ mod workers; pub use progress_store::DynamoDBProgressStore; pub use workers::{ - ArchivalConfig, ArchivalWorker, BlobTaskConfig, BlobWorker, KVStoreTaskConfig, KVStoreWorker, + ArchivalConfig, ArchivalReducer, ArchivalWorker, BlobTaskConfig, BlobWorker, KVStoreTaskConfig, + KVStoreWorker, }; diff --git a/crates/sui-data-ingestion/src/main.rs b/crates/sui-data-ingestion/src/main.rs index a55ad5535c5ec..0a03b9af29591 100644 --- a/crates/sui-data-ingestion/src/main.rs +++ b/crates/sui-data-ingestion/src/main.rs @@ -7,8 +7,8 @@ use serde::{Deserialize, Serialize}; use std::env; use std::path::PathBuf; use sui_data_ingestion::{ - ArchivalConfig, ArchivalWorker, BlobTaskConfig, BlobWorker, DynamoDBProgressStore, - KVStoreTaskConfig, KVStoreWorker, + ArchivalConfig, ArchivalReducer, ArchivalWorker, BlobTaskConfig, BlobWorker, + DynamoDBProgressStore, KVStoreTaskConfig, KVStoreWorker, }; use sui_data_ingestion_core::{DataIngestionMetrics, ReaderOptions}; use sui_data_ingestion_core::{IndexerExecutor, WorkerPool}; @@ -118,10 +118,15 @@ async fn main() -> Result<()> { for task_config in config.tasks { match task_config.task { Task::Archival(archival_config) => { - let worker_pool = WorkerPool::new( - ArchivalWorker::new(archival_config).await?, + let reducer = ArchivalReducer::new(archival_config).await?; + executor + .update_watermark(task_config.name.clone(), reducer.get_watermark().await?) + .await?; + let worker_pool = WorkerPool::new_with_reducer( + ArchivalWorker, task_config.name, task_config.concurrency, + Box::new(reducer), ); executor.register(worker_pool).await?; } diff --git a/crates/sui-data-ingestion/src/workers/archival.rs b/crates/sui-data-ingestion/src/workers/archival.rs index 1d5bf2700cf2e..3d6c9587ebb71 100644 --- a/crates/sui-data-ingestion/src/workers/archival.rs +++ b/crates/sui-data-ingestion/src/workers/archival.rs @@ -10,18 +10,16 @@ use object_store::path::Path; use object_store::ObjectStore; use serde::{Deserialize, Serialize}; use std::io::Cursor; -use std::ops::Range; use sui_archival::{ create_file_metadata_from_bytes, finalize_manifest, read_manifest_from_bytes, FileType, Manifest, CHECKPOINT_FILE_MAGIC, SUMMARY_FILE_MAGIC, }; -use sui_data_ingestion_core::{create_remote_store_client, Worker}; +use sui_data_ingestion_core::{create_remote_store_client, Reducer, Worker}; use sui_storage::blob::{Blob, BlobEncoding}; use sui_storage::{compress, FileCompression, StorageFormat}; use sui_types::base_types::{EpochId, ExecutionData}; use sui_types::full_checkpoint_content::CheckpointData; use sui_types::messages_checkpoint::{CheckpointSequenceNumber, FullCheckpointContents}; -use tokio::sync::Mutex; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct ArchivalConfig { @@ -31,90 +29,67 @@ pub struct ArchivalConfig { pub commit_duration_seconds: u64, } -struct AccumulatedState { - epoch: EpochId, - checkpoint_range: Range, - buffer: Vec, - summary_buffer: Vec, - last_commit_ms: u64, - should_update_progress: bool, +pub struct ArchivalWorker; +#[async_trait] +impl Worker for ArchivalWorker { + type Result = CheckpointData; + async fn process_checkpoint(&self, checkpoint: &CheckpointData) -> Result { + Ok(checkpoint.clone()) + } } -pub struct ArchivalWorker { +pub struct ArchivalReducer { remote_store: Box, - state: Mutex, - commit_file_size: usize, commit_duration_ms: u64, } -impl ArchivalWorker { +impl ArchivalReducer { pub async fn new(config: ArchivalConfig) -> Result { let remote_store = create_remote_store_client(config.remote_url, config.remote_store_options, 10)?; - let manifest = Self::read_manifest(&remote_store).await?; - let state = AccumulatedState { - epoch: manifest.epoch_num(), - checkpoint_range: manifest.next_checkpoint_seq_num() - ..manifest.next_checkpoint_seq_num(), - buffer: vec![], - summary_buffer: vec![], - last_commit_ms: 0, - should_update_progress: false, - }; Ok(Self { remote_store, - state: Mutex::new(state), - commit_file_size: config.commit_file_size, commit_duration_ms: config.commit_duration_seconds * 1000, }) } - - async fn read_manifest(remote_store: &dyn ObjectStore) -> Result { - Ok(match remote_store.get(&Path::from("MANIFEST")).await { - Ok(resp) => read_manifest_from_bytes(resp.bytes().await?.to_vec())?, - Err(err) if err.to_string().contains("404") => Manifest::new(0, 0), - Err(err) => Err(err)?, - }) - } - - async fn upload(&self, state: &AccumulatedState) -> Result<()> { - let checkpoint_file_path = - format!("epoch_{}/{}.chk", state.epoch, state.checkpoint_range.start); + async fn upload( + &self, + epoch: EpochId, + start: CheckpointSequenceNumber, + end: CheckpointSequenceNumber, + summary_buffer: Vec, + buffer: Vec, + ) -> Result<()> { + let checkpoint_file_path = format!("epoch_{}/{}.chk", epoch, start); let chk_bytes = self .upload_file( Path::from(checkpoint_file_path.clone()), CHECKPOINT_FILE_MAGIC, - &state.buffer, + &buffer, ) .await?; - let summary_file_path = - format!("epoch_{}/{}.sum", state.epoch, state.checkpoint_range.start); + let summary_file_path = format!("epoch_{}/{}.sum", epoch, start); let sum_bytes = self .upload_file( Path::from(summary_file_path.clone()), SUMMARY_FILE_MAGIC, - &state.summary_buffer, + &summary_buffer, ) .await?; let mut manifest = Self::read_manifest(&self.remote_store).await?; let checkpoint_file_metadata = create_file_metadata_from_bytes( chk_bytes, FileType::CheckpointContent, - state.epoch, - state.checkpoint_range.clone(), + epoch, + start..end, )?; let summary_file_metadata = create_file_metadata_from_bytes( sum_bytes, FileType::CheckpointSummary, - state.epoch, - state.checkpoint_range.clone(), + epoch, + start..end, )?; - manifest.update( - state.epoch, - state.checkpoint_range.end, - checkpoint_file_metadata, - summary_file_metadata, - ); + manifest.update(epoch, end, checkpoint_file_metadata, summary_file_metadata); let bytes = finalize_manifest(manifest)?; self.remote_store @@ -122,7 +97,6 @@ impl ArchivalWorker { .await?; Ok(()) } - async fn upload_file(&self, location: Path, magic: u32, content: &[u8]) -> Result { let mut buffer = vec![0; 4]; BigEndian::write_u32(&mut buffer, magic); @@ -138,67 +112,69 @@ impl ArchivalWorker { Ok(Bytes::from(compressed_buffer)) } - pub async fn initial_checkpoint_number(&self) -> CheckpointSequenceNumber { - self.state.lock().await.checkpoint_range.start + pub async fn get_watermark(&self) -> Result { + let manifest = Self::read_manifest(&self.remote_store).await?; + Ok(manifest.next_checkpoint_seq_num()) + } + async fn read_manifest(remote_store: &dyn ObjectStore) -> Result { + Ok(match remote_store.get(&Path::from("MANIFEST")).await { + Ok(resp) => read_manifest_from_bytes(resp.bytes().await?.to_vec())?, + Err(err) if err.to_string().contains("404") => Manifest::new(0, 0), + Err(err) => Err(err)?, + }) } } #[async_trait] -impl Worker for ArchivalWorker { - async fn process_checkpoint(&self, checkpoint: &CheckpointData) -> Result<()> { - let mut state = self.state.lock().await; - let sequence_number = checkpoint.checkpoint_summary.sequence_number; - if sequence_number < state.checkpoint_range.start { - return Ok(()); - } - let epoch = checkpoint.checkpoint_summary.epoch; - if state.buffer.is_empty() { - assert!(epoch == state.epoch || epoch == state.epoch + 1); - state.epoch = epoch; - state.last_commit_ms = checkpoint.checkpoint_summary.timestamp_ms; +impl Reducer for ArchivalReducer { + async fn commit(&self, batch: Vec) -> Result<()> { + if batch.is_empty() { + return Err(anyhow::anyhow!("commit batch can't be empty")); } - let full_checkpoint_contents = FullCheckpointContents::from_contents_and_execution_data( - checkpoint.checkpoint_contents.clone(), - checkpoint - .transactions - .iter() - .map(|t| ExecutionData::new(t.transaction.clone(), t.effects.clone())), - ); - let contents_blob = Blob::encode(&full_checkpoint_contents, BlobEncoding::Bcs)?; - let blob_size = contents_blob.size(); - let summary_blob = Blob::encode(&checkpoint.checkpoint_summary, BlobEncoding::Bcs)?; - - if !state.buffer.is_empty() - && (((state.buffer.len() + blob_size) > self.commit_file_size) - || state.epoch != epoch - || checkpoint.checkpoint_summary.timestamp_ms - > (self.commit_duration_ms + state.last_commit_ms)) - { - self.upload(&state).await?; - state.epoch = epoch; - state.checkpoint_range = sequence_number..sequence_number; - state.buffer = vec![]; - state.summary_buffer = vec![]; - state.last_commit_ms = checkpoint.checkpoint_summary.timestamp_ms; - state.should_update_progress = true; + let mut summary_buffer = vec![]; + let mut buffer = vec![]; + let first_checkpoint = &batch[0]; + let epoch = first_checkpoint.checkpoint_summary.epoch; + let start_checkpoint = first_checkpoint.checkpoint_summary.sequence_number; + let mut last_checkpoint = start_checkpoint; + for checkpoint in batch { + let full_checkpoint_contents = FullCheckpointContents::from_contents_and_execution_data( + checkpoint.checkpoint_contents.clone(), + checkpoint + .transactions + .iter() + .map(|t| ExecutionData::new(t.transaction.clone(), t.effects.clone())), + ); + let contents_blob = Blob::encode(&full_checkpoint_contents, BlobEncoding::Bcs)?; + let summary_blob = Blob::encode(&checkpoint.checkpoint_summary, BlobEncoding::Bcs)?; + contents_blob.write(&mut buffer)?; + summary_blob.write(&mut summary_buffer)?; + last_checkpoint += 1; } - contents_blob.write(&mut state.buffer)?; - summary_blob.write(&mut state.summary_buffer)?; - state.checkpoint_range.end += 1; + self.upload( + epoch, + start_checkpoint, + last_checkpoint, + summary_buffer, + buffer, + ) + .await?; Ok(()) } - async fn save_progress( + fn should_close_batch( &self, - sequence_number: CheckpointSequenceNumber, - ) -> Option { - let mut state = self.state.lock().await; - let should_update_progress = state.should_update_progress; - state.should_update_progress = false; - if should_update_progress && sequence_number > 0 { - Some(sequence_number - 1) - } else { - None + batch: &[CheckpointData], + next_item: Option<&CheckpointData>, + ) -> bool { + // never close a batch without a trigger condition + if batch.is_empty() || next_item.is_none() { + return false; } + let first_checkpoint = &batch[0].checkpoint_summary; + let next_checkpoint = next_item.expect("invariant's checked"); + next_checkpoint.checkpoint_summary.epoch != first_checkpoint.epoch + || next_checkpoint.checkpoint_summary.timestamp_ms + > (self.commit_duration_ms + first_checkpoint.timestamp_ms) } } diff --git a/crates/sui-data-ingestion/src/workers/blob.rs b/crates/sui-data-ingestion/src/workers/blob.rs index 370f6010a71b1..1344d9748d72c 100644 --- a/crates/sui-data-ingestion/src/workers/blob.rs +++ b/crates/sui-data-ingestion/src/workers/blob.rs @@ -32,6 +32,7 @@ impl BlobWorker { #[async_trait] impl Worker for BlobWorker { + type Result = (); async fn process_checkpoint(&self, checkpoint: &CheckpointData) -> Result<()> { let bytes = Blob::encode(checkpoint, BlobEncoding::Bcs)?.to_bytes(); let location = Path::from(format!( diff --git a/crates/sui-data-ingestion/src/workers/kv_store.rs b/crates/sui-data-ingestion/src/workers/kv_store.rs index a34a25c7e8a38..bddefd41da686 100644 --- a/crates/sui-data-ingestion/src/workers/kv_store.rs +++ b/crates/sui-data-ingestion/src/workers/kv_store.rs @@ -20,8 +20,10 @@ use sui_data_ingestion_core::Worker; use sui_storage::http_key_value_store::TaggedKey; use sui_types::full_checkpoint_content::CheckpointData; use sui_types::storage::ObjectKey; +use tracing::error; const TIMEOUT: Duration = Duration::from_secs(60); +const DDB_SIZE_LIMIT: usize = 399000; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct KVStoreTaskConfig { @@ -93,15 +95,17 @@ impl KVStoreWorker { continue; } seen.insert(digest.clone()); + let bytes = bcs::to_bytes(value.borrow())?; + if bytes.len() > DDB_SIZE_LIMIT { + error!("large value for table {:?} and key {:?}", table, digest); + continue; + } let item = WriteRequest::builder() .set_put_request(Some( PutRequest::builder() .item("digest", AttributeValue::B(Blob::new(digest))) .item("type", AttributeValue::S(Self::type_name(table))) - .item( - "bcs", - AttributeValue::B(Blob::new(bcs::to_bytes(value.borrow())?)), - ) + .item("bcs", AttributeValue::B(Blob::new(bytes))) .build(), )) .build(); @@ -172,6 +176,8 @@ impl KVStoreWorker { #[async_trait] impl Worker for KVStoreWorker { + type Result = (); + async fn process_checkpoint(&self, checkpoint: &CheckpointData) -> Result<()> { let mut transactions = vec![]; let mut effects = vec![]; diff --git a/crates/sui-data-ingestion/src/workers/mod.rs b/crates/sui-data-ingestion/src/workers/mod.rs index a2ad63a52752d..733123bded77d 100644 --- a/crates/sui-data-ingestion/src/workers/mod.rs +++ b/crates/sui-data-ingestion/src/workers/mod.rs @@ -4,6 +4,6 @@ mod archival; mod blob; mod kv_store; -pub use archival::{ArchivalConfig, ArchivalWorker}; +pub use archival::{ArchivalConfig, ArchivalReducer, ArchivalWorker}; pub use blob::{BlobTaskConfig, BlobWorker}; pub use kv_store::{KVStoreTaskConfig, KVStoreWorker}; diff --git a/crates/sui-deepbook-indexer/Cargo.toml b/crates/sui-deepbook-indexer/Cargo.toml index b32a1769c6c9d..a416198b6057d 100644 --- a/crates/sui-deepbook-indexer/Cargo.toml +++ b/crates/sui-deepbook-indexer/Cargo.toml @@ -21,7 +21,6 @@ clap.workspace = true mysten-metrics.workspace = true prometheus.workspace = true serde_yaml.workspace = true -sui-bridge.workspace = true sui-sdk.workspace = true sui-json-rpc-types.workspace = true sui-data-ingestion-core.workspace = true @@ -32,12 +31,11 @@ backoff.workspace = true sui-config.workspace = true sui-indexer-builder.workspace = true tempfile.workspace = true -bigdecimal = "0.4.0" +axum.workspace = true +bigdecimal = { version = "0.4.5" } [dev-dependencies] sui-types = { workspace = true, features = ["test-utils"] } -sui-test-transaction-builder.workspace = true -test-cluster.workspace = true hex-literal = "0.3.4" [[bin]] diff --git a/crates/sui-deepbook-indexer/config.yaml b/crates/sui-deepbook-indexer/config.yaml index e5a3b12686932..5d6dd23638d41 100644 --- a/crates/sui-deepbook-indexer/config.yaml +++ b/crates/sui-deepbook-indexer/config.yaml @@ -16,7 +16,3 @@ # metric_url: # Client metric port # metric_port: -# checkpoint size of each backfill worker, use 432000 for 1 worker per day, assume 5 checkpoint per second -# back_fill_lot_size: -# Optional starting checkpoint for realtime ingestion task -# resume_from_checkpoint: \ No newline at end of file diff --git a/crates/sui-deepbook-indexer/src/config.rs b/crates/sui-deepbook-indexer/src/config.rs index 363d722451061..3e854d87f3d42 100644 --- a/crates/sui-deepbook-indexer/src/config.rs +++ b/crates/sui-deepbook-indexer/src/config.rs @@ -17,7 +17,7 @@ pub struct IndexerConfig { pub deepbook_genesis_checkpoint: u64, pub concurrency: u64, pub metric_port: u16, - pub resume_from_checkpoint: Option, + pub service_port: u16, } impl sui_config::Config for IndexerConfig {} diff --git a/crates/sui-deepbook-indexer/src/error.rs b/crates/sui-deepbook-indexer/src/error.rs new file mode 100644 index 0000000000000..525cb8d0b4d11 --- /dev/null +++ b/crates/sui-deepbook-indexer/src/error.rs @@ -0,0 +1,7 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[derive(Debug, Clone)] +pub enum DeepBookError { + InternalError(String), +} diff --git a/crates/sui-deepbook-indexer/src/events.rs b/crates/sui-deepbook-indexer/src/events.rs index 026bc27d57ff5..d50ca0fcccf5b 100644 --- a/crates/sui-deepbook-indexer/src/events.rs +++ b/crates/sui-deepbook-indexer/src/events.rs @@ -14,7 +14,9 @@ pub struct MoveOrderFilledEvent { pub price: u64, pub taker_is_bid: bool, pub taker_fee: u64, + pub taker_fee_is_deep: bool, pub maker_fee: u64, + pub maker_fee_is_deep: bool, pub base_quantity: u64, pub quote_quantity: u64, pub maker_balance_manager_id: ObjectID, @@ -36,6 +38,20 @@ pub struct MoveOrderCanceledEvent { pub timestamp: u64, } +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct MoveOrderExpiredEvent { + pub balance_manager_id: ObjectID, + pub pool_id: ObjectID, + pub order_id: u128, + pub client_order_id: u64, + pub trader: SuiAddress, + pub price: u64, + pub is_bid: bool, + pub original_quantity: u64, + pub base_asset_quantity_canceled: u64, + pub timestamp: u64, +} + #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct MoveOrderModifiedEvent { pub balance_manager_id: ObjectID, @@ -45,6 +61,8 @@ pub struct MoveOrderModifiedEvent { pub trader: SuiAddress, pub price: u64, pub is_bid: bool, + pub previous_quantity: u64, + pub filled_quantity: u64, pub new_quantity: u64, pub timestamp: u64, } @@ -60,6 +78,7 @@ pub struct MoveOrderPlacedEvent { pub is_bid: bool, pub placed_quantity: u64, pub expire_timestamp: u64, + pub timestamp: u64, } #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] diff --git a/crates/sui-deepbook-indexer/src/lib.rs b/crates/sui-deepbook-indexer/src/lib.rs index 3c461811dc991..80a90aebfbeeb 100644 --- a/crates/sui-deepbook-indexer/src/lib.rs +++ b/crates/sui-deepbook-indexer/src/lib.rs @@ -2,11 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 pub mod config; +pub mod error; pub mod events; pub mod metrics; pub mod models; pub mod postgres_manager; pub mod schema; +pub mod server; pub mod types; pub mod sui_datasource; diff --git a/crates/sui-deepbook-indexer/src/main.rs b/crates/sui-deepbook-indexer/src/main.rs index 3ef803c77a5b4..4e47ffaba2b03 100644 --- a/crates/sui-deepbook-indexer/src/main.rs +++ b/crates/sui-deepbook-indexer/src/main.rs @@ -14,6 +14,7 @@ use sui_data_ingestion_core::DataIngestionMetrics; use sui_deepbook_indexer::config::IndexerConfig; use sui_deepbook_indexer::metrics::DeepBookIndexerMetrics; use sui_deepbook_indexer::postgres_manager::get_connection_pool; +use sui_deepbook_indexer::server::run_server; use sui_deepbook_indexer::sui_datasource::SuiCheckpointDatasource; use sui_deepbook_indexer::sui_deepbook_indexer::PgDeepbookPersistent; use sui_deepbook_indexer::sui_deepbook_indexer::SuiDeepBookDataMapper; @@ -84,6 +85,11 @@ async fn main() -> Result<()> { ingestion_metrics.clone(), indexer_meterics.clone(), ); + + let service_address = + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), config.service_port); + run_server(service_address, datastore.clone()); + let indexer = IndexerBuilder::new( "SuiDeepBookIndexer", sui_checkpoint_datasource, diff --git a/crates/sui-deepbook-indexer/src/metrics.rs b/crates/sui-deepbook-indexer/src/metrics.rs index 4a88807cd7d3f..b263c19095e13 100644 --- a/crates/sui-deepbook-indexer/src/metrics.rs +++ b/crates/sui-deepbook-indexer/src/metrics.rs @@ -11,6 +11,7 @@ pub struct DeepBookIndexerMetrics { pub(crate) total_deepbook_transactions: IntCounter, pub(crate) backfill_tasks_remaining_checkpoints: IntGaugeVec, pub(crate) tasks_processed_checkpoints: IntCounterVec, + pub(crate) inflight_live_tasks: IntGaugeVec, } impl DeepBookIndexerMetrics { @@ -36,6 +37,13 @@ impl DeepBookIndexerMetrics { registry, ) .unwrap(), + inflight_live_tasks: register_int_gauge_vec_with_registry!( + "deepbook_indexer_inflight_live_tasks", + "Number of inflight live tasks", + &["task_name"], + registry, + ) + .unwrap(), } } diff --git a/crates/sui-deepbook-indexer/src/migrations/00000000000000_diesel_initial_setup/up.sql b/crates/sui-deepbook-indexer/src/migrations/00000000000000_diesel_initial_setup/up.sql index cfd3fe851df94..04d9951f6e66a 100644 --- a/crates/sui-deepbook-indexer/src/migrations/00000000000000_diesel_initial_setup/up.sql +++ b/crates/sui-deepbook-indexer/src/migrations/00000000000000_diesel_initial_setup/up.sql @@ -16,6 +16,7 @@ CREATE TABLE IF NOT EXISTS order_updates is_bid BOOLEAN NOT NULL, original_quantity BIGINT NOT NULL, quantity BIGINT NOT NULL, + filled_quantity BIGINT NOT NULL, onchain_timestamp BIGINT NOT NULL, balance_manager_id TEXT NOT NULL, trader TEXT NOT NULL @@ -36,7 +37,9 @@ CREATE TABLE IF NOT EXISTS order_fills taker_client_order_id BIGINT NOT NULL, price BIGINT NOT NULL, taker_fee BIGINT NOT NULL, + taker_fee_is_deep BOOLEAN NOT NULL, maker_fee BIGINT NOT NULL, + maker_fee_is_deep BOOLEAN NOT NULL, taker_is_bid BOOLEAN NOT NULL, base_quantity BIGINT NOT NULL, quote_quantity BIGINT NOT NULL, @@ -178,4 +181,21 @@ CREATE TABLE IF NOT EXISTS sui_error_transactions failure_status TEXT NOT NULL, package TEXT NOT NULL, cmd_idx BIGINT -); \ No newline at end of file +); + +CREATE TABLE IF NOT EXISTS pools +( + pool_id TEXT PRIMARY KEY, + pool_name TEXT NOT NULL, + base_asset_id TEXT NOT NULL, + base_asset_decimals SMALLINT NOT NULL, + base_asset_symbol TEXT NOT NULL, + base_asset_name TEXT NOT NULL, + quote_asset_id TEXT NOT NULL, + quote_asset_decimals SMALLINT NOT NULL, + quote_asset_symbol TEXT NOT NULL, + quote_asset_name TEXT NOT NULL, + min_size INTEGER NOT NULL, + lot_size INTEGER NOT NULL, + tick_size INTEGER NOT NULL +); diff --git a/crates/sui-deepbook-indexer/src/models.rs b/crates/sui-deepbook-indexer/src/models.rs index f556b31e89a0c..d0d57671e3bce 100644 --- a/crates/sui-deepbook-indexer/src/models.rs +++ b/crates/sui-deepbook-indexer/src/models.rs @@ -4,11 +4,12 @@ use diesel::data_types::PgTimestamp; use diesel::{Identifiable, Insertable, Queryable, Selectable}; +use serde::Serialize; use sui_indexer_builder::{Task, LIVE_TASK_TARGET_CHECKPOINT}; use crate::schema::{ - balances, flashloans, order_fills, order_updates, pool_prices, progress_store, proposals, - rebates, stakes, sui_error_transactions, trade_params_update, votes, + balances, flashloans, order_fills, order_updates, pool_prices, pools, progress_store, + proposals, rebates, stakes, sui_error_transactions, trade_params_update, votes, }; #[derive(Queryable, Selectable, Insertable, Identifiable, Debug)] @@ -26,6 +27,7 @@ pub struct OrderUpdate { pub is_bid: bool, pub original_quantity: i64, pub quantity: i64, + pub filled_quantity: i64, pub onchain_timestamp: i64, pub trader: String, pub balance_manager_id: String, @@ -45,7 +47,9 @@ pub struct OrderFill { pub taker_client_order_id: i64, pub price: i64, pub taker_fee: i64, + pub taker_fee_is_deep: bool, pub maker_fee: i64, + pub maker_fee_is_deep: bool, pub taker_is_bid: bool, pub base_quantity: i64, pub quote_quantity: i64, @@ -54,6 +58,13 @@ pub struct OrderFill { pub onchain_timestamp: i64, } +#[derive(Queryable)] +pub struct OrderFillSummary { + pub maker_balance_manager_id: String, + pub taker_balance_manager_id: String, + pub base_quantity: i64, +} + #[derive(Queryable, Selectable, Insertable, Identifiable, Debug)] #[diesel(table_name = flashloans, primary_key(digest))] pub struct Flashloan { @@ -161,6 +172,24 @@ pub struct Votes { pub stake: i64, } +#[derive(Queryable, Selectable, Insertable, Identifiable, Debug, Serialize)] +#[diesel(table_name = pools, primary_key(pool_id))] +pub struct Pools { + pub pool_id: String, + pub pool_name: String, + pub base_asset_id: String, + pub base_asset_decimals: i16, + pub base_asset_symbol: String, + pub base_asset_name: String, + pub quote_asset_id: String, + pub quote_asset_decimals: i16, + pub quote_asset_symbol: String, + pub quote_asset_name: String, + pub min_size: i32, + pub lot_size: i32, + pub tick_size: i32, +} + #[derive(Queryable, Selectable, Insertable, Identifiable, Debug)] #[diesel(table_name = sui_error_transactions, primary_key(txn_digest))] pub struct SuiErrorTransactions { diff --git a/crates/sui-deepbook-indexer/src/schema.rs b/crates/sui-deepbook-indexer/src/schema.rs index ff2eeef876445..61abd7987b533 100644 --- a/crates/sui-deepbook-indexer/src/schema.rs +++ b/crates/sui-deepbook-indexer/src/schema.rs @@ -47,7 +47,9 @@ diesel::table! { taker_client_order_id -> Int8, price -> Int8, taker_fee -> Int8, + taker_fee_is_deep -> Bool, maker_fee -> Int8, + maker_fee_is_deep -> Bool, taker_is_bid -> Bool, base_quantity -> Int8, quote_quantity -> Int8, @@ -73,6 +75,7 @@ diesel::table! { is_bid -> Bool, original_quantity -> Int8, quantity -> Int8, + filled_quantity -> Int8, onchain_timestamp -> Int8, balance_manager_id -> Text, trader -> Text, @@ -93,6 +96,24 @@ diesel::table! { } } +diesel::table! { + pools (pool_id) { + pool_id -> Text, + pool_name -> Text, + base_asset_id -> Text, + base_asset_decimals -> Int2, + base_asset_symbol -> Text, + base_asset_name -> Text, + quote_asset_id -> Text, + quote_asset_decimals -> Int2, + quote_asset_symbol -> Text, + quote_asset_name -> Text, + min_size -> Int4, + lot_size -> Int4, + tick_size -> Int4, + } +} + diesel::table! { progress_store (task_name) { task_name -> Text, @@ -200,6 +221,7 @@ diesel::allow_tables_to_appear_in_same_query!( order_fills, order_updates, pool_prices, + pools, progress_store, proposals, rebates, diff --git a/crates/sui-deepbook-indexer/src/server.rs b/crates/sui-deepbook-indexer/src/server.rs new file mode 100644 index 0000000000000..1f2a5a9985be6 --- /dev/null +++ b/crates/sui-deepbook-indexer/src/server.rs @@ -0,0 +1,144 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + error::DeepBookError, + models::{OrderFillSummary, Pools}, + schema, + sui_deepbook_indexer::PgDeepbookPersistent, +}; +use axum::{ + debug_handler, + extract::{Path, State}, + http::StatusCode, + routing::get, + Json, Router, +}; +use diesel::BoolExpressionMethods; +use diesel::QueryDsl; +use diesel::{ExpressionMethods, SelectableHelper}; +use diesel_async::RunQueryDsl; +use std::net::SocketAddr; +use std::time::{SystemTime, UNIX_EPOCH}; +use tokio::{net::TcpListener, task::JoinHandle}; + +pub const GET_POOLS_PATH: &str = "/get_pools"; +pub const GET_24HR_VOLUME_PATH: &str = "/get_24hr_volume/:pool_id"; +pub const GET_24HR_VOLUME_BY_BALANCE_MANAGER_ID: &str = + "/get_24hr_volume_by_balance_manager_id/:pool_id/:balance_manager_id"; + +pub fn run_server(socket_address: SocketAddr, state: PgDeepbookPersistent) -> JoinHandle<()> { + tokio::spawn(async move { + let listener = TcpListener::bind(socket_address).await.unwrap(); + axum::serve(listener, make_router(state)).await.unwrap(); + }) +} + +pub(crate) fn make_router(state: PgDeepbookPersistent) -> Router { + Router::new() + .route("/", get(health_check)) + .route(GET_POOLS_PATH, get(get_pools)) + .route(GET_24HR_VOLUME_PATH, get(get_24hr_volume)) + .route( + GET_24HR_VOLUME_BY_BALANCE_MANAGER_ID, + get(get_24hr_volume_by_balance_manager_id), + ) + .with_state(state) +} + +impl axum::response::IntoResponse for DeepBookError { + // TODO: distinguish client error. + fn into_response(self) -> axum::response::Response { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Something went wrong: {:?}", self), + ) + .into_response() + } +} + +impl From for DeepBookError +where + E: Into, +{ + fn from(err: E) -> Self { + Self::InternalError(err.into().to_string()) + } +} + +async fn health_check() -> StatusCode { + StatusCode::OK +} + +/// Get all pools stored in database +#[debug_handler] +async fn get_pools( + State(state): State, +) -> Result>, DeepBookError> { + let connection = &mut state.pool.get().await?; + let results = schema::pools::table + .select(Pools::as_select()) + .load(connection) + .await?; + + Ok(Json(results)) +} + +async fn get_24hr_volume( + Path(pool_id): Path, + State(state): State, +) -> Result, DeepBookError> { + let connection = &mut state.pool.get().await?; + let unix_ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as i64; + let day_ago = unix_ts - 24 * 60 * 60 * 1000; + let vols: Vec = schema::order_fills::table + .select(schema::order_fills::base_quantity) + .filter(schema::order_fills::pool_id.eq(pool_id)) + .filter(schema::order_fills::onchain_timestamp.gt(day_ago)) + .load(connection) + .await?; + Ok(Json(vols.into_iter().map(|v| v as u64).sum())) +} + +async fn get_24hr_volume_by_balance_manager_id( + Path((pool_id, balance_manager_id)): Path<(String, String)>, + State(state): State, +) -> Result>, DeepBookError> { + let connection = &mut state.pool.get().await?; + let unix_ts = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis() as i64; + let day_ago = unix_ts - 24 * 60 * 60 * 1000; + let results: Vec = schema::order_fills::table + .select(( + schema::order_fills::maker_balance_manager_id, + schema::order_fills::taker_balance_manager_id, + schema::order_fills::base_quantity, + )) + .filter(schema::order_fills::pool_id.eq(pool_id)) + .filter(schema::order_fills::onchain_timestamp.gt(day_ago)) + .filter( + schema::order_fills::maker_balance_manager_id + .eq(&balance_manager_id) + .or(schema::order_fills::taker_balance_manager_id.eq(&balance_manager_id)), + ) + .load(connection) + .await?; + + let mut maker_vol = 0; + let mut taker_vol = 0; + for order_fill in results { + if order_fill.maker_balance_manager_id == balance_manager_id { + maker_vol += order_fill.base_quantity; + }; + if order_fill.taker_balance_manager_id == balance_manager_id { + taker_vol += order_fill.base_quantity; + }; + } + + Ok(Json(vec![maker_vol, taker_vol])) +} diff --git a/crates/sui-deepbook-indexer/src/sui_datasource.rs b/crates/sui-deepbook-indexer/src/sui_datasource.rs index a9942bf541599..3ca231bfc15a9 100644 --- a/crates/sui-deepbook-indexer/src/sui_datasource.rs +++ b/crates/sui-deepbook-indexer/src/sui_datasource.rs @@ -111,6 +111,10 @@ impl Datasource for SuiCheckpointDatasource { fn get_tasks_processed_checkpoints_metric(&self) -> &IntCounterVec { &self.indexer_metrics.tasks_processed_checkpoints } + + fn get_inflight_live_tasks_metrics(&self) -> &IntGaugeVec { + &self.indexer_metrics.inflight_live_tasks + } } struct PerTaskInMemProgressStore { @@ -157,6 +161,8 @@ pub type CheckpointTxnData = (CheckpointTransaction, u64, u64); #[async_trait] impl Worker for IndexerWorker { + type Result = (); + async fn process_checkpoint(&self, checkpoint: &SuiCheckpointData) -> anyhow::Result<()> { tracing::trace!( "Received checkpoint [{}] {}: {}", diff --git a/crates/sui-deepbook-indexer/src/sui_deepbook_indexer.rs b/crates/sui-deepbook-indexer/src/sui_deepbook_indexer.rs index f06bc039e18f4..92eac3b0ad449 100644 --- a/crates/sui-deepbook-indexer/src/sui_deepbook_indexer.rs +++ b/crates/sui-deepbook-indexer/src/sui_deepbook_indexer.rs @@ -22,9 +22,9 @@ use sui_types::execution_status::ExecutionStatus; use sui_types::full_checkpoint_content::CheckpointTransaction; use crate::events::{ - MoveBalanceEvent, MoveFlashLoanBorrowedEvent, MoveOrderCanceledEvent, MoveOrderFilledEvent, - MoveOrderModifiedEvent, MoveOrderPlacedEvent, MovePriceAddedEvent, MoveProposalEvent, - MoveRebateEvent, MoveStakeEvent, MoveTradeParamsUpdateEvent, MoveVoteEvent, + MoveBalanceEvent, MoveFlashLoanBorrowedEvent, MoveOrderCanceledEvent, MoveOrderExpiredEvent, + MoveOrderFilledEvent, MoveOrderModifiedEvent, MoveOrderPlacedEvent, MovePriceAddedEvent, + MoveProposalEvent, MoveRebateEvent, MoveStakeEvent, MoveTradeParamsUpdateEvent, MoveVoteEvent, }; use crate::metrics::DeepBookIndexerMetrics; use crate::postgres_manager::PgPool; @@ -43,7 +43,7 @@ use crate::{models, schema}; /// Persistent layer impl #[derive(Clone)] pub struct PgDeepbookPersistent { - pool: PgPool, + pub pool: PgPool, save_progress_policy: ProgressSavingPolicy, } @@ -375,8 +375,6 @@ fn process_sui_event( Ok(if ev.type_.address == *package_id { match ev.type_.name.as_str() { "OrderPlaced" => { - info!("Observed Deepbook Order Placed {:?}", ev); - // metrics.total_sui_token_deposited.inc(); let move_event: MoveOrderPlacedEvent = bcs::from_bytes(&ev.contents)?; let txn_kind = tx.transaction.transaction_data().clone().into_kind(); let first_command = txn_kind.iter_commands().next(); @@ -385,7 +383,7 @@ fn process_sui_event( } else { "".to_string() }; - Some(ProcessedTxnData::OrderUpdate(OrderUpdate { + let txn_data = Some(ProcessedTxnData::OrderUpdate(OrderUpdate { digest: tx.transaction.digest().to_string(), sender: tx.transaction.sender_address().to_string(), checkpoint, @@ -396,16 +394,18 @@ fn process_sui_event( client_order_id: move_event.client_order_id, price: move_event.price, is_bid: move_event.is_bid, - onchain_timestamp: move_event.expire_timestamp, + onchain_timestamp: move_event.timestamp, original_quantity: move_event.placed_quantity, quantity: move_event.placed_quantity, + filled_quantity: 0, trader: move_event.trader.to_string(), balance_manager_id: move_event.balance_manager_id.to_string(), - })) + })); + info!("Observed Deepbook Order Placed {:?}", txn_data); + + txn_data } "OrderModified" => { - info!("Observed Deepbook Order Modified {:?}", ev); - // metrics.total_sui_token_deposited.inc(); let move_event: MoveOrderModifiedEvent = bcs::from_bytes(&ev.contents)?; let txn_kind = tx.transaction.transaction_data().clone().into_kind(); let first_command = txn_kind.iter_commands().next(); @@ -414,7 +414,7 @@ fn process_sui_event( } else { "".to_string() }; - Some(ProcessedTxnData::OrderUpdate(OrderUpdate { + let txn_data = Some(ProcessedTxnData::OrderUpdate(OrderUpdate { digest: tx.transaction.digest().to_string(), sender: tx.transaction.sender_address().to_string(), checkpoint, @@ -426,15 +426,17 @@ fn process_sui_event( price: move_event.price, is_bid: move_event.is_bid, onchain_timestamp: move_event.timestamp, - original_quantity: 0, + original_quantity: move_event.previous_quantity, quantity: move_event.new_quantity, + filled_quantity: move_event.filled_quantity, trader: move_event.trader.to_string(), balance_manager_id: move_event.balance_manager_id.to_string(), - })) + })); + info!("Observed Deepbook Order Modified {:?}", txn_data); + + txn_data } "OrderCanceled" => { - info!("Observed Deepbook Order Canceled {:?}", ev); - // metrics.total_sui_token_deposited.inc(); let move_event: MoveOrderCanceledEvent = bcs::from_bytes(&ev.contents)?; let txn_kind = tx.transaction.transaction_data().clone().into_kind(); let first_command = txn_kind.iter_commands().next(); @@ -443,7 +445,7 @@ fn process_sui_event( } else { "".to_string() }; - Some(ProcessedTxnData::OrderUpdate(OrderUpdate { + let txn_data = Some(ProcessedTxnData::OrderUpdate(OrderUpdate { digest: tx.transaction.digest().to_string(), sender: tx.transaction.sender_address().to_string(), checkpoint, @@ -457,13 +459,48 @@ fn process_sui_event( onchain_timestamp: move_event.timestamp, original_quantity: move_event.original_quantity, quantity: move_event.base_asset_quantity_canceled, + filled_quantity: move_event.original_quantity + - move_event.base_asset_quantity_canceled, + trader: move_event.trader.to_string(), + balance_manager_id: move_event.balance_manager_id.to_string(), + })); + info!("Observed Deepbook Order Canceled {:?}", txn_data); + + txn_data + } + "OrderExpired" => { + let move_event: MoveOrderExpiredEvent = bcs::from_bytes(&ev.contents)?; + let txn_kind = tx.transaction.transaction_data().clone().into_kind(); + let first_command = txn_kind.iter_commands().next(); + let package = if let Some(Command::MoveCall(move_call)) = first_command { + move_call.package.to_string() + } else { + "".to_string() + }; + let txn_data = Some(ProcessedTxnData::OrderUpdate(OrderUpdate { + digest: tx.transaction.digest().to_string(), + sender: tx.transaction.sender_address().to_string(), + checkpoint, + package, + status: OrderUpdateStatus::Expired, + pool_id: move_event.pool_id.to_string(), + order_id: move_event.order_id, + client_order_id: move_event.client_order_id, + price: move_event.price, + is_bid: move_event.is_bid, + onchain_timestamp: move_event.timestamp, + original_quantity: move_event.original_quantity, + quantity: move_event.base_asset_quantity_canceled, + filled_quantity: move_event.original_quantity + - move_event.base_asset_quantity_canceled, trader: move_event.trader.to_string(), balance_manager_id: move_event.balance_manager_id.to_string(), - })) + })); + info!("Observed Deepbook Order Expired {:?}", txn_data); + + txn_data } "OrderFilled" => { - info!("Observed Deepbook Order Filled {:?}", ev); - // metrics.total_sui_token_deposited.inc(); let move_event: MoveOrderFilledEvent = bcs::from_bytes(&ev.contents)?; let txn_kind = tx.transaction.transaction_data().clone().into_kind(); let first_command = txn_kind.iter_commands().next(); @@ -472,7 +509,7 @@ fn process_sui_event( } else { "".to_string() }; - Some(ProcessedTxnData::OrderFill(OrderFill { + let txn_data = Some(ProcessedTxnData::OrderFill(OrderFill { digest: tx.transaction.digest().to_string(), sender: tx.transaction.sender_address().to_string(), checkpoint, @@ -485,17 +522,20 @@ fn process_sui_event( price: move_event.price, taker_is_bid: move_event.taker_is_bid, taker_fee: move_event.taker_fee, + taker_fee_is_deep: move_event.taker_fee_is_deep, maker_fee: move_event.maker_fee, + maker_fee_is_deep: move_event.maker_fee_is_deep, base_quantity: move_event.base_quantity, quote_quantity: move_event.quote_quantity, maker_balance_manager_id: move_event.maker_balance_manager_id.to_string(), taker_balance_manager_id: move_event.taker_balance_manager_id.to_string(), onchain_timestamp: move_event.timestamp, - })) + })); + info!("Observed Deepbook Order Filled {:?}", txn_data); + + txn_data } "FlashLoanBorrowed" => { - info!("Observed Deepbook Flash Loan Borrowed {:?}", ev); - // metrics.total_sui_token_deposited.inc(); let move_event: MoveFlashLoanBorrowedEvent = bcs::from_bytes(&ev.contents)?; let txn_kind = tx.transaction.transaction_data().clone().into_kind(); let first_command = txn_kind.iter_commands().next(); @@ -504,7 +544,7 @@ fn process_sui_event( } else { "".to_string() }; - Some(ProcessedTxnData::Flashloan(Flashloan { + let txn_data = Some(ProcessedTxnData::Flashloan(Flashloan { digest: tx.transaction.digest().to_string(), sender: tx.transaction.sender_address().to_string(), checkpoint, @@ -513,11 +553,12 @@ fn process_sui_event( borrow_quantity: move_event.borrow_quantity, borrow: true, type_name: move_event.type_name.to_string(), - })) + })); + info!("Observed Deepbook Flash Loan Borrowed {:?}", txn_data); + + txn_data } "PriceAdded" => { - info!("Observed Deepbook Price Addition {:?}", ev); - // metrics.total_sui_token_deposited.inc(); let move_event: MovePriceAddedEvent = bcs::from_bytes(&ev.contents)?; let txn_kind = tx.transaction.transaction_data().clone().into_kind(); let first_command = txn_kind.iter_commands().next(); @@ -526,7 +567,7 @@ fn process_sui_event( } else { "".to_string() }; - Some(ProcessedTxnData::PoolPrice(PoolPrice { + let txn_data = Some(ProcessedTxnData::PoolPrice(PoolPrice { digest: tx.transaction.digest().to_string(), sender: tx.transaction.sender_address().to_string(), checkpoint, @@ -534,11 +575,12 @@ fn process_sui_event( target_pool: move_event.target_pool.to_string(), conversion_rate: move_event.conversion_rate, reference_pool: move_event.reference_pool.to_string(), - })) + })); + info!("Observed Deepbook Price Addition {:?}", txn_data); + + txn_data } "BalanceEvent" => { - info!("Observed Deepbook Balance Event {:?}", ev); - // metrics.total_sui_token_deposited.inc(); let move_event: MoveBalanceEvent = bcs::from_bytes(&ev.contents)?; let txn_kind = tx.transaction.transaction_data().clone().into_kind(); let first_command = txn_kind.iter_commands().next(); @@ -547,7 +589,7 @@ fn process_sui_event( } else { "".to_string() }; - Some(ProcessedTxnData::Balances(Balances { + let txn_data = Some(ProcessedTxnData::Balances(Balances { digest: tx.transaction.digest().to_string(), sender: tx.transaction.sender_address().to_string(), checkpoint, @@ -556,11 +598,12 @@ fn process_sui_event( asset: move_event.asset.to_string(), amount: move_event.amount, deposit: move_event.deposit, - })) + })); + info!("Observed Deepbook Balance Event {:?}", txn_data); + + txn_data } "ProposalEvent" => { - info!("Observed Deepbook Proposal Event {:?}", ev); - // metrics.total_sui_token_deposited.inc(); let move_event: MoveProposalEvent = bcs::from_bytes(&ev.contents)?; let txn_kind = tx.transaction.transaction_data().clone().into_kind(); let first_command = txn_kind.iter_commands().next(); @@ -569,7 +612,7 @@ fn process_sui_event( } else { "".to_string() }; - Some(ProcessedTxnData::Proposals(Proposals { + let txn_data = Some(ProcessedTxnData::Proposals(Proposals { digest: tx.transaction.digest().to_string(), sender: tx.transaction.sender_address().to_string(), checkpoint, @@ -579,11 +622,12 @@ fn process_sui_event( taker_fee: move_event.taker_fee, maker_fee: move_event.maker_fee, stake_required: move_event.stake_required, - })) + })); + info!("Observed Deepbook Proposal Event {:?}", txn_data); + + txn_data } "RebateEvent" => { - info!("Observed Deepbook Rebate Event {:?}", ev); - // metrics.total_sui_token_deposited.inc(); let move_event: MoveRebateEvent = bcs::from_bytes(&ev.contents)?; let txn_kind = tx.transaction.transaction_data().clone().into_kind(); let first_command = txn_kind.iter_commands().next(); @@ -592,7 +636,7 @@ fn process_sui_event( } else { "".to_string() }; - Some(ProcessedTxnData::Rebates(Rebates { + let txn_data = Some(ProcessedTxnData::Rebates(Rebates { digest: tx.transaction.digest().to_string(), sender: tx.transaction.sender_address().to_string(), checkpoint, @@ -601,11 +645,12 @@ fn process_sui_event( balance_manager_id: move_event.balance_manager_id.to_string(), epoch: move_event.epoch, claim_amount: move_event.claim_amount, - })) + })); + info!("Observed Deepbook Rebate Event {:?}", txn_data); + + txn_data } "StakeEvent" => { - info!("Observed Deepbook Stake Event {:?}", ev); - // metrics.total_sui_token_deposited.inc(); let move_event: MoveStakeEvent = bcs::from_bytes(&ev.contents)?; let txn_kind = tx.transaction.transaction_data().clone().into_kind(); let first_command = txn_kind.iter_commands().next(); @@ -614,7 +659,7 @@ fn process_sui_event( } else { "".to_string() }; - Some(ProcessedTxnData::Stakes(Stakes { + let txn_data = Some(ProcessedTxnData::Stakes(Stakes { digest: tx.transaction.digest().to_string(), sender: tx.transaction.sender_address().to_string(), checkpoint, @@ -624,11 +669,12 @@ fn process_sui_event( epoch: move_event.epoch, amount: move_event.amount, stake: move_event.stake, - })) + })); + info!("Observed Deepbook Stake Event {:?}", txn_data); + + txn_data } "TradeParamsUpdateEvent" => { - info!("Observed Deepbook Trade Params Update Event {:?}", ev); - // metrics.total_sui_token_deposited.inc(); let move_event: MoveTradeParamsUpdateEvent = bcs::from_bytes(&ev.contents)?; let txn_kind = tx.transaction.transaction_data().clone().into_kind(); let first_command = txn_kind.iter_commands().next(); @@ -649,7 +695,7 @@ fn process_sui_event( } } } - Some(ProcessedTxnData::TradeParamsUpdate(TradeParamsUpdate { + let txn_data = Some(ProcessedTxnData::TradeParamsUpdate(TradeParamsUpdate { digest: tx.transaction.digest().to_string(), sender: tx.transaction.sender_address().to_string(), checkpoint, @@ -658,11 +704,12 @@ fn process_sui_event( taker_fee: move_event.taker_fee, maker_fee: move_event.maker_fee, stake_required: move_event.stake_required, - })) + })); + info!("Observed Deepbook Trade Params Update Event {:?}", txn_data); + + txn_data } "VoteEvent" => { - info!("Observed Deepbook Vote Event {:?}", ev); - // metrics.total_sui_token_deposited.inc(); let move_event: MoveVoteEvent = bcs::from_bytes(&ev.contents)?; let txn_kind = tx.transaction.transaction_data().clone().into_kind(); let first_command = txn_kind.iter_commands().next(); @@ -671,7 +718,7 @@ fn process_sui_event( } else { "".to_string() }; - Some(ProcessedTxnData::Votes(Votes { + let txn_data = Some(ProcessedTxnData::Votes(Votes { digest: tx.transaction.digest().to_string(), sender: tx.transaction.sender_address().to_string(), checkpoint, @@ -682,7 +729,10 @@ fn process_sui_event( from_proposal_id: move_event.from_proposal_id.map(|id| id.to_string()), to_proposal_id: move_event.to_proposal_id.to_string(), stake: move_event.stake, - })) + })); + info!("Observed Deepbook Vote Event {:?}", txn_data); + + txn_data } _ => { // todo: metrics.total_sui_bridge_txn_other.inc(); diff --git a/crates/sui-deepbook-indexer/src/types.rs b/crates/sui-deepbook-indexer/src/types.rs index e04fec322866e..251299f0b4870 100644 --- a/crates/sui-deepbook-indexer/src/types.rs +++ b/crates/sui-deepbook-indexer/src/types.rs @@ -18,7 +18,7 @@ use crate::models::SuiErrorTransactions; use crate::models::TradeParamsUpdate as DBTradeParamsUpdate; use crate::models::Votes as DBVotes; -#[derive(Clone)] +#[derive(Clone, Debug)] pub enum ProcessedTxnData { Flashloan(Flashloan), OrderUpdate(OrderUpdate), @@ -33,11 +33,12 @@ pub enum ProcessedTxnData { Error(SuiTxnError), } -#[derive(Clone)] +#[derive(Clone, Debug)] pub(crate) enum OrderUpdateStatus { Placed, Modified, Canceled, + Expired, } impl Display for OrderUpdateStatus { @@ -46,12 +47,13 @@ impl Display for OrderUpdateStatus { OrderUpdateStatus::Placed => "Placed", OrderUpdateStatus::Modified => "Modified", OrderUpdateStatus::Canceled => "Canceled", + OrderUpdateStatus::Expired => "Expired", }; write!(f, "{str}") } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct OrderUpdate { pub(crate) digest: String, pub(crate) sender: String, @@ -65,6 +67,7 @@ pub struct OrderUpdate { pub(crate) is_bid: bool, pub(crate) original_quantity: u64, pub(crate) quantity: u64, + pub(crate) filled_quantity: u64, pub(crate) onchain_timestamp: u64, pub(crate) trader: String, pub(crate) balance_manager_id: String, @@ -86,13 +89,14 @@ impl OrderUpdate { is_bid: self.is_bid, original_quantity: self.original_quantity as i64, quantity: self.quantity as i64, + filled_quantity: self.filled_quantity as i64, onchain_timestamp: self.onchain_timestamp as i64, balance_manager_id: self.balance_manager_id.clone(), } } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct OrderFill { pub(crate) digest: String, pub(crate) sender: String, @@ -106,7 +110,9 @@ pub struct OrderFill { pub(crate) price: u64, pub(crate) taker_is_bid: bool, pub(crate) taker_fee: u64, + pub(crate) taker_fee_is_deep: bool, pub(crate) maker_fee: u64, + pub(crate) maker_fee_is_deep: bool, pub(crate) base_quantity: u64, pub(crate) quote_quantity: u64, pub(crate) maker_balance_manager_id: String, @@ -128,7 +134,9 @@ impl OrderFill { taker_client_order_id: self.taker_client_order_id as i64, price: self.price as i64, taker_fee: self.taker_fee as i64, + taker_fee_is_deep: self.taker_fee_is_deep, maker_fee: self.maker_fee as i64, + maker_fee_is_deep: self.maker_fee_is_deep, taker_is_bid: self.taker_is_bid, base_quantity: self.base_quantity as i64, quote_quantity: self.quote_quantity as i64, @@ -139,7 +147,7 @@ impl OrderFill { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Flashloan { pub(crate) digest: String, pub(crate) sender: String, @@ -166,7 +174,7 @@ impl Flashloan { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct PoolPrice { pub(crate) digest: String, pub(crate) sender: String, @@ -191,7 +199,7 @@ impl PoolPrice { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Balances { pub digest: String, pub sender: String, @@ -218,7 +226,7 @@ impl Balances { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Proposals { pub(crate) digest: String, pub(crate) sender: String, @@ -247,7 +255,7 @@ impl Proposals { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Rebates { pub(crate) digest: String, pub(crate) sender: String, @@ -274,7 +282,7 @@ impl Rebates { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Stakes { pub(crate) digest: String, pub(crate) sender: String, @@ -303,7 +311,7 @@ impl Stakes { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct TradeParamsUpdate { pub(crate) digest: String, pub(crate) sender: String, @@ -330,7 +338,7 @@ impl TradeParamsUpdate { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct Votes { pub(crate) digest: String, pub(crate) sender: String, @@ -361,7 +369,7 @@ impl Votes { } } -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct SuiTxnError { pub(crate) tx_digest: TransactionDigest, pub(crate) sender: SuiAddress, diff --git a/crates/sui-e2e-tests/Cargo.toml b/crates/sui-e2e-tests/Cargo.toml index 970458f3a9e94..9d4370817302d 100644 --- a/crates/sui-e2e-tests/Cargo.toml +++ b/crates/sui-e2e-tests/Cargo.toml @@ -64,6 +64,7 @@ sui-sdk.workspace = true sui-keys.workspace = true sui-rest-api.workspace = true shared-crypto.workspace = true +sui-sdk-types.workspace = true passkey-types.workspace = true passkey-client.workspace = true diff --git a/crates/sui-e2e-tests/tests/bridge_tests.rs b/crates/sui-e2e-tests/tests/bridge_tests.rs deleted file mode 100644 index 350b896c962df..0000000000000 --- a/crates/sui-e2e-tests/tests/bridge_tests.rs +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use sui_bridge::crypto::BridgeAuthorityKeyPair; -use sui_bridge::BRIDGE_ENABLE_PROTOCOL_VERSION; -use sui_json_rpc_api::BridgeReadApiClient; -use sui_macros::sim_test; -use sui_types::bridge::get_bridge; -use sui_types::bridge::BridgeTrait; -use sui_types::crypto::get_key_pair; -use sui_types::SUI_BRIDGE_OBJECT_ID; -use test_cluster::TestClusterBuilder; - -#[sim_test] -async fn test_create_bridge_state_object() { - let test_cluster = TestClusterBuilder::new() - .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION - 1).into()) - .with_epoch_duration_ms(20000) - .build() - .await; - - let handles = test_cluster.all_node_handles(); - - // no node has the bridge state object yet - for h in &handles { - h.with(|node| { - assert!(node - .state() - .get_object_cache_reader() - .get_latest_object_ref_or_tombstone(SUI_BRIDGE_OBJECT_ID) - .unwrap() - .is_none()); - }); - } - - // wait until feature is enabled - test_cluster - .wait_for_protocol_version(BRIDGE_ENABLE_PROTOCOL_VERSION.into()) - .await; - // wait until next epoch - authenticator state object is created at the end of the first epoch - // in which it is supported. - test_cluster.wait_for_epoch_all_nodes(2).await; // protocol upgrade completes in epoch 1 - - for h in &handles { - h.with(|node| { - node.state() - .get_object_cache_reader() - .get_latest_object_ref_or_tombstone(SUI_BRIDGE_OBJECT_ID) - .unwrap() - .expect("auth state object should exist"); - }); - } -} - -#[tokio::test] -async fn test_committee_registration() { - telemetry_subscribers::init_for_testing(); - let mut bridge_keys = vec![]; - for _ in 0..=3 { - let (_, kp): (_, BridgeAuthorityKeyPair) = get_key_pair(); - bridge_keys.push(kp); - } - let test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() - .with_protocol_version((BRIDGE_ENABLE_PROTOCOL_VERSION).into()) - .build_with_bridge(bridge_keys, false) - .await; - - let bridge = get_bridge( - test_cluster - .fullnode_handle - .sui_node - .state() - .get_object_store(), - ) - .unwrap(); - - // Member should be empty before end of epoch - assert!(bridge.committee().members.contents.is_empty()); - assert_eq!( - test_cluster.swarm.active_validators().count(), - bridge.committee().member_registrations.contents.len() - ); - - test_cluster - .trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized() - .await; -} - -#[tokio::test] -async fn test_bridge_api_compatibility() { - let test_cluster: test_cluster::TestCluster = TestClusterBuilder::new() - .with_protocol_version(BRIDGE_ENABLE_PROTOCOL_VERSION.into()) - .build() - .await; - - test_cluster.trigger_reconfiguration().await; - let client = test_cluster.rpc_client(); - client.get_latest_bridge().await.unwrap(); - // TODO: assert fields in summary - - client - .get_bridge_object_initial_shared_version() - .await - .unwrap(); -} diff --git a/crates/sui-e2e-tests/tests/rest.rs b/crates/sui-e2e-tests/tests/rest.rs index ff408edee6549..629801f4f36c5 100644 --- a/crates/sui-e2e-tests/tests/rest.rs +++ b/crates/sui-e2e-tests/tests/rest.rs @@ -1,10 +1,22 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use shared_crypto::intent::Intent; +use sui_keys::keystore::AccountKeystore; use sui_macros::sim_test; +use sui_rest_api::client::reqwest::StatusCode; use sui_rest_api::client::BalanceChange; +use sui_rest_api::transactions::ResolveTransactionQueryParameters; use sui_rest_api::Client; use sui_rest_api::ExecuteTransactionQueryParameters; +use sui_sdk_types::types::Argument; +use sui_sdk_types::types::Command; +use sui_sdk_types::types::TransactionExpiration; +use sui_sdk_types::types::UnresolvedGasPayment; +use sui_sdk_types::types::UnresolvedInputArgument; +use sui_sdk_types::types::UnresolvedProgrammableTransaction; +use sui_sdk_types::types::UnresolvedTransaction; +use sui_sdk_types::types::UnresolvedValue; use sui_test_transaction_builder::make_transfer_sui_transaction; use sui_types::base_types::SuiAddress; use sui_types::effects::TransactionEffectsAPI; @@ -53,3 +65,419 @@ async fn execute_transaction_transfer() { assert_eq!(actual, expected); } + +#[sim_test] +async fn resolve_transaction_simple_transfer() { + let test_cluster = TestClusterBuilder::new().build().await; + + let client = Client::new(test_cluster.rpc_url()); + let recipient = SuiAddress::random_for_testing_only(); + + let (sender, mut gas) = test_cluster.wallet.get_one_account().await.unwrap(); + gas.sort_by_key(|object_ref| object_ref.0); + let obj_to_send = gas.first().unwrap().0; + + let unresolved_transaction = UnresolvedTransaction { + ptb: UnresolvedProgrammableTransaction { + inputs: vec![ + UnresolvedInputArgument { + object_id: Some(obj_to_send.into()), + ..Default::default() + }, + UnresolvedInputArgument { + value: Some(UnresolvedValue::String(recipient.to_string())), + ..Default::default() + }, + ], + commands: vec![Command::TransferObjects( + sui_sdk_types::types::TransferObjects { + objects: vec![Argument::Input(0)], + address: Argument::Input(1), + }, + )], + }, + sender: sender.into(), + gas_payment: None, + expiration: TransactionExpiration::None, + }; + + let resolved = client + .inner() + .resolve_transaction_with_parameters( + &unresolved_transaction, + &ResolveTransactionQueryParameters { + simulate: true, + ..Default::default() + }, + ) + .await + .unwrap() + .into_inner(); + + let signed_transaction = test_cluster + .wallet + .sign_transaction(&resolved.transaction.try_into().unwrap()); + let effects = client + .execute_transaction( + &ExecuteTransactionQueryParameters::default(), + &signed_transaction, + ) + .await + .unwrap() + .effects; + + assert!(effects.status().is_ok()); + assert_eq!( + resolved.simulation.unwrap().effects, + effects.try_into().unwrap() + ); +} + +#[sim_test] +async fn resolve_transaction_transfer_with_sponsor() { + let test_cluster = TestClusterBuilder::new().build().await; + + let client = Client::new(test_cluster.rpc_url()); + let recipient = SuiAddress::random_for_testing_only(); + + let (sender, gas) = test_cluster.wallet.get_one_account().await.unwrap(); + let obj_to_send = gas.first().unwrap().0; + let sponsor = test_cluster.wallet.get_addresses()[1]; + + let unresolved_transaction = UnresolvedTransaction { + ptb: UnresolvedProgrammableTransaction { + inputs: vec![ + UnresolvedInputArgument { + object_id: Some(obj_to_send.into()), + ..Default::default() + }, + UnresolvedInputArgument { + value: Some(UnresolvedValue::String(recipient.to_string())), + ..Default::default() + }, + ], + commands: vec![Command::TransferObjects( + sui_sdk_types::types::TransferObjects { + objects: vec![Argument::Input(0)], + address: Argument::Input(1), + }, + )], + }, + sender: sender.into(), + gas_payment: Some(UnresolvedGasPayment { + objects: vec![], + owner: sponsor.into(), + price: None, + budget: None, + }), + expiration: TransactionExpiration::None, + }; + + let resolved = client + .inner() + .resolve_transaction_with_parameters( + &unresolved_transaction, + &ResolveTransactionQueryParameters { + simulate: true, + ..Default::default() + }, + ) + .await + .unwrap() + .into_inner(); + + let transaction_data = resolved.transaction.clone().try_into().unwrap(); + let sender_sig = test_cluster + .wallet + .config + .keystore + .sign_secure(&sender, &transaction_data, Intent::sui_transaction()) + .unwrap(); + let sponsor_sig = test_cluster + .wallet + .config + .keystore + .sign_secure(&sponsor, &transaction_data, Intent::sui_transaction()) + .unwrap(); + + let signed_transaction = sui_types::transaction::Transaction::from_data( + transaction_data, + vec![sender_sig, sponsor_sig], + ); + let effects = client + .execute_transaction( + &ExecuteTransactionQueryParameters::default(), + &signed_transaction, + ) + .await + .unwrap() + .effects; + + assert!(effects.status().is_ok()); + assert_eq!( + resolved.simulation.unwrap().effects, + effects.try_into().unwrap() + ); +} + +#[sim_test] +async fn resolve_transaction_borrowed_shared_object() { + let test_cluster = TestClusterBuilder::new().build().await; + + let client = Client::new(test_cluster.rpc_url()); + + let sender = test_cluster.wallet.get_addresses()[0]; + + let unresolved_transaction = UnresolvedTransaction { + ptb: UnresolvedProgrammableTransaction { + inputs: vec![UnresolvedInputArgument { + object_id: Some("0x6".parse().unwrap()), + ..Default::default() + }], + commands: vec![Command::MoveCall(sui_sdk_types::types::MoveCall { + package: "0x2".parse().unwrap(), + module: "clock".parse().unwrap(), + function: "timestamp_ms".parse().unwrap(), + type_arguments: vec![], + arguments: vec![Argument::Input(0)], + })], + }, + sender: sender.into(), + gas_payment: None, + expiration: TransactionExpiration::None, + }; + + let resolved = client + .inner() + .resolve_transaction_with_parameters( + &unresolved_transaction, + &ResolveTransactionQueryParameters { + simulate: true, + ..Default::default() + }, + ) + .await + .unwrap() + .into_inner(); + + let signed_transaction = test_cluster + .wallet + .sign_transaction(&resolved.transaction.try_into().unwrap()); + let effects = client + .execute_transaction( + &ExecuteTransactionQueryParameters::default(), + &signed_transaction, + ) + .await + .unwrap() + .effects; + + assert!(effects.status().is_ok()); +} + +#[sim_test] +async fn resolve_transaction_mutable_shared_object() { + let test_cluster = TestClusterBuilder::new().build().await; + + let client = Client::new(test_cluster.rpc_url()); + + let (sender, mut gas) = test_cluster.wallet.get_one_account().await.unwrap(); + gas.sort_by_key(|object_ref| object_ref.0); + let obj_to_stake = gas.first().unwrap().0; + let validator_address = client + .inner() + .get_system_state_summary() + .await + .unwrap() + .inner() + .active_validators + .first() + .unwrap() + .address; + + let unresolved_transaction = UnresolvedTransaction { + ptb: UnresolvedProgrammableTransaction { + inputs: vec![ + UnresolvedInputArgument { + object_id: Some("0x5".parse().unwrap()), + ..Default::default() + }, + UnresolvedInputArgument { + object_id: Some(obj_to_stake.into()), + ..Default::default() + }, + UnresolvedInputArgument { + value: Some(UnresolvedValue::String(validator_address.to_string())), + ..Default::default() + }, + ], + commands: vec![Command::MoveCall(sui_sdk_types::types::MoveCall { + package: "0x3".parse().unwrap(), + module: "sui_system".parse().unwrap(), + function: "request_add_stake".parse().unwrap(), + type_arguments: vec![], + arguments: vec![Argument::Input(0), Argument::Input(1), Argument::Input(2)], + })], + }, + sender: sender.into(), + gas_payment: None, + expiration: TransactionExpiration::None, + }; + + let resolved = client + .inner() + .resolve_transaction_with_parameters( + &unresolved_transaction, + &ResolveTransactionQueryParameters { + simulate: true, + ..Default::default() + }, + ) + .await + .unwrap() + .into_inner(); + + let signed_transaction = test_cluster + .wallet + .sign_transaction(&resolved.transaction.try_into().unwrap()); + let effects = client + .execute_transaction( + &ExecuteTransactionQueryParameters::default(), + &signed_transaction, + ) + .await + .unwrap() + .effects; + + assert!(effects.status().is_ok()); + assert_eq!( + resolved.simulation.unwrap().effects, + effects.try_into().unwrap() + ); +} + +#[sim_test] +async fn resolve_transaction_insufficient_gas() { + let test_cluster = TestClusterBuilder::new().build().await; + let client = Client::new(test_cluster.rpc_url()); + + // Test the case where we don't have enough coins/gas for the required budget + let unresolved_transaction = UnresolvedTransaction { + ptb: UnresolvedProgrammableTransaction { + inputs: vec![UnresolvedInputArgument { + object_id: Some("0x6".parse().unwrap()), + ..Default::default() + }], + commands: vec![Command::MoveCall(sui_sdk_types::types::MoveCall { + package: "0x2".parse().unwrap(), + module: "clock".parse().unwrap(), + function: "timestamp_ms".parse().unwrap(), + type_arguments: vec![], + arguments: vec![Argument::Input(0)], + })], + }, + sender: SuiAddress::random_for_testing_only().into(), // random account with no gas + gas_payment: None, + expiration: TransactionExpiration::None, + }; + + let error = client + .inner() + .resolve_transaction(&unresolved_transaction) + .await + .unwrap_err(); + + assert_eq!(error.status(), Some(StatusCode::BAD_REQUEST)); + assert_contains( + error.message().unwrap_or_default(), + "unable to select sufficient gas", + ); +} + +fn assert_contains(haystack: &str, needle: &str) { + if !haystack.contains(needle) { + panic!("{haystack:?} does not contain {needle:?}"); + } +} + +#[sim_test] +async fn resolve_transaction_with_raw_json() { + let test_cluster = TestClusterBuilder::new().build().await; + + let client = Client::new(test_cluster.rpc_url()); + let recipient = SuiAddress::random_for_testing_only(); + + let (sender, mut gas) = test_cluster.wallet.get_one_account().await.unwrap(); + gas.sort_by_key(|object_ref| object_ref.0); + let obj_to_send = gas.first().unwrap().0; + + let unresolved_transaction = serde_json::json!({ + "inputs": [ + { + "object_id": obj_to_send + }, + { + "value": 1 + }, + { + "value": recipient + } + ], + + "commands": [ + { + "command": "split_coins", + "coin": { "input": 0 }, + "amounts": [ + { + "input": 1, + }, + { + "input": 1, + } + ] + }, + { + "command": "transfer_objects", + "objects": [ + { "result": [0, 1] }, + { "result": [0, 0] } + ], + "address": { "input": 2 } + } + ], + + "sender": sender + }); + + let resolved = client + .inner() + .resolve_transaction_with_parameters( + &serde_json::from_value(unresolved_transaction).unwrap(), + &ResolveTransactionQueryParameters { + simulate: true, + ..Default::default() + }, + ) + .await + .unwrap() + .into_inner(); + + let signed_transaction = test_cluster + .wallet + .sign_transaction(&resolved.transaction.try_into().unwrap()); + let effects = client + .execute_transaction( + &ExecuteTransactionQueryParameters::default(), + &signed_transaction, + ) + .await + .unwrap() + .effects; + + assert!(effects.status().is_ok(), "{:?}", effects.status()); + assert_eq!( + resolved.simulation.unwrap().effects, + effects.try_into().unwrap() + ); +} diff --git a/crates/sui-e2e-tests/tests/simulator_tests.rs b/crates/sui-e2e-tests/tests/simulator_tests.rs index ef9cc70c0da2e..205a39920797f 100644 --- a/crates/sui-e2e-tests/tests/simulator_tests.rs +++ b/crates/sui-e2e-tests/tests/simulator_tests.rs @@ -11,7 +11,6 @@ use rand::{ Rng, }; use std::collections::{HashMap, HashSet}; -use sui_protocol_config::ProtocolConfig; use sui_test_transaction_builder::make_transfer_sui_transaction; use tokio::time::{sleep, Duration, Instant}; use tracing::{debug, trace}; @@ -126,15 +125,6 @@ async fn test_hash_collections() { // repeatable and deterministic. #[sim_test(check_determinism)] async fn test_net_determinism() { - let _guard = ProtocolConfig::apply_overrides_for_testing(|_, mut config| { - // TODO: this test fails due to some non-determinism caused by submitting messages to - // consensus. It does not appear to be caused by this feature itself, so I'm disabling this - // until I have time to debug further. - config.set_enable_jwk_consensus_updates_for_testing(false); - config.set_random_beacon_for_testing(false); - config - }); - let mut test_cluster = TestClusterBuilder::new().build().await; let txn = make_transfer_sui_transaction(&test_cluster.wallet, None, None).await; diff --git a/crates/sui-e2e-tests/tests/traffic_control_tests.rs b/crates/sui-e2e-tests/tests/traffic_control_tests.rs index 2bac5e39d8b42..46d1ab050245d 100644 --- a/crates/sui-e2e-tests/tests/traffic_control_tests.rs +++ b/crates/sui-e2e-tests/tests/traffic_control_tests.rs @@ -11,6 +11,7 @@ use jsonrpsee::{ rpc_params, }; use std::fs::File; +use std::num::NonZeroUsize; use std::time::Duration; use sui_core::authority_client::make_network_authority_clients_with_network_config; use sui_core::authority_client::AuthorityAPI; @@ -45,6 +46,7 @@ async fn test_validator_traffic_control_noop() -> Result<(), anyhow::Error> { ..Default::default() }; let network_config = ConfigBuilder::new_with_temp_dir() + .committee_size(NonZeroUsize::new(4).unwrap()) .with_policy_config(Some(policy_config)) .build(); let test_cluster = TestClusterBuilder::new() @@ -88,6 +90,7 @@ async fn test_validator_traffic_control_ok() -> Result<(), anyhow::Error> { ..Default::default() }; let network_config = ConfigBuilder::new_with_temp_dir() + .committee_size(NonZeroUsize::new(4).unwrap()) .with_policy_config(Some(policy_config)) .build(); let test_cluster = TestClusterBuilder::new() @@ -133,6 +136,7 @@ async fn test_validator_traffic_control_dry_run() -> Result<(), anyhow::Error> { ..Default::default() }; let network_config = ConfigBuilder::new_with_temp_dir() + .committee_size(NonZeroUsize::new(4).unwrap()) .with_policy_config(Some(policy_config)) .build(); let test_cluster = TestClusterBuilder::new() @@ -217,6 +221,7 @@ async fn test_validator_traffic_control_error_blocked() -> Result<(), anyhow::Er ..Default::default() }; let network_config = ConfigBuilder::new_with_temp_dir() + .committee_size(NonZeroUsize::new(4).unwrap()) .with_policy_config(Some(policy_config)) .build(); let committee = network_config.committee_with_network(); @@ -546,6 +551,63 @@ async fn test_fullnode_traffic_control_error_blocked() -> Result<(), anyhow::Err panic!("Expected spam policy to trigger within {txn_count} requests"); } +#[tokio::test] +async fn test_fullnode_traffic_control_error_blocked() -> Result<(), anyhow::Error> { + let txn_count = 5; + let policy_config = PolicyConfig { + connection_blocklist_ttl_sec: 3, + error_policy_type: PolicyType::TestNConnIP(txn_count - 1), + dry_run: false, + ..Default::default() + }; + let test_cluster = TestClusterBuilder::new() + .with_fullnode_policy_config(Some(policy_config)) + .build() + .await; + + let jsonrpc_client = &test_cluster.fullnode_handle.rpc_client; + let context = test_cluster.wallet; + + let mut txns = batch_make_transfer_transactions(&context, txn_count as usize).await; + assert!( + txns.len() >= txn_count as usize, + "Expect at least {} txns. Do we generate enough gas objects during genesis?", + txn_count, + ); + + // it should take no more than 4 requests to be added to the blocklist + for _ in 0..txn_count { + let txn = txns.swap_remove(0); + let tx_digest = txn.digest(); + let (tx_bytes, _signatures) = txn.to_tx_bytes_and_signatures(); + // create invalid (empty) client signature + let signatures: Vec = vec![]; + let params = rpc_params![ + tx_bytes, + signatures, + SuiTransactionBlockResponseOptions::new(), + ExecuteTransactionRequestType::WaitForLocalExecution + ]; + let response: RpcResult = jsonrpc_client + .request("sui_executeTransactionBlock", params.clone()) + .await; + if let Err(err) = response { + if err.to_string().contains("Too many requests") { + return Ok(()); + } + } else { + let SuiTransactionBlockResponse { + digest, + confirmed_local_execution, + .. + } = response.unwrap(); + assert_eq!(&digest, tx_digest); + assert!(confirmed_local_execution.unwrap()); + } + } + panic!("Expected spam policy to trigger within {txn_count} requests"); +} + #[tokio::test] async fn test_validator_traffic_control_error_delegated() -> Result<(), anyhow::Error> { let n = 5; @@ -568,6 +630,7 @@ async fn test_validator_traffic_control_error_delegated() -> Result<(), anyhow:: drain_timeout_secs: 10, }; let network_config = ConfigBuilder::new_with_temp_dir() + .committee_size(NonZeroUsize::new(4).unwrap()) .with_policy_config(Some(policy_config)) .with_firewall_config(Some(firewall_config)) .build(); @@ -593,7 +656,7 @@ async fn test_validator_traffic_control_error_delegated() -> Result<(), anyhow:: let mut server = NodeFwTestServer::new(); server.start(port).await; // await for the server to start - tokio::time::sleep(tokio::time::Duration::from_secs(3)).await; + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; // it should take no more than 4 requests to be added to the blocklist for _ in 0..n { @@ -720,7 +783,7 @@ async fn test_traffic_control_dead_mans_switch() -> Result<(), anyhow::Error> { // NOTE: we need to hold onto this tc handle to ensure we don't inadvertently close // the receive channel (this would cause traffic controller to exit the loop and thus // we will never engage the dead mans switch) - let _tc = TrafficController::spawn_for_test(policy_config, Some(firewall_config)); + let _tc = TrafficController::init_for_test(policy_config, Some(firewall_config)); assert!( !drain_path.exists(), "Expected drain file to not exist after startup unless previously set", @@ -877,6 +940,32 @@ async fn test_traffic_sketch_with_sampled_spam() { assert!(metrics.num_blocked > (expected_requests / 5) - 1000); } +#[sim_test] +async fn test_traffic_sketch_allowlist_mode() { + let policy_config = PolicyConfig { + connection_blocklist_ttl_sec: 1, + proxy_blocklist_ttl_sec: 1, + // first two clients allowlisted, rest blocked + allow_list: Some(vec![String::from("127.0.0.0"), String::from("127.0.0.1")]), + dry_run: false, + ..Default::default() + }; + let metrics = TrafficSim::run( + policy_config, + 4, // num_clients + 10_000, // per_client_tps + Duration::from_secs(10), + true, // report + ) + .await; + + let expected_requests = 10_000 * 10 * 4; + // ~half of all requests blocked + assert!(metrics.num_blocked >= expected_requests / 2 - 1000); + assert!(metrics.num_requests > expected_requests - 1_000); + assert!(metrics.num_requests < expected_requests + 200); +} + async fn assert_traffic_control_ok(mut test_cluster: TestCluster) -> Result<(), anyhow::Error> { let context = &mut test_cluster.wallet; let jsonrpc_client = &test_cluster.fullnode_handle.rpc_client; diff --git a/crates/sui-faucet/Cargo.toml b/crates/sui-faucet/Cargo.toml index f8a99e7d2f936..fee9e1d93af61 100644 --- a/crates/sui-faucet/Cargo.toml +++ b/crates/sui-faucet/Cargo.toml @@ -27,6 +27,7 @@ ttl_cache.workspace = true eyre.workspace = true tempfile.workspace = true parking_lot.workspace = true +tonic.workspace = true sui-json-rpc-types.workspace = true sui-types.workspace = true @@ -38,6 +39,7 @@ telemetry-subscribers.workspace = true typed-store.workspace = true shared-crypto.workspace = true async-recursion.workspace = true +mysten-network.workspace = true [dev-dependencies] test-cluster.workspace = true diff --git a/crates/sui-faucet/src/faucet/simple_faucet.rs b/crates/sui-faucet/src/faucet/simple_faucet.rs index 00133d92dcd03..6bd0fda59e285 100644 --- a/crates/sui-faucet/src/faucet/simple_faucet.rs +++ b/crates/sui-faucet/src/faucet/simple_faucet.rs @@ -117,6 +117,9 @@ impl SimpleFaucet { .filter(|coin| coin.0.balance.value() >= (config.amount * config.num_coins as u64)) .collect::>(); let metrics = FaucetMetrics::new(prometheus_registry); + // set initial balance when faucet starts + let balance = coins.iter().map(|coin| coin.0.balance.value()).sum::(); + metrics.balance.set(balance as i64); let wal = WriteAheadLog::open(wal_path); let mut pending = vec![]; @@ -482,6 +485,17 @@ impl SimpleFaucet { } else { self.recycle_gas_coin(coin_id, uuid).await; } + + if let Some(ref balances) = result.balance_changes { + let sui_used = balances + .iter() + .find(|balance| balance.owner == self.active_address) + .map(|b| b.amount) + .unwrap_or_else(|| 0); + info!("SUI used in this tx {}: {}", tx_digest, sui_used); + self.metrics.balance.add(sui_used as i64); + } + Ok(result) } } @@ -618,11 +632,14 @@ impl SimpleFaucet { let tx_digest = tx.digest(); let client = self.wallet.get_client().await?; + Ok(client .quorum_driver_api() .execute_transaction_block( tx.clone(), - SuiTransactionBlockResponseOptions::new().with_effects(), + SuiTransactionBlockResponseOptions::new() + .with_effects() + .with_balance_changes(), Some(ExecuteTransactionRequestType::WaitForLocalExecution), ) .await @@ -1636,7 +1653,7 @@ mod tests { let candidates = faucet.drain_gas_queue(gas_coins.len() - 1).await; assert_eq!(discarded, 1); - assert!(candidates.get(&tiny_coin_id).is_none()); + assert!(!candidates.contains(&tiny_coin_id)); } #[tokio::test] diff --git a/crates/sui-faucet/src/metrics.rs b/crates/sui-faucet/src/metrics.rs index e67bbc334cd27..9571701835e20 100644 --- a/crates/sui-faucet/src/metrics.rs +++ b/crates/sui-faucet/src/metrics.rs @@ -2,28 +2,33 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use mysten_network::metrics::MetricsCallbackProvider; use prometheus::{ - register_histogram_with_registry, register_int_counter_with_registry, - register_int_gauge_with_registry, Histogram, IntCounter, IntGauge, Registry, + register_histogram_vec_with_registry, register_int_counter_vec_with_registry, + register_int_gauge_vec_with_registry, register_int_gauge_with_registry, HistogramVec, + IntCounterVec, IntGauge, IntGaugeVec, Registry, }; +use std::time::Duration; +use tonic::Code; /// Prometheus metrics which can be displayed in Grafana, queried and alerted on /// Metrics relevant to the requests coming into the service #[derive(Clone, Debug)] pub struct RequestMetrics { - pub(crate) total_requests_received: IntCounter, - pub(crate) total_requests_succeeded: IntCounter, - pub(crate) total_requests_shed: IntCounter, - pub(crate) total_requests_failed: IntCounter, - pub(crate) total_requests_disconnected: IntCounter, - pub(crate) current_requests_in_flight: IntGauge, - pub(crate) process_latency: Histogram, + pub(crate) total_requests_received: IntCounterVec, + pub(crate) total_requests_succeeded: IntCounterVec, + pub(crate) total_requests_shed: IntCounterVec, + pub(crate) total_requests_failed: IntCounterVec, + pub(crate) total_requests_disconnected: IntCounterVec, + pub(crate) current_requests_in_flight: IntGaugeVec, + pub(crate) process_latency: HistogramVec, } /// Metrics relevant to the running of the service #[derive(Clone, Debug)] pub struct FaucetMetrics { + pub(crate) balance: IntGauge, pub(crate) current_executions_in_flight: IntGauge, pub(crate) total_available_coins: IntGauge, pub(crate) total_discarded_coins: IntGauge, @@ -37,46 +42,53 @@ const LATENCY_SEC_BUCKETS: &[f64] = &[ impl RequestMetrics { pub fn new(registry: &Registry) -> Self { Self { - total_requests_received: register_int_counter_with_registry!( + total_requests_received: register_int_counter_vec_with_registry!( "total_requests_received", "Total number of requests received in Faucet", + &["path"], registry, ) .unwrap(), - total_requests_succeeded: register_int_counter_with_registry!( + total_requests_succeeded: register_int_counter_vec_with_registry!( "total_requests_succeeded", "Total number of requests processed successfully in Faucet", + &["path"], registry, ) .unwrap(), - total_requests_shed: register_int_counter_with_registry!( + total_requests_shed: register_int_counter_vec_with_registry!( "total_requests_shed", "Total number of requests that were dropped because the service was saturated", + &["path"], registry, ) .unwrap(), - total_requests_failed: register_int_counter_with_registry!( + total_requests_failed: register_int_counter_vec_with_registry!( "total_requests_failed", "Total number of requests that started but failed with an uncaught error", + &["path"], registry, ) .unwrap(), - total_requests_disconnected: register_int_counter_with_registry!( + total_requests_disconnected: register_int_counter_vec_with_registry!( "total_requests_disconnected", "Total number of requests where the client disconnected before the service \ returned a response", + &["path"], registry, ) .unwrap(), - current_requests_in_flight: register_int_gauge_with_registry!( + current_requests_in_flight: register_int_gauge_vec_with_registry!( "current_requests_in_flight", "Current number of requests being processed in Faucet", + &["path"], registry, ) .unwrap(), - process_latency: register_histogram_with_registry!( + process_latency: register_histogram_vec_with_registry!( "process_latency", "Latency of processing a Faucet request", + &["path"], LATENCY_SEC_BUCKETS.to_vec(), registry, ) @@ -88,6 +100,11 @@ impl RequestMetrics { impl FaucetMetrics { pub fn new(registry: &Registry) -> Self { Self { + balance: register_int_gauge_with_registry!( + "balance", + "Current balance of the all the available coins", + registry, + ).unwrap(), current_executions_in_flight: register_int_gauge_with_registry!( "current_executions_in_flight", "Current number of transactions being executed in Faucet", @@ -115,3 +132,50 @@ impl FaucetMetrics { } } } + +impl MetricsCallbackProvider for RequestMetrics { + fn on_request(&self, path: String) { + self.total_requests_received + .with_label_values(&[path.as_str()]) + .inc(); + } + + fn on_response(&self, path: String, latency: Duration, _status: u16, grpc_status_code: Code) { + self.process_latency + .with_label_values(&[path.as_str()]) + .observe(latency.as_secs_f64()); + + match grpc_status_code { + Code::Ok => { + self.total_requests_succeeded + .with_label_values(&[path.as_str()]) + .inc(); + } + Code::Unavailable | Code::ResourceExhausted => { + self.total_requests_shed + .with_label_values(&[path.as_str()]) + .inc(); + } + _ => { + self.total_requests_failed + .with_label_values(&[path.as_str()]) + .inc(); + } + } + } + + fn on_start(&self, path: &str) { + self.current_requests_in_flight + .with_label_values(&[path]) + .inc(); + } + + fn on_drop(&self, path: &str) { + self.total_requests_disconnected + .with_label_values(&[path]) + .inc(); + self.current_requests_in_flight + .with_label_values(&[path]) + .dec(); + } +} diff --git a/crates/sui-faucet/src/metrics_layer.rs b/crates/sui-faucet/src/metrics_layer.rs index 6b2084f00c625..7e7dfe3569af9 100644 --- a/crates/sui-faucet/src/metrics_layer.rs +++ b/crates/sui-faucet/src/metrics_layer.rs @@ -14,6 +14,7 @@ use tower::{load_shed::error::Overloaded, BoxError, Layer, Service, ServiceExt}; use tracing::{error, info, warn}; use crate::metrics::RequestMetrics; +use http::Request; /// Tower Layer for tracking metrics in Prometheus related to number, success-rate and latency of /// requests running through service. @@ -35,6 +36,7 @@ pub struct RequestMetricsFuture { struct MetricsGuard { timer: Option, metrics: Arc, + path: String, } impl RequestMetricsLayer { @@ -55,11 +57,14 @@ impl Layer for RequestMetricsLayer { } } -impl Service for RequestMetricsService +impl Service> for RequestMetricsService where - Inner: Service, Error = BoxError> + Clone + Send + 'static, + Inner: Service, Response = http::Response, Error = BoxError> + + Clone + + Send + + 'static, Inner::Future: Send, - Req: Send + 'static, + Body: Send + 'static, { type Response = Inner::Response; type Error = BoxError; @@ -69,18 +74,19 @@ where self.inner.poll_ready(ctx) } - fn call(&mut self, req: Req) -> Self::Future { - let metrics = MetricsGuard::new(self.metrics.clone()); + fn call(&mut self, req: Request) -> Self::Future { + let path = req.uri().path().to_string(); + let metrics = MetricsGuard::new(self.metrics.clone(), &path); let inner = self.inner.clone(); let future = Box::pin(async move { let resp = inner.oneshot(req).await; match &resp { - Result::Ok(resp) if !resp.status().is_success() => { + Ok(resp) if !resp.status().is_success() => { metrics.failed(None, Some(resp.status())) } - Result::Ok(_) => metrics.succeeded(), - Result::Err(err) => { + Ok(_) => metrics.succeeded(), + Err(err) => { if err.is::() { metrics.shed(); } else { @@ -104,54 +110,95 @@ impl Future for RequestMetricsFuture { } impl MetricsGuard { - fn new(metrics: Arc) -> Self { - metrics.total_requests_received.inc(); - metrics.current_requests_in_flight.inc(); + fn new(metrics: Arc, path: &str) -> Self { + metrics + .total_requests_received + .with_label_values(&[path]) + .inc(); + metrics + .current_requests_in_flight + .with_label_values(&[path]) + .inc(); MetricsGuard { - timer: Some(metrics.process_latency.start_timer()), + timer: Some( + metrics + .process_latency + .with_label_values(&[path]) + .start_timer(), + ), metrics, + path: path.to_string(), } } fn succeeded(mut self) { - let elapsed = self.timer.take().unwrap().stop_and_record(); - self.metrics.total_requests_succeeded.inc(); - info!("Request succeeded in {:.2}s", elapsed); + if let Some(timer) = self.timer.take() { + let elapsed = timer.stop_and_record(); + self.metrics + .total_requests_succeeded + .with_label_values(&[&self.path]) + .inc(); + info!( + "Request succeeded for path {} in {:.2}s", + self.path, elapsed + ); + } } fn failed(mut self, error: Option<&BoxError>, status: Option) { - let elapsed = self.timer.take().unwrap().stop_and_record(); - let code = status - .map(|c| c.as_str().to_string()) - .unwrap_or_else(|| "no_code".to_string()); - self.metrics.total_requests_failed.inc(); - - if let Some(err) = error { - error!( - "Request failed in {:.2}s, error {:?}, code {}", - elapsed, err, code - ); - } else { - warn!("Request failed in {:.2}s, code: {}", elapsed, code); + if let Some(timer) = self.timer.take() { + let elapsed = timer.stop_and_record(); + self.metrics + .total_requests_failed + .with_label_values(&[&self.path]) + .inc(); + + if let Some(err) = error { + error!( + "Request failed for path {} in {:.2}s, error {:?}", + self.path, elapsed, err + ); + } else if let Some(status) = status { + error!( + "Request failed for path {} in {:.2}s with status: {}", + self.path, elapsed, status + ); + } else { + warn!("Request failed for path {} in {:.2}s", self.path, elapsed); + } } } fn shed(mut self) { - let elapsed = self.timer.take().unwrap().stop_and_record(); - self.metrics.total_requests_shed.inc(); - info!("Request shed in {:.2}s", elapsed); + if let Some(timer) = self.timer.take() { + let elapsed = timer.stop_and_record(); + self.metrics + .total_requests_shed + .with_label_values(&[&self.path]) + .inc(); + info!("Request shed for path {} in {:.2}s", self.path, elapsed); + } } } impl Drop for MetricsGuard { fn drop(&mut self) { - self.metrics.current_requests_in_flight.dec(); + self.metrics + .current_requests_in_flight + .with_label_values(&[&self.path]) + .dec(); // Request was still in flight when the guard was dropped, implying the client disconnected. if let Some(timer) = self.timer.take() { let elapsed = timer.stop_and_record(); - self.metrics.total_requests_disconnected.inc(); - info!("Request disconnected in {:.2}s", elapsed); + self.metrics + .total_requests_disconnected + .with_label_values(&[&self.path]) + .inc(); + info!( + "Request disconnected for path {} in {:.2}s", + self.path, elapsed + ); } } } diff --git a/crates/sui-faucet/src/server.rs b/crates/sui-faucet/src/server.rs index cea9242ad9e64..158ee1536a140 100644 --- a/crates/sui-faucet/src/server.rs +++ b/crates/sui-faucet/src/server.rs @@ -192,7 +192,7 @@ async fn request_status( } } Err(e) => ( - StatusCode::INTERNAL_SERVER_ERROR, + StatusCode::BAD_REQUEST, Json(BatchStatusFaucetResponse::from(FaucetError::Internal( e.to_string(), ))), diff --git a/crates/sui-framework-snapshot/bytecode_snapshot/62/0x0000000000000000000000000000000000000000000000000000000000000001 b/crates/sui-framework-snapshot/bytecode_snapshot/62/0x0000000000000000000000000000000000000000000000000000000000000001 new file mode 100644 index 0000000000000000000000000000000000000000..b9845e76190cee1ca53a19f6efe841a5a9196dda GIT binary patch literal 14381 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 literal 0 HcmV?d00001 diff --git a/crates/sui-framework-snapshot/bytecode_snapshot/62/0x0000000000000000000000000000000000000000000000000000000000000002 b/crates/sui-framework-snapshot/bytecode_snapshot/62/0x0000000000000000000000000000000000000000000000000000000000000002 new file mode 100644 index 0000000000000000000000000000000000000000..0153f6cc52752e4c343b28072be6fdb57101cf96 GIT binary patch literal 66985 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ˈp`w;Dcw~u(su6h5*!rk&2tjk=>$W8Ef3H+7; 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ˈp`w;Dcw~u(su6h5*!rk&2tjk=>$W8Ef3H+7; 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 otherwise.

public fun is_printable_char(byte: u8): bool {
     byte >= 0x20 && // Disallow metacharacters
-    byte <= 0x7E // Don't allow DEL metacharacter
+        byte <= 0x7E // Don't allow DEL metacharacter
 }
 
diff --git a/crates/sui-framework/docs/move-stdlib/bcs.md b/crates/sui-framework/docs/move-stdlib/bcs.md index 4a02c8bfa35b7..633d619dd44c7 100644 --- a/crates/sui-framework/docs/move-stdlib/bcs.md +++ b/crates/sui-framework/docs/move-stdlib/bcs.md @@ -31,7 +31,7 @@ Return the binary representation of v in BCS (Binary Canonical Seri Implementation -
native public fun to_bytes<MoveValue>(v: &MoveValue): vector<u8>;
+
public native fun to_bytes<MoveValue>(v: &MoveValue): vector<u8>;
 
diff --git a/crates/sui-framework/docs/move-stdlib/option.md b/crates/sui-framework/docs/move-stdlib/option.md index bffabbe535ca2..bbc74e2ee87b7 100644 --- a/crates/sui-framework/docs/move-stdlib/option.md +++ b/crates/sui-framework/docs/move-stdlib/option.md @@ -285,10 +285,7 @@ Return default if t does not hold a value Implementation -
public fun get_with_default<Element: copy + drop>(
-    t: &Option<Element>,
-    default: Element,
-): Element {
+
public fun get_with_default<Element: copy + drop>(t: &Option<Element>, default: Element): Element {
     let vec_ref = &t.vec;
     if (vec_ref.is_empty()) default
     else vec_ref[0]
@@ -432,7 +429,7 @@ Different from swap(), swap_or_fill() allows for t not holding a va
 
public fun swap_or_fill<Element>(t: &mut Option<Element>, e: Element): Option<Element> {
     let vec_ref = &mut t.vec;
     let old_value = if (vec_ref.is_empty()) none()
-        else some(vec_ref.pop_back());
+    else some(vec_ref.pop_back());
     vec_ref.push_back(e);
     old_value
 }
diff --git a/crates/sui-framework/docs/move-stdlib/string.md b/crates/sui-framework/docs/move-stdlib/string.md
index 420d672321165..16a0f2cb1b5c6 100644
--- a/crates/sui-framework/docs/move-stdlib/string.md
+++ b/crates/sui-framework/docs/move-stdlib/string.md
@@ -365,10 +365,7 @@ must be at a valid utf8 char boundary.
 
 
public fun insert(s: &mut String, at: u64, o: String) {
     let bytes = &s.bytes;
-    assert!(
-        at <= bytes.length() && internal_is_char_boundary(bytes, at),
-        EInvalidIndex,
-    );
+    assert!(at <= bytes.length() && internal_is_char_boundary(bytes, at), EInvalidIndex);
     let l = s.length();
     let mut front = s.substring(0, at);
     let end = s.substring(at, l);
@@ -406,9 +403,9 @@ guaranteeing that the result is valid utf8.
     let l = bytes.length();
     assert!(
         j <= l &&
-        i <= j &&
-        internal_is_char_boundary(bytes, i) &&
-        internal_is_char_boundary(bytes, j),
+            i <= j &&
+            internal_is_char_boundary(bytes, i) &&
+            internal_is_char_boundary(bytes, j),
         EInvalidIndex,
     );
     String { bytes: internal_sub_string(bytes, i, j) }
diff --git a/crates/sui-framework/docs/move-stdlib/type_name.md b/crates/sui-framework/docs/move-stdlib/type_name.md
index 3b0bf0df95028..b2ae36229709b 100644
--- a/crates/sui-framework/docs/move-stdlib/type_name.md
+++ b/crates/sui-framework/docs/move-stdlib/type_name.md
@@ -213,22 +213,22 @@ u8, u16, u32, u64, u128, u256, bool, address, vector.
 
public fun is_primitive(self: &TypeName): bool {
     let bytes = self.name.as_bytes();
     bytes == &b"bool" ||
-    bytes == &b"u8" ||
-    bytes == &b"u16" ||
-    bytes == &b"u32" ||
-    bytes == &b"u64" ||
-    bytes == &b"u128" ||
-    bytes == &b"u256" ||
-    bytes == &b"address" ||
-    (
-        bytes.length() >= 6 &&
-        bytes[0] == ASCII_V &&
-        bytes[1] == ASCII_E &&
-        bytes[2] == ASCII_C &&
-        bytes[3] == ASCII_T &&
-        bytes[4] == ASCII_O &&
-        bytes[5] == ASCII_R,
-    )
+        bytes == &b"u8" ||
+        bytes == &b"u16" ||
+        bytes == &b"u32" ||
+        bytes == &b"u64" ||
+        bytes == &b"u128" ||
+        bytes == &b"u256" ||
+        bytes == &b"address" ||
+        (
+            bytes.length() >= 6 &&
+            bytes[0] == ASCII_V &&
+            bytes[1] == ASCII_E &&
+            bytes[2] == ASCII_C &&
+            bytes[3] == ASCII_T &&
+            bytes[4] == ASCII_O &&
+            bytes[5] == ASCII_R,
+        )
 }
 
diff --git a/crates/sui-framework/docs/move-stdlib/vector.md b/crates/sui-framework/docs/move-stdlib/vector.md index 7f9d3d1f48877..0832eebeb7d27 100644 --- a/crates/sui-framework/docs/move-stdlib/vector.md +++ b/crates/sui-framework/docs/move-stdlib/vector.md @@ -436,7 +436,10 @@ Aborts if i is out of bounds. if (i >= len) abort EINDEX_OUT_OF_BOUNDS; len = len - 1; - while (i < len) v.swap(i, { i = i + 1; i }); + while (i < len) v.swap(i, { + i = i + 1; + i + }); v.pop_back() }
diff --git a/crates/sui-framework/docs/sui-framework/authenticator_state.md b/crates/sui-framework/docs/sui-framework/authenticator_state.md index 93d44e078b5d6..b529ea58e6ee0 100644 --- a/crates/sui-framework/docs/sui-framework/authenticator_state.md +++ b/crates/sui-framework/docs/sui-framework/authenticator_state.md @@ -308,9 +308,9 @@ Sender is not @0x0 the system address.
fun jwk_equal(a: &JWK, b: &JWK): bool {
     (&a.kty == &b.kty) &&
-       (&a.e == &b.e) &&
-       (&a.n == &b.n) &&
-       (&a.alg == &b.alg)
+        (&a.e == &b.e) &&
+        (&a.n == &b.n) &&
+        (&a.alg == &b.alg)
 }
 
@@ -484,9 +484,7 @@ Can only be called by genesis or change_epoch transactions. Implementation -
fun load_inner_mut(
-    self: &mut AuthenticatorState,
-): &mut AuthenticatorStateInner {
+
fun load_inner_mut(self: &mut AuthenticatorState): &mut AuthenticatorStateInner {
     let version = self.version;
 
     // replace this with a lazy update function when we add a new version of the inner object.
@@ -518,9 +516,7 @@ Can only be called by genesis or change_epoch transactions.
 Implementation
 
 
-
fun load_inner(
-    self: &AuthenticatorState,
-): &AuthenticatorStateInner {
+
fun load_inner(self: &AuthenticatorState): &AuthenticatorStateInner {
     let version = self.version;
 
     // replace this with a lazy update function when we add a new version of the inner object.
@@ -709,7 +705,8 @@ indicate that the JWK has been validated in the current epoch and should not be
     self: &mut AuthenticatorState,
     // any jwk below this epoch is not retained
     min_epoch: u64,
-    ctx: &TxContext) {
+    ctx: &TxContext,
+) {
     // This will only be called by sui_system::advance_epoch
     assert!(ctx.sender() == @0x0, ENotSystemAddress);
 
@@ -793,10 +790,7 @@ JWK state from the chain.
 Implementation
 
 
-
fun get_active_jwks(
-    self: &AuthenticatorState,
-    ctx: &TxContext,
-): vector<ActiveJwk> {
+
fun get_active_jwks(self: &AuthenticatorState, ctx: &TxContext): vector<ActiveJwk> {
     assert!(ctx.sender() == @0x0, ENotSystemAddress);
     self.load_inner().active_jwks
 }
diff --git a/crates/sui-framework/docs/sui-framework/balance.md b/crates/sui-framework/docs/sui-framework/balance.md
index f901091e319b4..b1f07a40d5384 100644
--- a/crates/sui-framework/docs/sui-framework/balance.md
+++ b/crates/sui-framework/docs/sui-framework/balance.md
@@ -435,10 +435,7 @@ and nowhere else.
 
 
fun create_staking_rewards<T>(value: u64, ctx: &TxContext): Balance<T> {
     assert!(ctx.sender() == @0x0, ENotSystemAddress);
-    assert!(
-        std::type_name::get<T>().into_string().into_bytes() == SUI_TYPE_NAME,
-        ENotSUI,
-    );
+    assert!(std::type_name::get<T>().into_string().into_bytes() == SUI_TYPE_NAME, ENotSUI);
     Balance { value }
 }
 
@@ -467,10 +464,7 @@ and nowhere else.
fun destroy_storage_rebates<T>(self: Balance<T>, ctx: &TxContext) {
     assert!(ctx.sender() == @0x0, ENotSystemAddress);
-    assert!(
-        std::type_name::get<T>().into_string().into_bytes() == SUI_TYPE_NAME,
-        ENotSUI,
-    );
+    assert!(std::type_name::get<T>().into_string().into_bytes() == SUI_TYPE_NAME, ENotSUI);
     let Balance { value: _ } = self;
 }
 
diff --git a/crates/sui-framework/docs/sui-framework/bls12381.md b/crates/sui-framework/docs/sui-framework/bls12381.md index a6f913de466cb..5950e23b99f8f 100644 --- a/crates/sui-framework/docs/sui-framework/bls12381.md +++ b/crates/sui-framework/docs/sui-framework/bls12381.md @@ -299,7 +299,11 @@ BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_, return true. Otherwise, return fals Implementation -
public native fun bls12381_min_sig_verify(signature: &vector<u8>, public_key: &vector<u8>, msg: &vector<u8>): bool;
+
public native fun bls12381_min_sig_verify(
+    signature: &vector<u8>,
+    public_key: &vector<u8>,
+    msg: &vector<u8>,
+): bool;
 
@@ -327,7 +331,11 @@ BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_, return true. Otherwise, return fals Implementation -
public native fun bls12381_min_pk_verify(signature: &vector<u8>, public_key: &vector<u8>, msg: &vector<u8>): bool;
+
public native fun bls12381_min_pk_verify(
+    signature: &vector<u8>,
+    public_key: &vector<u8>,
+    msg: &vector<u8>,
+): bool;
 
@@ -817,7 +825,10 @@ Aborts with EInputTooLong if the vectors are larger than 32 (may in Implementation -
public fun g1_multi_scalar_multiplication(scalars: &vector<Element<Scalar>>, elements: &vector<Element<G1>>): Element<G1> {
+
public fun g1_multi_scalar_multiplication(
+    scalars: &vector<Element<Scalar>>,
+    elements: &vector<Element<G1>>,
+): Element<G1> {
     group_ops::multi_scalar_multiplication(G1_TYPE, scalars, elements)
 }
 
@@ -1064,7 +1075,10 @@ Aborts with EInputTooLong if the vectors are larger than 32 (may in Implementation -
public fun g2_multi_scalar_multiplication(scalars: &vector<Element<Scalar>>, elements: &vector<Element<G2>>): Element<G2> {
+
public fun g2_multi_scalar_multiplication(
+    scalars: &vector<Element<Scalar>>,
+    elements: &vector<Element<G2>>,
+): Element<G2> {
     group_ops::multi_scalar_multiplication(G2_TYPE, scalars, elements)
 }
 
diff --git a/crates/sui-framework/docs/sui-framework/borrow.md b/crates/sui-framework/docs/sui-framework/borrow.md index 0fe949d035fb0..c9af348f4c10c 100644 --- a/crates/sui-framework/docs/sui-framework/borrow.md +++ b/crates/sui-framework/docs/sui-framework/borrow.md @@ -137,7 +137,7 @@ Create a new Referent s
public fun new<T: key + store>(value: T, ctx: &mut TxContext): Referent<T> {
     Referent {
         id: tx_context::fresh_object_address(ctx),
-        value: option::some(value)
+        value: option::some(value),
     }
 }
 
@@ -167,10 +167,13 @@ hot potato. let value = self.value.extract(); let id = object::id(&value); - (value, Borrow { - ref: self.id, - obj: id - }) + ( + value, + Borrow { + ref: self.id, + obj: id, + }, + ) }
diff --git a/crates/sui-framework/docs/sui-framework/clock.md b/crates/sui-framework/docs/sui-framework/clock.md index 4093c882c71ad..3919d8837f026 100644 --- a/crates/sui-framework/docs/sui-framework/clock.md +++ b/crates/sui-framework/docs/sui-framework/clock.md @@ -154,11 +154,7 @@ called exactly once, during genesis. Implementation -
fun consensus_commit_prologue(
-    clock: &mut Clock,
-    timestamp_ms: u64,
-    ctx: &TxContext,
-) {
+
fun consensus_commit_prologue(clock: &mut Clock, timestamp_ms: u64, ctx: &TxContext) {
     // Validator will make a special system call with sender set as 0x0.
     assert!(ctx.sender() == @0x0, ENotSystemAddress);
 
diff --git a/crates/sui-framework/docs/sui-framework/coin.md b/crates/sui-framework/docs/sui-framework/coin.md
index 307d3c710d138..869e0a6406a4c 100644
--- a/crates/sui-framework/docs/sui-framework/coin.md
+++ b/crates/sui-framework/docs/sui-framework/coin.md
@@ -646,12 +646,10 @@ Aborts if value > bal
 Implementation
 
 
-
public fun take<T>(
-    balance: &mut Balance<T>, value: u64, ctx: &mut TxContext,
-): Coin<T> {
+
public fun take<T>(balance: &mut Balance<T>, value: u64, ctx: &mut TxContext): Coin<T> {
     Coin {
         id: object::new(ctx),
-        balance: balance.split(value)
+        balance: balance.split(value),
     }
 }
 
@@ -730,9 +728,7 @@ and the remaining balance is left is self. Implementation -
public fun split<T>(
-    self: &mut Coin<T>, split_amount: u64, ctx: &mut TxContext
-): Coin<T> {
+
public fun split<T>(self: &mut Coin<T>, split_amount: u64, ctx: &mut TxContext): Coin<T> {
     take(&mut self.balance, split_amount, ctx)
 }
 
@@ -758,9 +754,7 @@ Split coin self into n - 1 coins with equal balances. Implementation -
public fun divide_into_n<T>(
-    self: &mut Coin<T>, n: u64, ctx: &mut TxContext
-): vector<Coin<T>> {
+
public fun divide_into_n<T>(self: &mut Coin<T>, n: u64, ctx: &mut TxContext): vector<Coin<T>> {
     assert!(n > 0, EInvalidArg);
     assert!(n <= value(self), ENotEnough);
 
@@ -857,7 +851,7 @@ type, ensuring that there's only one vector<u8>,
     description: vector<u8>,
     icon_url: Option<Url>,
-    ctx: &mut TxContext
+    ctx: &mut TxContext,
 ): (TreasuryCap<T>, CoinMetadata<T>) {
     // Make sure there's only one instance of the type T
     assert!(sui::types::is_one_time_witness(&witness), EBadWitness);
@@ -865,7 +859,7 @@ type, ensuring that there's only one TreasuryCap {
             id: object::new(ctx),
-            total_supply: balance::create_supply(witness)
+            total_supply: balance::create_supply(witness),
         },
         CoinMetadata {
             id: object::new(ctx),
@@ -873,8 +867,8 @@ type, ensuring that there's only one string::utf8(name),
             symbol: ascii::string(symbol),
             description: string::utf8(description),
-            icon_url
-        }
+            icon_url,
+        },
     )
 }
 
@@ -923,7 +917,7 @@ will not change the result of the "contains" APIs. name, description, icon_url, - ctx + ctx, ); let deny_cap = DenyCapV2 { id: object::new(ctx), @@ -998,12 +992,10 @@ in cap accordingly. Implementation -
public fun mint<T>(
-    cap: &mut TreasuryCap<T>, value: u64, ctx: &mut TxContext,
-): Coin<T> {
+
public fun mint<T>(cap: &mut TreasuryCap<T>, value: u64, ctx: &mut TxContext): Coin<T> {
     Coin {
         id: object::new(ctx),
-        balance: cap.total_supply.increase_supply(value)
+        balance: cap.total_supply.increase_supply(value),
     }
 }
 
@@ -1030,9 +1022,7 @@ Aborts if value + cap.total_supply >= U64_MAX Implementation -
public fun mint_balance<T>(
-    cap: &mut TreasuryCap<T>, value: u64
-): Balance<T> {
+
public fun mint_balance<T>(cap: &mut TreasuryCap<T>, value: u64): Balance<T> {
     cap.total_supply.increase_supply(value)
 }
 
@@ -1184,10 +1174,7 @@ start of the next epoch, the address will be unable to receive objects of this c Implementation -
public fun deny_list_v2_contains_next_epoch<T>(
-    deny_list: &DenyList,
-    addr: address,
-): bool {
+
public fun deny_list_v2_contains_next_epoch<T>(deny_list: &DenyList, addr: address): bool {
     let ty = type_name::get_with_original_ids<T>().into_string().into_bytes();
     deny_list.v2_contains_next_epoch(DENY_LIST_COIN_INDEX, ty, addr)
 }
@@ -1308,9 +1295,7 @@ Check if the global pause is enabled for the given coin type in the next epoch.
 Implementation
 
 
-
public fun deny_list_v2_is_global_pause_enabled_next_epoch<T>(
-    deny_list: &DenyList,
-): bool {
+
public fun deny_list_v2_is_global_pause_enabled_next_epoch<T>(deny_list: &DenyList): bool {
     let ty = type_name::get_with_original_ids<T>().into_string().into_bytes();
     deny_list.v2_is_global_pause_enabled_next_epoch(DENY_LIST_COIN_INDEX, ty)
 }
@@ -1337,7 +1322,10 @@ Mint amount of mint_and_transfer<T>(
-    c: &mut TreasuryCap<T>, amount: u64, recipient: address, ctx: &mut TxContext
+    c: &mut TreasuryCap<T>,
+    amount: u64,
+    recipient: address,
+    ctx: &mut TxContext,
 ) {
     transfer::public_transfer(mint(c, amount, ctx), recipient)
 }
@@ -1364,7 +1352,9 @@ Update name of the coin in update_name<T>(
-    _treasury: &TreasuryCap<T>, metadata: &mut CoinMetadata<T>, name: string::String
+    _treasury: &TreasuryCap<T>,
+    metadata: &mut CoinMetadata<T>,
+    name: string::String,
 ) {
     metadata.name = name;
 }
@@ -1391,7 +1381,9 @@ Update the symbol of the coin in update_symbol<T>(
-    _treasury: &TreasuryCap<T>, metadata: &mut CoinMetadata<T>, symbol: ascii::String
+    _treasury: &TreasuryCap<T>,
+    metadata: &mut CoinMetadata<T>,
+    symbol: ascii::String,
 ) {
     metadata.symbol = symbol;
 }
@@ -1418,7 +1410,9 @@ Update the description of the coin in update_description<T>(
-    _treasury: &TreasuryCap<T>, metadata: &mut CoinMetadata<T>, description: string::String
+    _treasury: &TreasuryCap<T>,
+    metadata: &mut CoinMetadata<T>,
+    description: string::String,
 ) {
     metadata.description = description;
 }
@@ -1445,7 +1439,9 @@ Update the url of the coin in update_icon_url<T>(
-    _treasury: &TreasuryCap<T>, metadata: &mut CoinMetadata<T>, url: ascii::String
+    _treasury: &TreasuryCap<T>,
+    metadata: &mut CoinMetadata<T>,
+    url: ascii::String,
 ) {
     metadata.icon_url = option::some(url::new_unsafe(url));
 }
@@ -1624,7 +1620,7 @@ with the coin as input objects.
     name: vector<u8>,
     description: vector<u8>,
     icon_url: Option<Url>,
-    ctx: &mut TxContext
+    ctx: &mut TxContext,
 ): (TreasuryCap<T>, DenyCap<T>, CoinMetadata<T>) {
     let (treasury_cap, metadata) = create_currency(
         witness,
@@ -1633,7 +1629,7 @@ with the coin as input objects.
         name,
         description,
         icon_url,
-        ctx
+        ctx,
     );
     let deny_cap = DenyCap {
         id: object::new(ctx),
@@ -1669,18 +1665,13 @@ from interacting with the specified coin type as an input to a transaction.
 
 
 
public fun deny_list_add<T>(
-   deny_list: &mut DenyList,
-   _deny_cap: &mut DenyCap<T>,
-   addr: address,
-   _ctx: &mut TxContext
+    deny_list: &mut DenyList,
+    _deny_cap: &mut DenyCap<T>,
+    addr: address,
+    _ctx: &mut TxContext,
 ) {
-    let `type` =
-        type_name::into_string(type_name::get_with_original_ids<T>()).into_bytes();
-    deny_list.v1_add(
-        DENY_LIST_COIN_INDEX,
-        `type`,
-        addr,
-    )
+    let `type` = type_name::into_string(type_name::get_with_original_ids<T>()).into_bytes();
+    deny_list.v1_add(DENY_LIST_COIN_INDEX, `type`, addr)
 }
 
@@ -1706,18 +1697,13 @@ Aborts with ENotFrozen if the address is not already in the list.
public fun deny_list_remove<T>(
-   deny_list: &mut DenyList,
-   _deny_cap: &mut DenyCap<T>,
-   addr: address,
-   _ctx: &mut TxContext
+    deny_list: &mut DenyList,
+    _deny_cap: &mut DenyCap<T>,
+    addr: address,
+    _ctx: &mut TxContext,
 ) {
-    let `type` =
-        type_name::into_string(type_name::get_with_original_ids<T>()).into_bytes();
-    deny_list.v1_remove(
-        DENY_LIST_COIN_INDEX,
-        `type`,
-        addr,
-    )
+    let `type` = type_name::into_string(type_name::get_with_original_ids<T>()).into_bytes();
+    deny_list.v1_remove(DENY_LIST_COIN_INDEX, `type`, addr)
 }
 
@@ -1742,10 +1728,7 @@ return false if given a non-coin type. Implementation -
public fun deny_list_contains<T>(
-   deny_list: &DenyList,
-   addr: address,
-): bool {
+
public fun deny_list_contains<T>(deny_list: &DenyList, addr: address): bool {
     let name = type_name::get_with_original_ids<T>();
     if (type_name::is_primitive(&name)) return false;
 
diff --git a/crates/sui-framework/docs/sui-framework/config.md b/crates/sui-framework/docs/sui-framework/config.md
index b14717f6eb345..769468e8648cb 100644
--- a/crates/sui-framework/docs/sui-framework/config.md
+++ b/crates/sui-framework/docs/sui-framework/config.md
@@ -406,7 +406,7 @@ title: Module `0x2::config`
     Name: copy + drop + store,
     Value: copy + drop + store,
 >(
-    config: & Config<WriteCap>,
+    config: &Config<WriteCap>,
     name: Name,
     ctx: &TxContext,
 ): bool {
diff --git a/crates/sui-framework/docs/sui-framework/deny_list.md b/crates/sui-framework/docs/sui-framework/deny_list.md
index bde0440aff568..275c1bc5c2a08 100644
--- a/crates/sui-framework/docs/sui-framework/deny_list.md
+++ b/crates/sui-framework/docs/sui-framework/deny_list.md
@@ -366,7 +366,7 @@ meaningless to add them to the deny list.
 ) {
     let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx);
     let setting_name = AddressKey(addr);
-    let next_epoch_entry = per_type_config.entry!<_,AddressKey, bool>(
+    let next_epoch_entry = per_type_config.entry!<_, AddressKey, bool>(
         &mut ConfigWriteCap(),
         setting_name,
         |_deny_list, _cap, _ctx| true,
@@ -638,9 +638,8 @@ meaningless to add them to the deny list.
     ctx: &mut TxContext,
 ) {
     let bag_entry: &mut PerTypeList = &mut deny_list.lists[per_type_index];
-    let elements =
-        if (!bag_entry.denied_addresses.contains(per_type_key)) vector[]
-        else bag_entry.denied_addresses.remove(per_type_key).into_keys();
+    let elements = if (!bag_entry.denied_addresses.contains(per_type_key)) vector[]
+    else bag_entry.denied_addresses.remove(per_type_key).into_keys();
     elements.do_ref!(|addr| {
         let addr = *addr;
         let denied_count = &mut bag_entry.denied_count[addr];
@@ -650,9 +649,9 @@ meaningless to add them to the deny list.
         }
     });
     let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx);
-    elements.do!(|addr|  {
+    elements.do!(|addr| {
         let setting_name = AddressKey(addr);
-        let next_epoch_entry = per_type_config.entry!<_,AddressKey, bool>(
+        let next_epoch_entry = per_type_config.entry!<_, AddressKey, bool>(
             &mut ConfigWriteCap(),
             setting_name,
             |_deny_list, _cap, _ctx| true,
@@ -773,11 +772,7 @@ meaningless to add them to the deny list.
 Implementation
 
 
-
fun per_type_exists(
-    deny_list: &DenyList,
-    per_type_index: u64,
-    per_type_key: vector<u8>,
-): bool {
+
fun per_type_exists(deny_list: &DenyList, per_type_index: u64, per_type_key: vector<u8>): bool {
     let key = ConfigKey { per_type_index, per_type_key };
     ofield::exists_(&deny_list.id, key)
 }
@@ -838,11 +833,7 @@ the type specified is the type of the coin, not the coin type itself. For exampl
 Implementation
 
 
-
fun v1_per_type_list_add(
-    list: &mut PerTypeList,
-    `type`: vector<u8>,
-    addr: address,
-) {
+
fun v1_per_type_list_add(list: &mut PerTypeList, `type`: vector<u8>, addr: address) {
     if (!list.denied_addresses.contains(`type`)) {
         list.denied_addresses.add(`type`, vec_set::empty());
     };
@@ -912,11 +903,7 @@ Aborts with v1_per_type_list_remove(
-    list: &mut PerTypeList,
-    `type`: vector<u8>,
-    addr: address,
-) {
+
fun v1_per_type_list_remove(list: &mut PerTypeList, `type`: vector<u8>, addr: address) {
     let denied_addresses = &mut list.denied_addresses[`type`];
     assert!(denied_addresses.contains(&addr), ENotDenied);
     denied_addresses.remove(&addr);
@@ -980,11 +967,7 @@ Returns true iff the given address is denied for the given type.
 Implementation
 
 
-
fun v1_per_type_list_contains(
-    list: &PerTypeList,
-    `type`: vector<u8>,
-    addr: address,
-): bool {
+
fun v1_per_type_list_contains(list: &PerTypeList, `type`: vector<u8>, addr: address): bool {
     if (!list.denied_count.contains(addr)) return false;
 
     let denied_count = &list.denied_count[addr];
diff --git a/crates/sui-framework/docs/sui-framework/display.md b/crates/sui-framework/docs/sui-framework/display.md
index 3d085df973e54..e56a69e0d080f 100644
--- a/crates/sui-framework/docs/sui-framework/display.md
+++ b/crates/sui-framework/docs/sui-framework/display.md
@@ -247,7 +247,10 @@ Create a new Display object with a set of fields.
 
 
 
public fun new_with_fields<T: key>(
-    pub: &Publisher, fields: vector<String>, values: vector<String>, ctx: &mut TxContext
+    pub: &Publisher,
+    fields: vector<String>,
+    values: vector<String>,
+    ctx: &mut TxContext,
 ): Display<T> {
     let len = fields.length();
     assert!(len == values.length(), EVecLengthMismatch);
@@ -283,7 +286,7 @@ Create a new empty Display object and keep it.
 Implementation
 
 
-
entry public fun create_and_keep<T: key>(pub: &Publisher, ctx: &mut TxContext) {
+
public entry fun create_and_keep<T: key>(pub: &Publisher, ctx: &mut TxContext) {
     transfer::public_transfer(new<T>(pub, ctx), ctx.sender())
 }
 
@@ -308,9 +311,7 @@ Manually bump the version and emit an event with the updated version's contents. Implementation -
entry public fun update_version<T: key>(
-    display: &mut Display<T>
-) {
+
public entry fun update_version<T: key>(display: &mut Display<T>) {
     display.version = display.version + 1;
     event::emit(VersionUpdated<T> {
         version: display.version,
@@ -340,7 +341,7 @@ Sets a custom name field with the value.
 Implementation
 
 
-
entry public fun add<T: key>(self: &mut Display<T>, name: String, value: String) {
+
public entry fun add<T: key>(self: &mut Display<T>, name: String, value: String) {
     self.add_internal(name, value)
 }
 
@@ -365,8 +366,10 @@ Sets multiple fields with values. Implementation -
entry public fun add_multiple<T: key>(
-    self: &mut Display<T>, fields: vector<String>, values: vector<String>
+
public entry fun add_multiple<T: key>(
+    self: &mut Display<T>,
+    fields: vector<String>,
+    values: vector<String>,
 ) {
     let len = fields.length();
     assert!(len == values.length(), EVecLengthMismatch);
@@ -400,7 +403,7 @@ TODO (long run): version changes;
 Implementation
 
 
-
entry public fun edit<T: key>(self: &mut Display<T>, name: String, value: String) {
+
public entry fun edit<T: key>(self: &mut Display<T>, name: String, value: String) {
     let (_, _) = self.fields.remove(&name);
     self.add_internal(name, value)
 }
@@ -426,7 +429,7 @@ Remove the key from the Display.
 Implementation
 
 
-
entry public fun remove<T: key>(self: &mut Display<T>, name: String) {
+
public entry fun remove<T: key>(self: &mut Display<T>, name: String) {
     self.fields.remove(&name);
 }
 
@@ -530,7 +533,7 @@ Internal function to create a new let uid = object::new(ctx); event::emit(DisplayCreated<T> { - id: uid.to_inner() + id: uid.to_inner(), }); Display { diff --git a/crates/sui-framework/docs/sui-framework/dynamic_field.md b/crates/sui-framework/docs/sui-framework/dynamic_field.md index 0b9a655197389..2afa2c24eb54d 100644 --- a/crates/sui-framework/docs/sui-framework/dynamic_field.md +++ b/crates/sui-framework/docs/sui-framework/dynamic_field.md @@ -191,10 +191,7 @@ type. Implementation -
public fun borrow<Name: copy + drop + store, Value: store>(
-    object: &UID,
-    name: Name,
-): &Value {
+
public fun borrow<Name: copy + drop + store, Value: store>(object: &UID, name: Name): &Value {
     let object_addr = object.to_address();
     let hash = hash_type_and_key(object_addr, name);
     let field = borrow_child_object<Field<Name, Value>>(object, hash);
@@ -260,10 +257,7 @@ type.
 Implementation
 
 
-
public fun remove<Name: copy + drop + store, Value: store>(
-    object: &mut UID,
-    name: Name,
-): Value {
+
public fun remove<Name: copy + drop + store, Value: store>(object: &mut UID, name: Name): Value {
     let object_addr = object.to_address();
     let hash = hash_type_and_key(object_addr, name);
     let Field { id, name: _, value } = remove_child_object<Field<Name, Value>>(object_addr, hash);
@@ -293,10 +287,7 @@ Returns true if and only if the exists_<Name: copy + drop + store>(
-    object: &UID,
-    name: Name,
-): bool {
+
public fun exists_<Name: copy + drop + store>(object: &UID, name: Name): bool {
     let object_addr = object.to_address();
     let hash = hash_type_and_key(object_addr, name);
     has_child_object(object_addr, hash)
@@ -325,7 +316,7 @@ Removes the dynamic field if it exists. Returns the some(Value) if
 
 
public fun remove_if_exists<Name: copy + drop + store, Value: store>(
     object: &mut UID,
-    name: Name
+    name: Name,
 ): Option<Value> {
     if (exists_<Name>(object, name)) {
         option::some(remove(object, name))
@@ -446,7 +437,10 @@ May abort with hash_type_and_key<K: copy + drop + store>(parent: address, k: K): address;
+
public(package) native fun hash_type_and_key<K: copy + drop + store>(
+    parent: address,
+    k: K,
+): address;
 
@@ -516,7 +510,10 @@ we need two versions to return a reference or a mutable reference Implementation -
public(package) native fun borrow_child_object_mut<Child: key>(object: &mut UID, id: address): &mut Child;
+
public(package) native fun borrow_child_object_mut<Child: key>(
+    object: &mut UID,
+    id: address,
+): &mut Child;
 
diff --git a/crates/sui-framework/docs/sui-framework/dynamic_object_field.md b/crates/sui-framework/docs/sui-framework/dynamic_object_field.md index f2481565976ed..a3668a6155c5b 100644 --- a/crates/sui-framework/docs/sui-framework/dynamic_object_field.md +++ b/crates/sui-framework/docs/sui-framework/dynamic_object_field.md @@ -107,10 +107,7 @@ specified type. Implementation -
public fun borrow<Name: copy + drop + store, Value: key + store>(
-    object: &UID,
-    name: Name,
-): &Value {
+
public fun borrow<Name: copy + drop + store, Value: key + store>(object: &UID, name: Name): &Value {
     borrow_impl!(object, name)
 }
 
@@ -199,10 +196,7 @@ Returns true if and only if the exists_<Name: copy + drop + store>( - object: &UID, - name: Name, -): bool { +
public fun exists_<Name: copy + drop + store>(object: &UID, name: Name): bool {
     let key = Wrapper { name };
     field::exists_with_type<Wrapper<Name>, ID>(object, key)
 }
@@ -258,10 +252,7 @@ Returns none otherwise
 Implementation
 
 
-
public fun id<Name: copy + drop + store>(
-    object: &UID,
-    name: Name,
-): Option<ID> {
+
public fun id<Name: copy + drop + store>(object: &UID, name: Name): Option<ID> {
     let key = Wrapper { name };
     if (!field::exists_with_type<Wrapper<Name>, ID>(object, key)) return option::none();
     let (_field, value_addr) = field::field_info<Wrapper<Name>>(object, key);
diff --git a/crates/sui-framework/docs/sui-framework/ecdsa_k1.md b/crates/sui-framework/docs/sui-framework/ecdsa_k1.md
index 90b255d7f7bfb..3e64c934b6ed3 100644
--- a/crates/sui-framework/docs/sui-framework/ecdsa_k1.md
+++ b/crates/sui-framework/docs/sui-framework/ecdsa_k1.md
@@ -93,7 +93,11 @@ applied to Secp256k1 signatures. May abort with secp256k1_ecrecover(signature: &vector<u8>, msg: &vector<u8>, hash: u8): vector<u8>;
+
public native fun secp256k1_ecrecover(
+    signature: &vector<u8>,
+    msg: &vector<u8>,
+    hash: u8,
+): vector<u8>;
 
@@ -150,7 +154,12 @@ If the signature is valid to the pubkey and hashed message, return true. Else fa Implementation -
public native fun secp256k1_verify(signature: &vector<u8>, public_key: &vector<u8>, msg: &vector<u8>, hash: u8): bool;
+
public native fun secp256k1_verify(
+    signature: &vector<u8>,
+    public_key: &vector<u8>,
+    msg: &vector<u8>,
+    hash: u8,
+): bool;
 
diff --git a/crates/sui-framework/docs/sui-framework/ecdsa_r1.md b/crates/sui-framework/docs/sui-framework/ecdsa_r1.md index cd28a7ba29d38..889240cc5654b 100644 --- a/crates/sui-framework/docs/sui-framework/ecdsa_r1.md +++ b/crates/sui-framework/docs/sui-framework/ecdsa_r1.md @@ -82,7 +82,11 @@ applied to Secp256r1 signatures. May fail with secp256r1_ecrecover(signature: &vector<u8>, msg: &vector<u8>, hash: u8): vector<u8>; +
public native fun secp256r1_ecrecover(
+    signature: &vector<u8>,
+    msg: &vector<u8>,
+    hash: u8,
+): vector<u8>;
 
@@ -113,7 +117,12 @@ If the signature is valid to the pubkey and hashed message, return true. Else fa Implementation -
public native fun secp256r1_verify(signature: &vector<u8>, public_key: &vector<u8>, msg: &vector<u8>, hash: u8): bool;
+
public native fun secp256r1_verify(
+    signature: &vector<u8>,
+    public_key: &vector<u8>,
+    msg: &vector<u8>,
+    hash: u8,
+): bool;
 
diff --git a/crates/sui-framework/docs/sui-framework/ecvrf.md b/crates/sui-framework/docs/sui-framework/ecvrf.md index 77aeb3c71b65c..5a0c5d563340b 100644 --- a/crates/sui-framework/docs/sui-framework/ecvrf.md +++ b/crates/sui-framework/docs/sui-framework/ecvrf.md @@ -64,7 +64,12 @@ Verify a proof for a Ristretto ECVRF. Returns true if the proof is valid and cor Implementation -
public native fun ecvrf_verify(hash: &vector<u8>, alpha_string: &vector<u8>, public_key: &vector<u8>, proof: &vector<u8>): bool;
+
public native fun ecvrf_verify(
+    hash: &vector<u8>,
+    alpha_string: &vector<u8>,
+    public_key: &vector<u8>,
+    proof: &vector<u8>,
+): bool;
 
diff --git a/crates/sui-framework/docs/sui-framework/ed25519.md b/crates/sui-framework/docs/sui-framework/ed25519.md index fd65b4fbe38d2..759e2b80de7f7 100644 --- a/crates/sui-framework/docs/sui-framework/ed25519.md +++ b/crates/sui-framework/docs/sui-framework/ed25519.md @@ -32,7 +32,11 @@ Otherwise, return false. Implementation -
public native fun ed25519_verify(signature: &vector<u8>, public_key: &vector<u8>, msg: &vector<u8>): bool;
+
public native fun ed25519_verify(
+    signature: &vector<u8>,
+    public_key: &vector<u8>,
+    msg: &vector<u8>,
+): bool;
 
diff --git a/crates/sui-framework/docs/sui-framework/groth16.md b/crates/sui-framework/docs/sui-framework/groth16.md index 4d3db01febc22..b392a49c49de5 100644 --- a/crates/sui-framework/docs/sui-framework/groth16.md +++ b/crates/sui-framework/docs/sui-framework/groth16.md @@ -268,12 +268,17 @@ Creates a PreparedVe Implementation -
public fun pvk_from_bytes(vk_gamma_abc_g1_bytes: vector<u8>, alpha_g1_beta_g2_bytes: vector<u8>, gamma_g2_neg_pc_bytes: vector<u8>, delta_g2_neg_pc_bytes: vector<u8>): PreparedVerifyingKey {
+
public fun pvk_from_bytes(
+    vk_gamma_abc_g1_bytes: vector<u8>,
+    alpha_g1_beta_g2_bytes: vector<u8>,
+    gamma_g2_neg_pc_bytes: vector<u8>,
+    delta_g2_neg_pc_bytes: vector<u8>,
+): PreparedVerifyingKey {
     PreparedVerifyingKey {
         vk_gamma_abc_g1_bytes,
         alpha_g1_beta_g2_bytes,
         gamma_g2_neg_pc_bytes,
-        delta_g2_neg_pc_bytes
+        delta_g2_neg_pc_bytes,
     }
 }
 
@@ -411,7 +416,10 @@ Native functions that flattens the inputs into an array and passes to the Rust n Implementation -
native fun prepare_verifying_key_internal(curve: u8, verifying_key: &vector<u8>): PreparedVerifyingKey;
+
native fun prepare_verifying_key_internal(
+    curve: u8,
+    verifying_key: &vector<u8>,
+): PreparedVerifyingKey;
 
@@ -439,7 +447,12 @@ Returns a boolean indicating whether the proof is valid. Implementation -
public fun verify_groth16_proof(curve: &Curve, prepared_verifying_key: &PreparedVerifyingKey, public_proof_inputs: &PublicProofInputs, proof_points: &ProofPoints): bool {
+
public fun verify_groth16_proof(
+    curve: &Curve,
+    prepared_verifying_key: &PreparedVerifyingKey,
+    public_proof_inputs: &PublicProofInputs,
+    proof_points: &ProofPoints,
+): bool {
     verify_groth16_proof_internal(
         curve.id,
         &prepared_verifying_key.vk_gamma_abc_g1_bytes,
@@ -447,7 +460,7 @@ Returns a boolean indicating whether the proof is valid.
         &prepared_verifying_key.gamma_g2_neg_pc_bytes,
         &prepared_verifying_key.delta_g2_neg_pc_bytes,
         &public_proof_inputs.bytes,
-        &proof_points.bytes
+        &proof_points.bytes,
     )
 }
 
@@ -472,7 +485,15 @@ Native functions that flattens the inputs into arrays of vectors and passed to t Implementation -
native fun verify_groth16_proof_internal(curve: u8, vk_gamma_abc_g1_bytes: &vector<u8>, alpha_g1_beta_g2_bytes: &vector<u8>, gamma_g2_neg_pc_bytes: &vector<u8>, delta_g2_neg_pc_bytes: &vector<u8>, public_proof_inputs: &vector<u8>, proof_points: &vector<u8>): bool;
+
native fun verify_groth16_proof_internal(
+    curve: u8,
+    vk_gamma_abc_g1_bytes: &vector<u8>,
+    alpha_g1_beta_g2_bytes: &vector<u8>,
+    gamma_g2_neg_pc_bytes: &vector<u8>,
+    delta_g2_neg_pc_bytes: &vector<u8>,
+    public_proof_inputs: &vector<u8>,
+    proof_points: &vector<u8>,
+): bool;
 
diff --git a/crates/sui-framework/docs/sui-framework/group_ops.md b/crates/sui-framework/docs/sui-framework/group_ops.md index 99a122f3b9b86..de2f6a81a5828 100644 --- a/crates/sui-framework/docs/sui-framework/group_ops.md +++ b/crates/sui-framework/docs/sui-framework/group_ops.md @@ -312,12 +312,16 @@ Aborts with EInputTooLo Implementation -
public(package) fun multi_scalar_multiplication<S, G>(type_: u8, scalars: &vector<Element<S>>, elements: &vector<Element<G>>): Element<G> {
+
public(package) fun multi_scalar_multiplication<S, G>(
+    type_: u8,
+    scalars: &vector<Element<S>>,
+    elements: &vector<Element<G>>,
+): Element<G> {
     assert!(scalars.length() > 0, EInvalidInput);
     assert!(scalars.length() == elements.length(), EInvalidInput);
 
     let mut scalars_bytes: vector<u8> = vector[];
-    let mut elements_bytes: vector<u8>  = vector[];
+    let mut elements_bytes: vector<u8> = vector[];
     let mut i = 0;
     while (i < scalars.length()) {
         let scalar_vec = scalars[i];
@@ -349,7 +353,11 @@ Aborts with EInputTooLo
 Implementation
 
 
-
public(package) fun pairing<G1, G2, G3>(type_: u8, e1: &Element<G1>, e2: &Element<G2>): Element<G3> {
+
public(package) fun pairing<G1, G2, G3>(
+    type_: u8,
+    e1: &Element<G1>,
+    e2: &Element<G2>,
+): Element<G3> {
     Element<G3> { bytes: internal_pairing(type_, &e1.bytes, &e2.bytes) }
 }
 
@@ -505,7 +513,11 @@ Aborts with EInputTooLo Implementation -
native fun internal_multi_scalar_mul(type_: u8, scalars: &vector<u8>, elements: &vector<u8>): vector<u8>;
+
native fun internal_multi_scalar_mul(
+    type_: u8,
+    scalars: &vector<u8>,
+    elements: &vector<u8>,
+): vector<u8>;
 
@@ -555,7 +567,11 @@ Aborts with EInputTooLo let x_as_bytes = bcs::to_bytes(&x); // little endian let mut i = 0; while (i < 8) { - let position = if (big_endian) { buffer_len - i - 1 } else { i }; + let position = if (big_endian) { + buffer_len - i - 1 + } else { + i + }; *(&mut buffer[position]) = x_as_bytes[i]; i = i + 1; }; diff --git a/crates/sui-framework/docs/sui-framework/hash.md b/crates/sui-framework/docs/sui-framework/hash.md index 3054c03b9bf3d..ce5a53a48c04c 100644 --- a/crates/sui-framework/docs/sui-framework/hash.md +++ b/crates/sui-framework/docs/sui-framework/hash.md @@ -31,7 +31,7 @@ Hash the input bytes using Blake2b-256 and returns 32 bytes. Implementation -
native public fun blake2b256(data: &vector<u8>): vector<u8>;
+
public native fun blake2b256(data: &vector<u8>): vector<u8>;
 
@@ -55,7 +55,7 @@ Hash the input bytes using keccak256 and returns 32 bytes. Implementation -
native public fun keccak256(data: &vector<u8>): vector<u8>;
+
public native fun keccak256(data: &vector<u8>): vector<u8>;
 
diff --git a/crates/sui-framework/docs/sui-framework/hex.md b/crates/sui-framework/docs/sui-framework/hex.md index 5a835de9a6314..c7844eb46f877 100644 --- a/crates/sui-framework/docs/sui-framework/hex.md +++ b/crates/sui-framework/docs/sui-framework/hex.md @@ -133,11 +133,11 @@ Aborts if the hex string contains non-valid hex characters (valid characters are
fun decode_byte(hex: u8): u8 {
-    if (/* 0 .. 9 */ 48 <= hex && hex < 58) {
+    if (48 <= hex && hex < 58) {
         hex - 48
-    } else if (/* A .. F */ 65 <= hex && hex < 71) {
+    } else if (65 <= hex && hex < 71) {
         10 + hex - 65
-    } else if (/* a .. f */ 97 <= hex && hex < 103) {
+    } else if (97 <= hex && hex < 103) {
         10 + hex - 97
     } else {
         abort ENotValidHexCharacter
diff --git a/crates/sui-framework/docs/sui-framework/kiosk.md b/crates/sui-framework/docs/sui-framework/kiosk.md
index 9cb3f26dca568..0e15459e4cee7 100644
--- a/crates/sui-framework/docs/sui-framework/kiosk.md
+++ b/crates/sui-framework/docs/sui-framework/kiosk.md
@@ -760,12 +760,12 @@ Creates a new Kiosk with a m
         profits: balance::zero(),
         owner: ctx.sender(),
         item_count: 0,
-        allow_extensions: false
+        allow_extensions: false,
     };
 
     let cap = KioskOwnerCap {
         id: object::new(ctx),
-        `for`: object::id(&kiosk)
+        `for`: object::id(&kiosk),
     };
 
     (kiosk, cap)
@@ -794,9 +794,7 @@ case where there's no items inside and a close_and_withdraw(
-    self: Kiosk, cap: KioskOwnerCap, ctx: &mut TxContext
-): Coin<SUI> {
+
public fun close_and_withdraw(self: Kiosk, cap: KioskOwnerCap, ctx: &mut TxContext): Coin<SUI> {
     let Kiosk { id, profits, owner: _, item_count, allow_extensions: _ } = self;
     let KioskOwnerCap { id: cap_id, `for` } = cap;
 
@@ -833,9 +831,7 @@ in a third party module.
 Implementation
 
 
-
public fun set_owner(
-    self: &mut Kiosk, cap: &KioskOwnerCap, ctx: &TxContext
-) {
+
public fun set_owner(self: &mut Kiosk, cap: &KioskOwnerCap, ctx: &TxContext) {
     assert!(self.has_access(cap), ENotOwner);
     self.owner = ctx.sender();
 }
@@ -862,9 +858,7 @@ implementing a custom logic that relies on the set_owner_custom(
-    self: &mut Kiosk, cap: &KioskOwnerCap, owner: address
-) {
+
public fun set_owner_custom(self: &mut Kiosk, cap: &KioskOwnerCap, owner: address) {
     assert!(self.has_access(cap), ENotOwner);
     self.owner = owner
 }
@@ -891,9 +885,7 @@ Performs an authorization check to make sure only owner can do that.
 Implementation
 
 
-
public fun place<T: key + store>(
-    self: &mut Kiosk, cap: &KioskOwnerCap, item: T
-) {
+
public fun place<T: key + store>(self: &mut Kiosk, cap: &KioskOwnerCap, item: T) {
     assert!(self.has_access(cap), ENotOwner);
     self.place_internal(item)
 }
@@ -925,7 +917,10 @@ and the item can be sold, otherwise the asset might be locked forever.
 
 
 
public fun lock<T: key + store>(
-    self: &mut Kiosk, cap: &KioskOwnerCap, _policy: &TransferPolicy<T>, item: T
+    self: &mut Kiosk,
+    cap: &KioskOwnerCap,
+    _policy: &TransferPolicy<T>,
+    item: T,
 ) {
     assert!(self.has_access(cap), ENotOwner);
     self.lock_internal(item)
@@ -953,9 +948,7 @@ Performs an authorization check to make sure only owner can do that.
 Implementation
 
 
-
public fun take<T: key + store>(
-    self: &mut Kiosk, cap: &KioskOwnerCap, id: ID
-): T {
+
public fun take<T: key + store>(self: &mut Kiosk, cap: &KioskOwnerCap, id: ID): T {
     assert!(self.has_access(cap), ENotOwner);
     assert!(!self.is_locked(id), EItemLocked);
     assert!(!self.is_listed_exclusively(id), EListedExclusively);
@@ -988,9 +981,7 @@ Performs an authorization check to make sure only owner can sell.
 Implementation
 
 
-
public fun list<T: key + store>(
-    self: &mut Kiosk, cap: &KioskOwnerCap, id: ID, price: u64
-) {
+
public fun list<T: key + store>(self: &mut Kiosk, cap: &KioskOwnerCap, id: ID, price: u64) {
     assert!(self.has_access(cap), ENotOwner);
     assert!(self.has_item_with_type<T>(id), EItemNotFound);
     assert!(!self.is_listed_exclusively(id), EListedExclusively);
@@ -1021,7 +1012,10 @@ Calls place and list together - simplifies the flow.
 
 
 
public fun place_and_list<T: key + store>(
-    self: &mut Kiosk, cap: &KioskOwnerCap, item: T, price: u64
+    self: &mut Kiosk,
+    cap: &KioskOwnerCap,
+    item: T,
+    price: u64,
 ) {
     let id = object::id(&item);
     self.place(cap, item);
@@ -1050,9 +1044,7 @@ user Kiosk. Can only be performed by the owner of the delist<T: key + store>(
-    self: &mut Kiosk, cap: &KioskOwnerCap, id: ID
-) {
+
public fun delist<T: key + store>(self: &mut Kiosk, cap: &KioskOwnerCap, id: ID) {
     assert!(self.has_access(cap), ENotOwner);
     assert!(self.has_item_with_type<T>(id), EItemNotFound);
     assert!(!self.is_listed_exclusively(id), EListedExclusively);
@@ -1090,7 +1082,9 @@ finalized.
 
 
 
public fun purchase<T: key + store>(
-    self: &mut Kiosk, id: ID, payment: Coin<SUI>
+    self: &mut Kiosk,
+    id: ID,
+    payment: Coin<SUI>,
 ): (T, TransferRequest<T>) {
     let price = df::remove<Listing, u64>(&mut self.id, Listing { id, is_exclusive: false });
     let inner = dof::remove<Item, T>(&mut self.id, Item { id });
@@ -1128,7 +1122,11 @@ for any price equal or higher than the min_price.
 
 
 
public fun list_with_purchase_cap<T: key + store>(
-    self: &mut Kiosk, cap: &KioskOwnerCap, id: ID, min_price: u64, ctx: &mut TxContext
+    self: &mut Kiosk,
+    cap: &KioskOwnerCap,
+    id: ID,
+    min_price: u64,
+    ctx: &mut TxContext,
 ): PurchaseCap<T> {
     assert!(self.has_access(cap), ENotOwner);
     assert!(self.has_item_with_type<T>(id), EItemNotFound);
@@ -1167,7 +1165,9 @@ as the price for the listing making sure it's no less than min_amountpublic fun purchase_with_cap<T: key + store>(
-    self: &mut Kiosk, purchase_cap: PurchaseCap<T>, payment: Coin<SUI>
+    self: &mut Kiosk,
+    purchase_cap: PurchaseCap<T>,
+    payment: Coin<SUI>,
 ): (T, TransferRequest<T>) {
     let PurchaseCap { id, item_id, kiosk_id, min_price } = purchase_cap;
     id.delete();
@@ -1209,9 +1209,7 @@ allow the item for taking. Can only be returned to its return_purchase_cap<T: key + store>(
-    self: &mut Kiosk, purchase_cap: PurchaseCap<T>
-) {
+
public fun return_purchase_cap<T: key + store>(self: &mut Kiosk, purchase_cap: PurchaseCap<T>) {
     let PurchaseCap { id, item_id, kiosk_id, min_price: _ } = purchase_cap;
 
     assert!(object::id(self) == kiosk_id, EWrongKiosk);
@@ -1241,7 +1239,10 @@ Withdraw profits from the Kiosk.
 
 
 
public fun withdraw(
-    self: &mut Kiosk, cap: &KioskOwnerCap, amount: Option<u64>, ctx: &mut TxContext
+    self: &mut Kiosk,
+    cap: &KioskOwnerCap,
+    amount: Option<u64>,
+    ctx: &mut TxContext,
 ): Coin<SUI> {
     assert!(self.has_access(cap), ENotOwner);
 
@@ -1433,7 +1434,7 @@ Check whether an item is listed (exclusively or non exclusively).
 
 
public fun is_listed(self: &Kiosk, id: ID): bool {
     df::exists_(&self.id, Listing { id, is_exclusive: false })
-    || self.is_listed_exclusively(id)
+        || self.is_listed_exclusively(id)
 }
 
@@ -1507,9 +1508,7 @@ Access the UID using the uid_mut_as_owner( - self: &mut Kiosk, cap: &KioskOwnerCap -): &mut UID { +
public fun uid_mut_as_owner(self: &mut Kiosk, cap: &KioskOwnerCap): &mut UID {
     assert!(self.has_access(cap), ENotOwner);
     &mut self.id
 }
@@ -1537,9 +1536,7 @@ setting.
 Implementation
 
 
-
public fun set_allow_extensions(
-    self: &mut Kiosk, cap: &KioskOwnerCap, allow_extensions: bool
-) {
+
public fun set_allow_extensions(self: &mut Kiosk, cap: &KioskOwnerCap, allow_extensions: bool) {
     assert!(self.has_access(cap), ENotOwner);
     self.allow_extensions = allow_extensions;
 }
@@ -1723,9 +1720,7 @@ at any time.
 Implementation
 
 
-
public fun borrow<T: key + store>(
-    self: &Kiosk, cap: &KioskOwnerCap, id: ID
-): &T {
+
public fun borrow<T: key + store>(self: &Kiosk, cap: &KioskOwnerCap, id: ID): &T {
     assert!(object::id(self) == cap.`for`, ENotOwner);
     assert!(self.has_item(id), EItemNotFound);
 
@@ -1754,9 +1749,7 @@ Item can be borrow_muted only if it's not is_listed.
 Implementation
 
 
-
public fun borrow_mut<T: key + store>(
-    self: &mut Kiosk, cap: &KioskOwnerCap, id: ID
-): &mut T {
+
public fun borrow_mut<T: key + store>(self: &mut Kiosk, cap: &KioskOwnerCap, id: ID): &mut T {
     assert!(self.has_access(cap), ENotOwner);
     assert!(self.has_item(id), EItemNotFound);
     assert!(!self.is_listed(id), EItemIsListed);
@@ -1786,17 +1779,12 @@ Item can be borrow_val-ed only if it's not is_listed.
 Implementation
 
 
-
public fun borrow_val<T: key + store>(
-    self: &mut Kiosk, cap: &KioskOwnerCap, id: ID
-): (T, Borrow) {
+
public fun borrow_val<T: key + store>(self: &mut Kiosk, cap: &KioskOwnerCap, id: ID): (T, Borrow) {
     assert!(self.has_access(cap), ENotOwner);
     assert!(self.has_item(id), EItemNotFound);
     assert!(!self.is_listed(id), EItemIsListed);
 
-    (
-        dof::remove(&mut self.id, Item { id }),
-        Borrow { kiosk_id: object::id(self), item_id: id }
-    )
+    (dof::remove(&mut self.id, Item { id }), Borrow { kiosk_id: object::id(self), item_id: id })
 }
 
@@ -1821,9 +1809,7 @@ if borrow_val is used. Implementation -
public fun return_val<T: key + store>(
-    self: &mut Kiosk, item: T, borrow: Borrow
-) {
+
public fun return_val<T: key + store>(self: &mut Kiosk, item: T, borrow: Borrow) {
     let Borrow { kiosk_id, item_id } = borrow;
 
     assert!(object::id(self) == kiosk_id, EWrongKiosk);
diff --git a/crates/sui-framework/docs/sui-framework/kiosk_extension.md b/crates/sui-framework/docs/sui-framework/kiosk_extension.md
index 415a890fea4a5..d7fc630d70d7c 100644
--- a/crates/sui-framework/docs/sui-framework/kiosk_extension.md
+++ b/crates/sui-framework/docs/sui-framework/kiosk_extension.md
@@ -255,7 +255,7 @@ permissions in the custom add call.
     self: &mut Kiosk,
     cap: &KioskOwnerCap,
     permissions: u128,
-    ctx: &mut TxContext
+    ctx: &mut TxContext,
 ) {
     assert!(self.has_access(cap), ENotOwner);
     df::add(
@@ -265,7 +265,7 @@ permissions in the custom add call.
             storage: bag::new(ctx),
             permissions,
             is_enabled: true,
-        }
+        },
     )
 }
 
@@ -292,10 +292,7 @@ The storage is still available to the extension (until it's removed). Implementation -
public fun disable<Ext: drop>(
-    self: &mut Kiosk,
-    cap: &KioskOwnerCap,
-) {
+
public fun disable<Ext: drop>(self: &mut Kiosk, cap: &KioskOwnerCap) {
     assert!(self.has_access(cap), ENotOwner);
     assert!(is_installed<Ext>(self), EExtensionNotInstalled);
     extension_mut<Ext>(self).is_enabled = false;
@@ -324,10 +321,7 @@ owner can disable them via disable call.
 Implementation
 
 
-
public fun enable<Ext: drop>(
-    self: &mut Kiosk,
-    cap: &KioskOwnerCap,
-) {
+
public fun enable<Ext: drop>(self: &mut Kiosk, cap: &KioskOwnerCap) {
     assert!(self.has_access(cap), ENotOwner);
     assert!(is_installed<Ext>(self), EExtensionNotInstalled);
     extension_mut<Ext>(self).is_enabled = true;
@@ -355,9 +349,7 @@ the extension storage must be empty for the transaction to succeed.
 Implementation
 
 
-
public fun remove<Ext: drop>(
-    self: &mut Kiosk, cap: &KioskOwnerCap
-) {
+
public fun remove<Ext: drop>(self: &mut Kiosk, cap: &KioskOwnerCap) {
     assert!(self.has_access(cap), ENotOwner);
     assert!(is_installed<Ext>(self), EExtensionNotInstalled);
 
@@ -392,9 +384,7 @@ the extension as long as the extension is installed.
 Implementation
 
 
-
public fun storage<Ext: drop>(
-    _ext: Ext, self: &Kiosk
-): &Bag {
+
public fun storage<Ext: drop>(_ext: Ext, self: &Kiosk): &Bag {
     assert!(is_installed<Ext>(self), EExtensionNotInstalled);
     &extension<Ext>(self).storage
 }
@@ -431,9 +421,7 @@ aware of the risks.
 Implementation
 
 
-
public fun storage_mut<Ext: drop>(
-    _ext: Ext, self: &mut Kiosk
-): &mut Bag {
+
public fun storage_mut<Ext: drop>(_ext: Ext, self: &mut Kiosk): &mut Bag {
     assert!(is_installed<Ext>(self), EExtensionNotInstalled);
     &mut extension_mut<Ext>(self).storage
 }
@@ -465,7 +453,10 @@ requires a TransferPolicy for the placed type to exist.
 
 
 
public fun place<Ext: drop, T: key + store>(
-    _ext: Ext, self: &mut Kiosk, item: T, _policy: &TransferPolicy<T>
+    _ext: Ext,
+    self: &mut Kiosk,
+    item: T,
+    _policy: &TransferPolicy<T>,
 ) {
     assert!(is_installed<Ext>(self), EExtensionNotInstalled);
     assert!(can_place<Ext>(self) || can_lock<Ext>(self), EExtensionNotAllowed);
@@ -496,7 +487,10 @@ authorized extension. The extension must have the lock permission.
 
 
 
public fun lock<Ext: drop, T: key + store>(
-    _ext: Ext, self: &mut Kiosk, item: T, _policy: &TransferPolicy<T>
+    _ext: Ext,
+    self: &mut Kiosk,
+    item: T,
+    _policy: &TransferPolicy<T>,
 ) {
     assert!(is_installed<Ext>(self), EExtensionNotInstalled);
     assert!(can_lock<Ext>(self), EExtensionNotAllowed);
diff --git a/crates/sui-framework/docs/sui-framework/math.md b/crates/sui-framework/docs/sui-framework/math.md
index d138083a64689..be044025e9ba1 100644
--- a/crates/sui-framework/docs/sui-framework/math.md
+++ b/crates/sui-framework/docs/sui-framework/math.md
@@ -162,7 +162,7 @@ DEPRECATED, use std::u128::sqrt instead
 
 
 
public fun sqrt_u128(x: u128): u128 {
-   x.sqrt()
+    x.sqrt()
 }
 
diff --git a/crates/sui-framework/docs/sui-framework/object.md b/crates/sui-framework/docs/sui-framework/object.md index 8e7a390ad700e..020d715c11e82 100644 --- a/crates/sui-framework/docs/sui-framework/object.md +++ b/crates/sui-framework/docs/sui-framework/object.md @@ -330,7 +330,7 @@ This should only be called once from clock(): UID { UID { - id: ID { bytes: SUI_CLOCK_OBJECT_ID } + id: ID { bytes: SUI_CLOCK_OBJECT_ID }, } }
@@ -358,7 +358,7 @@ This should only be called once from authenticator_state(): UID { UID { - id: ID { bytes: SUI_AUTHENTICATOR_STATE_ID } + id: ID { bytes: SUI_AUTHENTICATOR_STATE_ID }, } }
@@ -386,7 +386,7 @@ This should only be called once from randomness_state(): UID { UID { - id: ID { bytes: SUI_RANDOM_ID } + id: ID { bytes: SUI_RANDOM_ID }, } }
@@ -414,7 +414,7 @@ This should only be called once from sui_deny_list_object_id(): UID { UID { - id: ID { bytes: SUI_DENY_LIST_OBJECT_ID } + id: ID { bytes: SUI_DENY_LIST_OBJECT_ID }, } }
@@ -442,7 +442,7 @@ This should only be called once from bridge.
fun bridge(): UID {
     UID {
-        id: ID { bytes: SUI_BRIDGE_ID }
+        id: ID { bytes: SUI_BRIDGE_ID },
     }
 }
 
diff --git a/crates/sui-framework/docs/sui-framework/package.md b/crates/sui-framework/docs/sui-framework/package.md index a2c043014e433..b718af007cc98 100644 --- a/crates/sui-framework/docs/sui-framework/package.md +++ b/crates/sui-framework/docs/sui-framework/package.md @@ -899,11 +899,7 @@ for the upgrade to succeed. Implementation -
public fun authorize_upgrade(
-    cap: &mut UpgradeCap,
-    policy: u8,
-    digest: vector<u8>
-): UpgradeTicket {
+
public fun authorize_upgrade(cap: &mut UpgradeCap, policy: u8, digest: vector<u8>): UpgradeTicket {
     let id_zero = @0x0.to_id();
     assert!(cap.package != id_zero, EAlreadyAuthorized);
     assert!(policy >= cap.policy, ETooPermissive);
@@ -941,10 +937,7 @@ the upgrade.
 Implementation
 
 
-
public fun commit_upgrade(
-    cap: &mut UpgradeCap,
-    receipt: UpgradeReceipt,
-) {
+
public fun commit_upgrade(cap: &mut UpgradeCap, receipt: UpgradeReceipt) {
     let UpgradeReceipt { cap: cap_id, package } = receipt;
 
     assert!(object::id(cap) == cap_id, EWrongUpgradeCap);
diff --git a/crates/sui-framework/docs/sui-framework/pay.md b/crates/sui-framework/docs/sui-framework/pay.md
index 7abd57b9fe2ed..84b2a29a92726 100644
--- a/crates/sui-framework/docs/sui-framework/pay.md
+++ b/crates/sui-framework/docs/sui-framework/pay.md
@@ -80,9 +80,7 @@ and the remaining balance is left is self.
 Implementation
 
 
-
public entry fun split<T>(
-    coin: &mut Coin<T>, split_amount: u64, ctx: &mut TxContext
-) {
+
public entry fun split<T>(coin: &mut Coin<T>, split_amount: u64, ctx: &mut TxContext) {
     keep(coin.split(split_amount, ctx), ctx)
 }
 
@@ -108,9 +106,7 @@ in split_amounts. Remaining balance is left in self. Implementation -
public entry fun split_vec<T>(
-    self: &mut Coin<T>, split_amounts: vector<u64>, ctx: &mut TxContext
-) {
+
public entry fun split_vec<T>(self: &mut Coin<T>, split_amounts: vector<u64>, ctx: &mut TxContext) {
     let (mut i, len) = (0, split_amounts.length());
     while (i < len) {
         split(self, split_amounts[i], ctx);
@@ -141,7 +137,10 @@ Aborts with EVALUE if amount is greater than or equal
 
 
 
public entry fun split_and_transfer<T>(
-    c: &mut Coin<T>, amount: u64, recipient: address, ctx: &mut TxContext
+    c: &mut Coin<T>,
+    amount: u64,
+    recipient: address,
+    ctx: &mut TxContext,
 ) {
     transfer::public_transfer(c.split(amount, ctx), recipient)
 }
@@ -168,9 +167,7 @@ not evenly divisible by n, the remainder is left in selfImplementation
 
 
-
public entry fun divide_and_keep<T>(
-    self: &mut Coin<T>, n: u64, ctx: &mut TxContext
-) {
+
public entry fun divide_and_keep<T>(self: &mut Coin<T>, n: u64, ctx: &mut TxContext) {
     let mut vec: vector<Coin<T>> = self.divide_into_n(n, ctx);
     let (mut i, len) = (0, vec.length());
     while (i < len) {
diff --git a/crates/sui-framework/docs/sui-framework/priority_queue.md b/crates/sui-framework/docs/sui-framework/priority_queue.md
index 0787135268328..ef31cb38ebc00 100644
--- a/crates/sui-framework/docs/sui-framework/priority_queue.md
+++ b/crates/sui-framework/docs/sui-framework/priority_queue.md
@@ -119,7 +119,7 @@ Create a new priority queue from the input entry vectors.
 Implementation
 
 
-
public fun new<T: drop>(mut entries: vector<Entry<T>>) : PriorityQueue<T> {
+
public fun new<T: drop>(mut entries: vector<Entry<T>>): PriorityQueue<T> {
     let len = entries.length();
     let mut i = len / 2;
     // Max heapify from the first node that is a parent (node at len / 2).
@@ -151,7 +151,7 @@ Pop the entry with the highest priority value.
 Implementation
 
 
-
public fun pop_max<T: drop>(pq: &mut PriorityQueue<T>) : (u64, T) {
+
public fun pop_max<T: drop>(pq: &mut PriorityQueue<T>): (u64, T) {
     let len = pq.entries.length();
     assert!(len > 0, EPopFromEmptyHeap);
     // Swap the max element with the last element in the entries and remove the max element.
@@ -184,7 +184,7 @@ Insert a new entry into the queue.
 
 
 
public fun insert<T: drop>(pq: &mut PriorityQueue<T>, priority: u64, value: T) {
-    pq.entries.push_back(Entry { priority, value});
+    pq.entries.push_back(Entry { priority, value });
     let index = pq.entries.length() - 1;
     restore_heap_recursive(&mut pq.entries, index);
 }
diff --git a/crates/sui-framework/docs/sui-framework/random.md b/crates/sui-framework/docs/sui-framework/random.md
index 27837f0a1cf5c..2f80a77f7213b 100644
--- a/crates/sui-framework/docs/sui-framework/random.md
+++ b/crates/sui-framework/docs/sui-framework/random.md
@@ -300,9 +300,7 @@ Can only be called by genesis or change_epoch transactions.
 Implementation
 
 
-
fun load_inner_mut(
-    self: &mut Random,
-): &mut RandomInner {
+
fun load_inner_mut(self: &mut Random): &mut RandomInner {
     let version = versioned::version(&self.inner);
 
     // Replace this with a lazy update function when we add a new version of the inner object.
@@ -332,9 +330,7 @@ Can only be called by genesis or change_epoch transactions.
 Implementation
 
 
-
fun load_inner(
-    self: &Random,
-): &RandomInner {
+
fun load_inner(self: &Random): &RandomInner {
     let version = versioned::version(&self.inner);
 
     // Replace this with a lazy update function when we add a new version of the inner object.
@@ -387,8 +383,8 @@ transaction.
         // randomness ever being generated in that epoch.
         assert!(
             (epoch > inner.epoch && new_round == 0) ||
-                (new_round == inner.randomness_round + 1),
-            EInvalidRandomnessUpdate
+                    (new_round == inner.randomness_round + 1),
+            EInvalidRandomnessUpdate,
         );
     };
 
@@ -408,6 +404,11 @@ transaction.
 
 Create a generator. Can be used to derive up to MAX_U16 * 32 random bytes.
 
+Using randomness can be error-prone if you don't observe the subtleties in its correct use, for example, randomness
+dependent code might be exploitable to attacks that carefully set the gas budget
+in a way that breaks security. For more information, see:
+https://docs.sui.io/guides/developer/advanced/randomness-onchain
+
 
 
public fun new_generator(r: &random::Random, ctx: &mut tx_context::TxContext): random::RandomGenerator
 
@@ -422,7 +423,7 @@ Create a generator. Can be used to derive up to MAX_U16 * 32 random bytes. let inner = load_inner(r); let seed = hmac_sha3_256( &inner.random_bytes, - &ctx.fresh_object_address().to_bytes() + &ctx.fresh_object_address().to_bytes(), ); RandomGenerator { seed, counter: 0, buffer: vector[] } } diff --git a/crates/sui-framework/docs/sui-framework/sui.md b/crates/sui-framework/docs/sui-framework/sui.md index d32f25fc6a385..0c87894c47b07 100644 --- a/crates/sui-framework/docs/sui-framework/sui.md +++ b/crates/sui-framework/docs/sui-framework/sui.md @@ -134,7 +134,7 @@ This should be called only once during genesis creation. // TODO: add appropriate description and logo url b"", option::none(), - ctx + ctx, ); transfer::public_freeze_object(metadata); let mut supply = treasury.treasury_into_supply(); diff --git a/crates/sui-framework/docs/sui-framework/table_vec.md b/crates/sui-framework/docs/sui-framework/table_vec.md index 6c8598d8e84cc..9477c98620d05 100644 --- a/crates/sui-framework/docs/sui-framework/table_vec.md +++ b/crates/sui-framework/docs/sui-framework/table_vec.md @@ -95,7 +95,7 @@ Create an empty TableVec.
public fun empty<Element: store>(ctx: &mut TxContext): TableVec<Element> {
     TableVec {
-        contents: table::new(ctx)
+        contents: table::new(ctx),
     }
 }
 
@@ -364,7 +364,9 @@ Aborts if i or j is out of bounds.
public fun swap<Element: store>(t: &mut TableVec<Element>, i: u64, j: u64) {
     assert!(t.length() > i, EIndexOutOfBound);
     assert!(t.length() > j, EIndexOutOfBound);
-    if (i == j) { return };
+    if (i == j) {
+        return
+    };
     let element_i = t.contents.remove(i);
     let element_j = t.contents.remove(j);
     t.contents.add(j, element_i);
diff --git a/crates/sui-framework/docs/sui-framework/token.md b/crates/sui-framework/docs/sui-framework/token.md
index 03426586ec21c..b1a1f0c96b20f 100644
--- a/crates/sui-framework/docs/sui-framework/token.md
+++ b/crates/sui-framework/docs/sui-framework/token.md
@@ -496,17 +496,18 @@ hence it is safe to use it for authorization.
 
 
 
public fun new_policy<T>(
-    _treasury_cap: &TreasuryCap<T>, ctx: &mut TxContext
+    _treasury_cap: &TreasuryCap<T>,
+    ctx: &mut TxContext,
 ): (TokenPolicy<T>, TokenPolicyCap<T>) {
     let policy = TokenPolicy {
         id: object::new(ctx),
         spent_balance: balance::zero(),
-        rules: vec_map::empty()
+        rules: vec_map::empty(),
     };
 
     let cap = TokenPolicyCap {
         id: object::new(ctx),
-        `for`: object::id(&policy)
+        `for`: object::id(&policy),
     };
 
     (policy, cap)
@@ -566,9 +567,7 @@ to be used in verification.
 Implementation
 
 
-
public fun transfer<T>(
-    t: Token<T>, recipient: address, ctx: &mut TxContext
-): ActionRequest<T> {
+
public fun transfer<T>(t: Token<T>, recipient: address, ctx: &mut TxContext): ActionRequest<T> {
     let amount = t.balance.value();
     transfer::transfer(t, recipient);
 
@@ -577,7 +576,7 @@ to be used in verification.
         amount,
         option::some(recipient),
         option::none(),
-        ctx
+        ctx,
     )
 }
 
@@ -616,7 +615,7 @@ request and join the spent balance with the balance.value(), option::none(), option::some(balance), - ctx + ctx, ) }
@@ -642,9 +641,7 @@ Convert Token into an open < Implementation -
public fun to_coin<T>(
-    t: Token<T>, ctx: &mut TxContext
-): (Coin<T>, ActionRequest<T>) {
+
public fun to_coin<T>(t: Token<T>, ctx: &mut TxContext): (Coin<T>, ActionRequest<T>) {
     let Token { id, balance } = t;
     let amount = balance.value();
     id.delete();
@@ -656,8 +653,8 @@ Convert Token into an open <
             amount,
             option::none(),
             option::none(),
-            ctx
-        )
+            ctx,
+        ),
     )
 }
 
@@ -683,13 +680,11 @@ the "from_coin" action. Implementation -
public fun from_coin<T>(
-    coin: Coin<T>, ctx: &mut TxContext
-): (Token<T>, ActionRequest<T>) {
+
public fun from_coin<T>(coin: Coin<T>, ctx: &mut TxContext): (Token<T>, ActionRequest<T>) {
     let amount = coin.value();
     let token = Token {
         id: object::new(ctx),
-        balance: coin.into_balance()
+        balance: coin.into_balance(),
     };
 
     (
@@ -699,8 +694,8 @@ the "from_coin" action.
             amount,
             option::none(),
             option::none(),
-            ctx
-        )
+            ctx,
+        ),
     )
 }
 
@@ -753,9 +748,7 @@ Aborts if the Token.split<T>( - token: &mut Token<T>, amount: u64, ctx: &mut TxContext -): Token<T> { +
public fun split<T>(token: &mut Token<T>, amount: u64, ctx: &mut TxContext): Token<T> {
     assert!(token.balance.value() >= amount, EBalanceTooLow);
     Token {
         id: object::new(ctx),
@@ -872,7 +865,7 @@ Publicly available method to allow for custom actions.
     amount: u64,
     recipient: Option<address>,
     spent_balance: Option<Balance<T>>,
-    ctx: &TxContext
+    ctx: &TxContext,
 ): ActionRequest<T> {
     ActionRequest {
         name,
@@ -917,15 +910,18 @@ Aborts if:
 
public fun confirm_request<T>(
     policy: &TokenPolicy<T>,
     request: ActionRequest<T>,
-    _ctx: &mut TxContext
+    _ctx: &mut TxContext,
 ): (String, u64, address, Option<address>) {
     assert!(request.spent_balance.is_none(), ECantConsumeBalance);
     assert!(policy.rules.contains(&request.name), EUnknownAction);
 
     let ActionRequest {
-        name, approvals,
+        name,
+        approvals,
         spent_balance,
-        amount, sender, recipient,
+        amount,
+        sender,
+        recipient,
     } = request;
 
     spent_balance.destroy_none();
@@ -974,7 +970,7 @@ See confirm_request for the list of abort conditions.
 
public fun confirm_request_mut<T>(
     policy: &mut TokenPolicy<T>,
     mut request: ActionRequest<T>,
-    ctx: &mut TxContext
+    ctx: &mut TxContext,
 ): (String, u64, address, Option<address>) {
     assert!(policy.rules.contains(&request.name), EUnknownAction);
     assert!(request.spent_balance.is_some(), EUseImmutableConfirm);
@@ -1014,12 +1010,17 @@ Aborts if request contains spent_balance due to inability of the
 
public fun confirm_with_policy_cap<T>(
     _policy_cap: &TokenPolicyCap<T>,
     request: ActionRequest<T>,
-    _ctx: &mut TxContext
+    _ctx: &mut TxContext,
 ): (String, u64, address, Option<address>) {
     assert!(request.spent_balance.is_none(), ECantConsumeBalance);
 
     let ActionRequest {
-        name, amount, sender, recipient, approvals: _, spent_balance
+        name,
+        amount,
+        sender,
+        recipient,
+        approvals: _,
+        spent_balance,
     } = request;
 
     spent_balance.destroy_none();
@@ -1056,11 +1057,15 @@ to be consumed, decreasing the total_supply of the confirm_with_treasury_cap<T>(
     treasury_cap: &mut TreasuryCap<T>,
     request: ActionRequest<T>,
-    _ctx: &mut TxContext
+    _ctx: &mut TxContext,
 ): (String, u64, address, Option<address>) {
     let ActionRequest {
-        name, amount, sender, recipient, approvals: _,
-        spent_balance
+        name,
+        amount,
+        sender,
+        recipient,
+        approvals: _,
+        spent_balance,
     } = request;
 
     if (spent_balance.is_some()) {
@@ -1096,9 +1101,7 @@ required by the TokenPolicyImplementation
 
 
-
public fun add_approval<T, W: drop>(
-    _t: W, request: &mut ActionRequest<T>, _ctx: &mut TxContext
-) {
+
public fun add_approval<T, W: drop>(_t: W, request: &mut ActionRequest<T>, _ctx: &mut TxContext) {
     request.approvals.insert(type_name::get<W>())
 }
 
@@ -1135,7 +1138,7 @@ the TokenPolicy owner. self: &mut TokenPolicy<T>, cap: &TokenPolicyCap<T>, config: Config, - _ctx: &mut TxContext + _ctx: &mut TxContext, ) { assert!(object::id(self) == cap.`for`, ENotAuthorized); df::add(&mut self.id, key<Rule>(), config) @@ -1168,9 +1171,7 @@ Aborts if the Config is not present. Implementation -
public fun rule_config<T, Rule: drop, Config: store>(
-    _rule: Rule, self: &TokenPolicy<T>
-): &Config {
+
public fun rule_config<T, Rule: drop, Config: store>(_rule: Rule, self: &TokenPolicy<T>): &Config {
     assert!(has_rule_config_with_type<T, Rule, Config>(self), ENoConfig);
     df::borrow(&self.id, key<Rule>())
 }
@@ -1204,7 +1205,9 @@ Aborts if:
 
 
 
public fun rule_config_mut<T, Rule: drop, Config: store>(
-    _rule: Rule, self: &mut TokenPolicy<T>, cap: &TokenPolicyCap<T>
+    _rule: Rule,
+    self: &mut TokenPolicy<T>,
+    cap: &TokenPolicyCap<T>,
 ): &mut Config {
     assert!(has_rule_config_with_type<T, Rule, Config>(self), ENoConfig);
     assert!(object::id(self) == cap.`for`, ENotAuthorized);
@@ -1244,7 +1247,7 @@ Aborts if:
 
public fun remove_rule_config<T, Rule, Config: store>(
     self: &mut TokenPolicy<T>,
     cap: &TokenPolicyCap<T>,
-    _ctx: &mut TxContext
+    _ctx: &mut TxContext,
 ): Config {
     assert!(has_rule_config_with_type<T, Rule, Config>(self), ENoConfig);
     assert!(object::id(self) == cap.`for`, ENotAuthorized);
@@ -1299,9 +1302,7 @@ it matches the type provided.
 Implementation
 
 
-
public fun has_rule_config_with_type<T, Rule, Config: store>(
-    self: &TokenPolicy<T>
-): bool {
+
public fun has_rule_config_with_type<T, Rule, Config: store>(self: &TokenPolicy<T>): bool {
     df::exists_with_type<RuleKey<Rule>, Config>(&self.id, key<Rule>())
 }
 
@@ -1333,7 +1334,7 @@ Aborts if the TokenPolicyCapmut TokenPolicy<T>, cap: &TokenPolicyCap<T>, action: String, - _ctx: &mut TxContext + _ctx: &mut TxContext, ) { assert!(object::id(self) == cap.`for`, ENotAuthorized); self.rules.insert(action, vec_set::empty()); @@ -1367,7 +1368,7 @@ Aborts if the TokenPolicyCapmut TokenPolicy<T>, cap: &TokenPolicyCap<T>, action: String, - _ctx: &mut TxContext + _ctx: &mut TxContext, ) { assert!(object::id(self) == cap.`for`, ENotAuthorized); self.rules.remove(&action); @@ -1400,7 +1401,7 @@ Aborts if the TokenPolicyCapmut TokenPolicy<T>, cap: &TokenPolicyCap<T>, action: String, - ctx: &mut TxContext + ctx: &mut TxContext, ) { assert!(object::id(self) == cap.`for`, ENotAuthorized); if (!self.rules.contains(&action)) { @@ -1438,7 +1439,7 @@ Aborts if the TokenPolicyCapmut TokenPolicy<T>, cap: &TokenPolicyCap<T>, action: String, - _ctx: &mut TxContext + _ctx: &mut TxContext, ) { assert!(object::id(self) == cap.`for`, ENotAuthorized); @@ -1466,9 +1467,7 @@ Mint a Token with a given Implementation -
public fun mint<T>(
-    cap: &mut TreasuryCap<T>, amount: u64, ctx: &mut TxContext
-): Token<T> {
+
public fun mint<T>(cap: &mut TreasuryCap<T>, amount: u64, ctx: &mut TxContext): Token<T> {
     let balance = cap.supply_mut().increase_supply(amount);
     Token { id: object::new(ctx), balance }
 }
@@ -1525,7 +1524,7 @@ action is only available to the TreasuryCap owner.
 
public fun flush<T>(
     self: &mut TokenPolicy<T>,
     cap: &mut TreasuryCap<T>,
-    _ctx: &mut TxContext
+    _ctx: &mut TxContext,
 ): u64 {
     let amount = self.spent_balance.value();
     let balance = self.spent_balance.split(amount);
@@ -1578,9 +1577,7 @@ Returns the rules required for a specific action.
 Implementation
 
 
-
public fun rules<T>(
-    self: &TokenPolicy<T>, action: &String
-): VecSet<TypeName> {
+
public fun rules<T>(self: &TokenPolicy<T>, action: &String): VecSet<TypeName> {
     *self.rules.get(action)
 }
 
diff --git a/crates/sui-framework/docs/sui-framework/transfer.md b/crates/sui-framework/docs/sui-framework/transfer.md index ad5eaeae4fd73..95359ab5f8057 100644 --- a/crates/sui-framework/docs/sui-framework/transfer.md +++ b/crates/sui-framework/docs/sui-framework/transfer.md @@ -321,10 +321,7 @@ that T is an object defined in the module where receivepublic fun receive<T: key>(parent: &mut UID, to_receive: Receiving<T>): T { - let Receiving { - id, - version, - } = to_receive; + let Receiving { id, version } = to_receive; receive_impl(parent.to_address(), id, version) }
@@ -353,10 +350,7 @@ The object must have store to be received outside of its defining m
public fun public_receive<T: key + store>(parent: &mut UID, to_receive: Receiving<T>): T {
-    let Receiving {
-        id,
-        version,
-    } = to_receive;
+    let Receiving { id, version } = to_receive;
     receive_impl(parent.to_address(), id, version)
 }
 
diff --git a/crates/sui-framework/docs/sui-framework/transfer_policy.md b/crates/sui-framework/docs/sui-framework/transfer_policy.md index ab7730a9eac90..c8305a9f8cffa 100644 --- a/crates/sui-framework/docs/sui-framework/transfer_policy.md +++ b/crates/sui-framework/docs/sui-framework/transfer_policy.md @@ -374,9 +374,7 @@ the transaction will fail. Implementation -
public fun new_request<T>(
-    item: ID, paid: u64, from: ID
-): TransferRequest<T> {
+
public fun new_request<T>(item: ID, paid: u64, from: ID): TransferRequest<T> {
     TransferRequest { item, paid, from, receipts: vec_set::empty() }
 }
 
@@ -404,9 +402,7 @@ available for use, the type can not be traded in kiosks. Implementation -
public fun new<T>(
-    pub: &Publisher, ctx: &mut TxContext
-): (TransferPolicy<T>, TransferPolicyCap<T>) {
+
public fun new<T>(pub: &Publisher, ctx: &mut TxContext): (TransferPolicy<T>, TransferPolicyCap<T>) {
     assert!(package::from_package<T>(pub), 0);
     let id = object::new(ctx);
     let policy_id = id.to_inner();
@@ -415,7 +411,7 @@ available for use, the type can not be traded in kiosks.
 
     (
         TransferPolicy { id, rules: vec_set::empty(), balance: balance::zero() },
-        TransferPolicyCap { id: object::new(ctx), policy_id }
+        TransferPolicyCap { id: object::new(ctx), policy_id },
     )
 }
 
@@ -474,7 +470,7 @@ is not specified, all profits are withdrawn. self: &mut TransferPolicy<T>, cap: &TransferPolicyCap<T>, amount: Option<u64>, - ctx: &mut TxContext + ctx: &mut TxContext, ): Coin<SUI> { assert!(object::id(self) == cap.policy_id, ENotOwner); @@ -512,7 +508,9 @@ Can be performed by any party as long as they own it.
public fun destroy_and_withdraw<T>(
-    self: TransferPolicy<T>, cap: TransferPolicyCap<T>, ctx: &mut TxContext
+    self: TransferPolicy<T>,
+    cap: TransferPolicyCap<T>,
+    ctx: &mut TxContext,
 ): Coin<SUI> {
     assert!(object::id(&self) == cap.policy_id, ENotOwner);
 
@@ -552,7 +550,8 @@ Kiosk trades will not be possible.
 
 
 
public fun confirm_request<T>(
-    self: &TransferPolicy<T>, request: TransferRequest<T>
+    self: &TransferPolicy<T>,
+    request: TransferRequest<T>,
 ): (ID, u64, ID) {
     let TransferRequest { item, paid, from, receipts } = request;
     let mut completed = receipts.into_keys();
@@ -599,7 +598,10 @@ even if graceful unpacking has not been implemented in a "rule module".
 
 
 
public fun add_rule<T, Rule: drop, Config: store + drop>(
-    _: Rule, policy: &mut TransferPolicy<T>, cap: &TransferPolicyCap<T>, cfg: Config
+    _: Rule,
+    policy: &mut TransferPolicy<T>,
+    cap: &TransferPolicyCap<T>,
+    cfg: Config,
 ) {
     assert!(object::id(policy) == cap.policy_id, ENotOwner);
     assert!(!has_rule<T, Rule>(policy), ERuleAlreadySet);
@@ -629,8 +631,9 @@ Get the custom Config for the Rule (can be only one per "Rule" type).
 
 
 
public fun get_rule<T, Rule: drop, Config: store + drop>(
-    _: Rule, policy: &TransferPolicy<T>)
-: &Config {
+    _: Rule,
+    policy: &TransferPolicy<T>,
+): &Config {
     df::borrow(&policy.id, RuleKey<Rule> {})
 }
 
@@ -655,9 +658,7 @@ Add some SUI to the balance of a add_to_balance<T, Rule: drop>( - _: Rule, policy: &mut TransferPolicy<T>, coin: Coin<SUI> -) { +
public fun add_to_balance<T, Rule: drop>(_: Rule, policy: &mut TransferPolicy<T>, coin: Coin<SUI>) {
     assert!(has_rule<T, Rule>(policy), EUnknownRequirement);
     coin::put(&mut policy.balance, coin)
 }
@@ -684,9 +685,7 @@ confirming that the policy requirements are satisfied.
 Implementation
 
 
-
public fun add_receipt<T, Rule: drop>(
-    _: Rule, request: &mut TransferRequest<T>
-) {
+
public fun add_receipt<T, Rule: drop>(_: Rule, request: &mut TransferRequest<T>) {
     request.receipts.insert(type_name::get<Rule>())
 }
 
@@ -737,7 +736,8 @@ Remove the Rule from the remove_rule<T, Rule: drop, Config: store + drop>( - policy: &mut TransferPolicy<T>, cap: &TransferPolicyCap<T> + policy: &mut TransferPolicy<T>, + cap: &TransferPolicyCap<T>, ) { assert!(object::id(policy) == cap.policy_id, ENotOwner); let _: Config = df::remove(&mut policy.id, RuleKey<Rule> {}); @@ -789,9 +789,7 @@ to the Tra Implementation -
public fun uid_mut_as_owner<T>(
-    self: &mut TransferPolicy<T>, cap: &TransferPolicyCap<T>,
-): &mut UID {
+
public fun uid_mut_as_owner<T>(self: &mut TransferPolicy<T>, cap: &TransferPolicyCap<T>): &mut UID {
     assert!(object::id(self) == cap.policy_id, ENotOwner);
     &mut self.id
 }
diff --git a/crates/sui-framework/docs/sui-framework/tx_context.md b/crates/sui-framework/docs/sui-framework/tx_context.md
index 97e1840b5aed6..380f047c58eeb 100644
--- a/crates/sui-framework/docs/sui-framework/tx_context.md
+++ b/crates/sui-framework/docs/sui-framework/tx_context.md
@@ -167,7 +167,7 @@ Return the epoch start time as a unix timestamp in milliseconds.
 
 
 
public fun epoch_timestamp_ms(self: &TxContext): u64 {
-   self.epoch_timestamp_ms
+    self.epoch_timestamp_ms
 }
 
diff --git a/crates/sui-framework/docs/sui-framework/url.md b/crates/sui-framework/docs/sui-framework/url.md index e0e2204219292..6129f0f7d229c 100644 --- a/crates/sui-framework/docs/sui-framework/url.md +++ b/crates/sui-framework/docs/sui-framework/url.md @@ -113,7 +113,7 @@ Get inner URL Implementation -
public fun inner_url(self: &Url): String{
+
public fun inner_url(self: &Url): String {
     self.url
 }
 
diff --git a/crates/sui-framework/docs/sui-framework/vdf.md b/crates/sui-framework/docs/sui-framework/vdf.md index 2c828040ffb3b..93bad7176aa2d 100644 --- a/crates/sui-framework/docs/sui-framework/vdf.md +++ b/crates/sui-framework/docs/sui-framework/vdf.md @@ -104,7 +104,12 @@ process. Implementation -
public fun vdf_verify(input: &vector<u8>, output: &vector<u8>, proof: &vector<u8>, iterations: u64): bool {
+
public fun vdf_verify(
+    input: &vector<u8>,
+    output: &vector<u8>,
+    proof: &vector<u8>,
+    iterations: u64,
+): bool {
     vdf_verify_internal(input, output, proof, iterations)
 }
 
@@ -129,7 +134,12 @@ The internal functions for vdf_verify_internal. Implementation -
native fun vdf_verify_internal(input: &vector<u8>, output: &vector<u8>, proof: &vector<u8>, iterations: u64): bool;
+
native fun vdf_verify_internal(
+    input: &vector<u8>,
+    output: &vector<u8>,
+    proof: &vector<u8>,
+    iterations: u64,
+): bool;
 
diff --git a/crates/sui-framework/docs/sui-framework/vec_map.md b/crates/sui-framework/docs/sui-framework/vec_map.md index 65e54aa50ddff..96b7c340f3f7f 100644 --- a/crates/sui-framework/docs/sui-framework/vec_map.md +++ b/crates/sui-framework/docs/sui-framework/vec_map.md @@ -182,7 +182,7 @@ Create an empty V Implementation -
public fun empty<K: copy, V>(): VecMap<K,V> {
+
public fun empty<K: copy, V>(): VecMap<K, V> {
     VecMap { contents: vector[] }
 }
 
@@ -208,7 +208,7 @@ Aborts if key is already bound in self. Implementation -
public fun insert<K: copy, V>(self: &mut VecMap<K,V>, key: K, value: V) {
+
public fun insert<K: copy, V>(self: &mut VecMap<K, V>, key: K, value: V) {
     assert!(!self.contains(&key), EKeyAlreadyExists);
     self.contents.push_back(Entry { key, value })
 }
@@ -234,7 +234,7 @@ Remove the entry key |-> value from self. Aborts if Implementation
 
 
-
public fun remove<K: copy, V>(self: &mut VecMap<K,V>, key: &K): (K, V) {
+
public fun remove<K: copy, V>(self: &mut VecMap<K, V>, key: &K): (K, V) {
     let idx = self.get_idx(key);
     let Entry { key, value } = self.contents.remove(idx);
     (key, value)
@@ -261,7 +261,7 @@ Pop the most recently inserted entry from the map. Aborts if the map is empty.
 Implementation
 
 
-
public fun pop<K: copy, V>(self: &mut VecMap<K,V>): (K, V) {
+
public fun pop<K: copy, V>(self: &mut VecMap<K, V>): (K, V) {
     assert!(!self.contents.is_empty(), EMapEmpty);
     let Entry { key, value } = self.contents.pop_back();
     (key, value)
@@ -289,7 +289,7 @@ Aborts if key is not bound in self.
 Implementation
 
 
-
public fun get_mut<K: copy, V>(self: &mut VecMap<K,V>, key: &K): &mut V {
+
public fun get_mut<K: copy, V>(self: &mut VecMap<K, V>, key: &K): &mut V {
     let idx = self.get_idx(key);
     let entry = &mut self.contents[idx];
     &mut entry.value
@@ -317,7 +317,7 @@ Aborts if key is not bound in self.
 Implementation
 
 
-
public fun get<K: copy, V>(self: &VecMap<K,V>, key: &K): &V {
+
public fun get<K: copy, V>(self: &VecMap<K, V>, key: &K): &V {
     let idx = self.get_idx(key);
     let entry = &self.contents[idx];
     &entry.value
@@ -346,7 +346,7 @@ Only works for a "copyable" value as references cannot be stored in Implementation
 
 
-
public fun try_get<K: copy, V: copy>(self: &VecMap<K,V>, key: &K): Option<V> {
+
public fun try_get<K: copy, V: copy>(self: &VecMap<K, V>, key: &K): Option<V> {
     if (self.contains(key)) {
         option::some(*get(self, key))
     } else {
@@ -400,7 +400,7 @@ Return the number of entries in self
 Implementation
 
 
-
public fun size<K: copy, V>(self: &VecMap<K,V>): u64 {
+
public fun size<K: copy, V>(self: &VecMap<K, V>): u64 {
     self.contents.length()
 }
 
@@ -425,7 +425,7 @@ Return true if self has 0 elements, false otherwise Implementation -
public fun is_empty<K: copy, V>(self: &VecMap<K,V>): bool {
+
public fun is_empty<K: copy, V>(self: &VecMap<K, V>): bool {
     self.size() == 0
 }
 
@@ -521,10 +521,7 @@ and are *not* sorted. Implementation -
public fun from_keys_values<K: copy, V>(
-    mut keys: vector<K>,
-    mut values: vector<V>,
-): VecMap<K, V> {
+
public fun from_keys_values<K: copy, V>(mut keys: vector<K>, mut values: vector<V>): VecMap<K, V> {
     assert!(keys.length() == values.length(), EUnequalLengths);
     keys.reverse();
     values.reverse();
@@ -591,7 +588,7 @@ Note that map entries are stored in insertion order, *not* sorted by key.
 Implementation
 
 
-
public fun get_idx_opt<K: copy, V>(self: &VecMap<K,V>, key: &K): Option<u64> {
+
public fun get_idx_opt<K: copy, V>(self: &VecMap<K, V>, key: &K): Option<u64> {
     let mut i = 0;
     let n = size(self);
     while (i < n) {
@@ -625,7 +622,7 @@ Note that map entries are stored in insertion order, *not* sorted by key.
 Implementation
 
 
-
public fun get_idx<K: copy, V>(self: &VecMap<K,V>, key: &K): u64 {
+
public fun get_idx<K: copy, V>(self: &VecMap<K, V>, key: &K): u64 {
     let idx_opt = self.get_idx_opt(key);
     assert!(idx_opt.is_some(), EKeyDoesNotExist);
     idx_opt.destroy_some()
diff --git a/crates/sui-framework/docs/sui-framework/versioned.md b/crates/sui-framework/docs/sui-framework/versioned.md
index afc62e3e59a44..6a67a4a33f744 100644
--- a/crates/sui-framework/docs/sui-framework/versioned.md
+++ b/crates/sui-framework/docs/sui-framework/versioned.md
@@ -241,7 +241,7 @@ and must be used when we upgrade.
         VersionChangeCap {
             versioned_id: object::id(self),
             old_version: self.version,
-        }
+        },
     )
 }
 
@@ -267,7 +267,12 @@ by calling remove_value_for_upgrade. Implementation -
public fun upgrade<T: store>(self: &mut Versioned, new_version: u64, new_value: T, cap: VersionChangeCap) {
+
public fun upgrade<T: store>(
+    self: &mut Versioned,
+    new_version: u64,
+    new_value: T,
+    cap: VersionChangeCap,
+) {
     let VersionChangeCap { versioned_id, old_version } = cap;
     assert!(versioned_id == object::id(self), EInvalidUpgrade);
     assert!(old_version < new_version, EInvalidUpgrade);
diff --git a/crates/sui-framework/docs/sui-framework/zklogin_verified_id.md b/crates/sui-framework/docs/sui-framework/zklogin_verified_id.md
index bf08c6b09abc7..94fcbe8369127 100644
--- a/crates/sui-framework/docs/sui-framework/zklogin_verified_id.md
+++ b/crates/sui-framework/docs/sui-framework/zklogin_verified_id.md
@@ -238,7 +238,8 @@ Delete a VerifiedID
 
 
 
public fun delete(verified_id: VerifiedID) {
-    let VerifiedID { id, owner: _, key_claim_name: _, key_claim_value: _, issuer: _, audience: _ } = verified_id;
+    let VerifiedID { id, owner: _, key_claim_name: _, key_claim_value: _, issuer: _, audience: _ } =
+        verified_id;
     id.delete();
 }
 
@@ -301,7 +302,7 @@ This function has been disabled. _key_claim_value: &String, _issuer: &String, _audience: &String, - _pin_hash: u256 + _pin_hash: u256, ): bool { assert!(false, EFunctionDisabled); false @@ -338,7 +339,7 @@ string or if the inputs are longer than the allowed upper bounds: kc_name< key_claim_value: &vector<u8>, issuer: &vector<u8>, audience: &vector<u8>, - pin_hash: u256 + pin_hash: u256, ): bool;
diff --git a/crates/sui-framework/docs/sui-framework/zklogin_verified_issuer.md b/crates/sui-framework/docs/sui-framework/zklogin_verified_issuer.md index 8579229f66cc9..94eb854e30ca7 100644 --- a/crates/sui-framework/docs/sui-framework/zklogin_verified_issuer.md +++ b/crates/sui-framework/docs/sui-framework/zklogin_verified_issuer.md @@ -183,20 +183,16 @@ Aborts with verify_zklogin_issuer( - address_seed: u256, - issuer: String, - ctx: &mut TxContext, -) { +
public fun verify_zklogin_issuer(address_seed: u256, issuer: String, ctx: &mut TxContext) {
     let sender = ctx.sender();
     assert!(check_zklogin_issuer(sender, address_seed, &issuer), EInvalidProof);
     transfer::transfer(
         VerifiedIssuer {
             id: object::new(ctx),
             owner: sender,
-            issuer
+            issuer,
         },
-        sender
+        sender,
     )
 }
 
@@ -221,11 +217,7 @@ Returns true if address was created using zklogin with the g Implementation -
public fun check_zklogin_issuer(
-    address: address,
-    address_seed: u256,
-    issuer: &String,
-): bool {
+
public fun check_zklogin_issuer(address: address, address_seed: u256, issuer: &String): bool {
     check_zklogin_issuer_internal(address, address_seed, issuer.as_bytes())
 }
 
diff --git a/crates/sui-framework/docs/sui-system/staking_pool.md b/crates/sui-framework/docs/sui-system/staking_pool.md index 782ad81ed3cc1..f51eff50b2fa2 100644 --- a/crates/sui-framework/docs/sui-system/staking_pool.md +++ b/crates/sui-framework/docs/sui-system/staking_pool.md @@ -7,10 +7,16 @@ title: Module `0x3::staking_pool` - [Resource `StakingPool`](#0x3_staking_pool_StakingPool) - [Struct `PoolTokenExchangeRate`](#0x3_staking_pool_PoolTokenExchangeRate) - [Resource `StakedSui`](#0x3_staking_pool_StakedSui) +- [Resource `FungibleStakedSui`](#0x3_staking_pool_FungibleStakedSui) +- [Resource `FungibleStakedSuiData`](#0x3_staking_pool_FungibleStakedSuiData) +- [Struct `FungibleStakedSuiDataKey`](#0x3_staking_pool_FungibleStakedSuiDataKey) - [Constants](#@Constants_0) - [Function `new`](#0x3_staking_pool_new) - [Function `request_add_stake`](#0x3_staking_pool_request_add_stake) - [Function `request_withdraw_stake`](#0x3_staking_pool_request_withdraw_stake) +- [Function `redeem_fungible_staked_sui`](#0x3_staking_pool_redeem_fungible_staked_sui) +- [Function `calculate_fungible_staked_sui_withdraw_amount`](#0x3_staking_pool_calculate_fungible_staked_sui_withdraw_amount) +- [Function `convert_to_fungible_staked_sui`](#0x3_staking_pool_convert_to_fungible_staked_sui) - [Function `withdraw_from_principal`](#0x3_staking_pool_withdraw_from_principal) - [Function `unwrap_staked_sui`](#0x3_staking_pool_unwrap_staked_sui) - [Function `deposit_rewards`](#0x3_staking_pool_deposit_rewards) @@ -22,10 +28,14 @@ title: Module `0x3::staking_pool` - [Function `deactivate_staking_pool`](#0x3_staking_pool_deactivate_staking_pool) - [Function `sui_balance`](#0x3_staking_pool_sui_balance) - [Function `pool_id`](#0x3_staking_pool_pool_id) +- [Function `fungible_staked_sui_pool_id`](#0x3_staking_pool_fungible_staked_sui_pool_id) - [Function `staked_sui_amount`](#0x3_staking_pool_staked_sui_amount) - [Function `stake_activation_epoch`](#0x3_staking_pool_stake_activation_epoch) - [Function `is_preactive`](#0x3_staking_pool_is_preactive) - [Function `is_inactive`](#0x3_staking_pool_is_inactive) +- [Function `fungible_staked_sui_value`](#0x3_staking_pool_fungible_staked_sui_value) +- [Function `split_fungible_staked_sui`](#0x3_staking_pool_split_fungible_staked_sui) +- [Function `join_fungible_staked_sui`](#0x3_staking_pool_join_fungible_staked_sui) - [Function `split`](#0x3_staking_pool_split) - [Function `split_staked_sui`](#0x3_staking_pool_split_staked_sui) - [Function `join_staked_sui`](#0x3_staking_pool_join_staked_sui) @@ -228,6 +238,116 @@ A self-custodial object holding the staked SUI tokens. + + + + +## Resource `FungibleStakedSui` + +An alternative to StakedSui that holds the pool token amount instead of the SUI balance. +StakedSui objects can be converted to FungibleStakedSuis after the initial warmup period. +The advantage of this is that you can now merge multiple StakedSui objects from different +activation epochs into a single FungibleStakedSui object. + + +
struct FungibleStakedSui has store, key
+
+ + + +
+Fields + + +
+
+id: object::UID +
+
+ +
+
+pool_id: object::ID +
+
+ ID of the staking pool we are staking with. +
+
+value: u64 +
+
+ The pool token amount. +
+
+ + +
+ + + +## Resource `FungibleStakedSuiData` + +Holds useful information + + +
struct FungibleStakedSuiData has store, key
+
+ + + +
+Fields + + +
+
+id: object::UID +
+
+ +
+
+total_supply: u64 +
+
+ fungible_staked_sui supply +
+
+principal: balance::Balance<sui::SUI> +
+
+ principal balance. Rewards are withdrawn from the reward pool +
+
+ + +
+ + + +## Struct `FungibleStakedSuiDataKey` + + + +
struct FungibleStakedSuiDataKey has copy, drop, store
+
+ + + +
+Fields + + +
+
+dummy_field: bool +
+
+ +
+
+ +
@@ -244,6 +364,15 @@ A self-custodial object holding the staked SUI tokens. + + + + +
const ECannotMintFungibleStakedSuiYet: u64 = 19;
+
+ + + @@ -316,6 +445,15 @@ A self-custodial object holding the staked SUI tokens. + + + + +
const EInvariantFailure: u64 = 20;
+
+ + + @@ -548,6 +686,193 @@ A proportional amount of pool token withdraw is recorded and processed at epoch + + + + +## Function `redeem_fungible_staked_sui` + + + +
public(friend) fun redeem_fungible_staked_sui(pool: &mut staking_pool::StakingPool, fungible_staked_sui: staking_pool::FungibleStakedSui, ctx: &tx_context::TxContext): balance::Balance<sui::SUI>
+
+ + + +
+Implementation + + +
public(package) fun redeem_fungible_staked_sui(
+    pool: &mut StakingPool,
+    fungible_staked_sui: FungibleStakedSui,
+    ctx: &TxContext
+) : Balance<SUI> {
+    let FungibleStakedSui { id, pool_id, value } = fungible_staked_sui;
+    assert!(pool_id == object::id(pool), EWrongPool);
+
+    object::delete(id);
+
+    let latest_exchange_rate = pool_token_exchange_rate_at_epoch(pool, tx_context::epoch(ctx));
+    let fungible_staked_sui_data: &mut FungibleStakedSuiData = bag::borrow_mut(
+        &mut pool.extra_fields,
+        FungibleStakedSuiDataKey {}
+    );
+
+    let (principal_amount, rewards_amount) = calculate_fungible_staked_sui_withdraw_amount(
+        latest_exchange_rate,
+        value,
+        balance::value(&fungible_staked_sui_data.principal),
+        fungible_staked_sui_data.total_supply
+    );
+
+    fungible_staked_sui_data.total_supply = fungible_staked_sui_data.total_supply - value;
+
+    let mut sui_out = balance::split(&mut fungible_staked_sui_data.principal, principal_amount);
+    balance::join(
+        &mut sui_out,
+        balance::split(&mut pool.rewards_pool, rewards_amount)
+    );
+
+    pool.pending_total_sui_withdraw = pool.pending_total_sui_withdraw + balance::value(&sui_out);
+    pool.pending_pool_token_withdraw = pool.pending_pool_token_withdraw + value;
+
+    sui_out
+}
+
+ + + +
+ + + +## Function `calculate_fungible_staked_sui_withdraw_amount` + +written in separate function so i can test with random values +returns (principal_withdraw_amount, rewards_withdraw_amount) + + +
fun calculate_fungible_staked_sui_withdraw_amount(latest_exchange_rate: staking_pool::PoolTokenExchangeRate, fungible_staked_sui_value: u64, fungible_staked_sui_data_principal_amount: u64, fungible_staked_sui_data_total_supply: u64): (u64, u64)
+
+ + + +
+Implementation + + +
fun calculate_fungible_staked_sui_withdraw_amount(
+    latest_exchange_rate: PoolTokenExchangeRate,
+    fungible_staked_sui_value: u64,
+    fungible_staked_sui_data_principal_amount: u64, // fungible_staked_sui_data.principal.value()
+    fungible_staked_sui_data_total_supply: u64, // fungible_staked_sui_data.total_supply
+) : (u64, u64) {
+    // 1. if the entire FungibleStakedSuiData supply is redeemed, how much sui should we receive?
+    let total_sui_amount = get_sui_amount(&latest_exchange_rate, fungible_staked_sui_data_total_supply);
+
+    // min with total_sui_amount to prevent underflow
+    let fungible_staked_sui_data_principal_amount = std::u64::min(
+        fungible_staked_sui_data_principal_amount,
+        total_sui_amount
+    );
+
+    // 2. how much do we need to withdraw from the rewards pool?
+    let total_rewards = total_sui_amount - fungible_staked_sui_data_principal_amount;
+
+    // 3. proportionally withdraw from both wrt the fungible_staked_sui_value.
+    let principal_withdraw_amount = ((fungible_staked_sui_value as u128)
+        * (fungible_staked_sui_data_principal_amount as u128)
+        / (fungible_staked_sui_data_total_supply as u128)) as u64;
+
+    let rewards_withdraw_amount = ((fungible_staked_sui_value as u128)
+        * (total_rewards as u128)
+        / (fungible_staked_sui_data_total_supply as u128)) as u64;
+
+    // invariant check, just in case
+    let expected_sui_amount = get_sui_amount(&latest_exchange_rate, fungible_staked_sui_value);
+    assert!(principal_withdraw_amount + rewards_withdraw_amount <= expected_sui_amount, EInvariantFailure);
+
+    (principal_withdraw_amount, rewards_withdraw_amount)
+}
+
+ + + +
+ + + +## Function `convert_to_fungible_staked_sui` + +Convert the given staked SUI to an FungibleStakedSui object + + +
public(friend) fun convert_to_fungible_staked_sui(pool: &mut staking_pool::StakingPool, staked_sui: staking_pool::StakedSui, ctx: &mut tx_context::TxContext): staking_pool::FungibleStakedSui
+
+ + + +
+Implementation + + +
public(package) fun convert_to_fungible_staked_sui(
+    pool: &mut StakingPool,
+    staked_sui: StakedSui,
+    ctx: &mut TxContext
+) : FungibleStakedSui {
+    let StakedSui { id, pool_id, stake_activation_epoch, principal } = staked_sui;
+
+    assert!(pool_id == object::id(pool), EWrongPool);
+    assert!(
+        tx_context::epoch(ctx) >= stake_activation_epoch,
+        ECannotMintFungibleStakedSuiYet
+    );
+
+    object::delete(id);
+
+
+    let exchange_rate_at_staking_epoch = pool_token_exchange_rate_at_epoch(
+        pool,
+        stake_activation_epoch
+    );
+
+    let pool_token_amount = get_token_amount(
+        &exchange_rate_at_staking_epoch,
+        balance::value(&principal)
+    );
+
+    if (!bag::contains(&pool.extra_fields, FungibleStakedSuiDataKey {})) {
+        bag::add(
+            &mut pool.extra_fields,
+            FungibleStakedSuiDataKey {},
+            FungibleStakedSuiData {
+                id: object::new(ctx),
+                total_supply: pool_token_amount,
+                principal
+            }
+        );
+    }
+    else {
+        let fungible_staked_sui_data: &mut FungibleStakedSuiData = bag::borrow_mut(
+            &mut pool.extra_fields,
+            FungibleStakedSuiDataKey {}
+        );
+        fungible_staked_sui_data.total_supply = fungible_staked_sui_data.total_supply + pool_token_amount;
+        balance::join(&mut fungible_staked_sui_data.principal, principal);
+    };
+
+    FungibleStakedSui {
+        id: object::new(ctx),
+        pool_id,
+        value: pool_token_amount,
+    }
+}
+
+ + +
@@ -892,6 +1217,28 @@ withdraws can be made to the pool. + + + + +## Function `fungible_staked_sui_pool_id` + + + +
public fun fungible_staked_sui_pool_id(fungible_staked_sui: &staking_pool::FungibleStakedSui): object::ID
+
+ + + +
+Implementation + + +
public fun fungible_staked_sui_pool_id(fungible_staked_sui: &FungibleStakedSui): ID { fungible_staked_sui.pool_id }
+
+ + +
@@ -988,6 +1335,93 @@ Returns true if the input staking pool is inactive. + + + + +## Function `fungible_staked_sui_value` + + + +
public fun fungible_staked_sui_value(fungible_staked_sui: &staking_pool::FungibleStakedSui): u64
+
+ + + +
+Implementation + + +
public fun fungible_staked_sui_value(fungible_staked_sui: &FungibleStakedSui): u64 { fungible_staked_sui.value }
+
+ + + +
+ + + +## Function `split_fungible_staked_sui` + + + +
public fun split_fungible_staked_sui(fungible_staked_sui: &mut staking_pool::FungibleStakedSui, split_amount: u64, ctx: &mut tx_context::TxContext): staking_pool::FungibleStakedSui
+
+ + + +
+Implementation + + +
public fun split_fungible_staked_sui(
+    fungible_staked_sui: &mut FungibleStakedSui,
+    split_amount: u64,
+    ctx: &mut TxContext
+): FungibleStakedSui {
+    assert!(split_amount <= fungible_staked_sui.value, EInsufficientPoolTokenBalance);
+
+    fungible_staked_sui.value = fungible_staked_sui.value - split_amount;
+
+    FungibleStakedSui {
+        id: object::new(ctx),
+        pool_id: fungible_staked_sui.pool_id,
+        value: split_amount,
+    }
+}
+
+ + + +
+ + + +## Function `join_fungible_staked_sui` + + + +
public fun join_fungible_staked_sui(self: &mut staking_pool::FungibleStakedSui, other: staking_pool::FungibleStakedSui)
+
+ + + +
+Implementation + + +
public fun join_fungible_staked_sui(self: &mut FungibleStakedSui, other: FungibleStakedSui) {
+    let FungibleStakedSui { id, pool_id, value } = other;
+    assert!(self.pool_id == pool_id, EWrongPool);
+
+    object::delete(id);
+
+    self.value = self.value + value;
+}
+
+ + +
diff --git a/crates/sui-framework/docs/sui-system/sui_system.md b/crates/sui-framework/docs/sui-system/sui_system.md index 9799ac326b842..ca151bd20b68c 100644 --- a/crates/sui-framework/docs/sui-system/sui_system.md +++ b/crates/sui-framework/docs/sui-system/sui_system.md @@ -55,6 +55,8 @@ the SuiSystemStateInner version, or vice versa. - [Function `request_add_stake_non_entry`](#0x3_sui_system_request_add_stake_non_entry) - [Function `request_add_stake_mul_coin`](#0x3_sui_system_request_add_stake_mul_coin) - [Function `request_withdraw_stake`](#0x3_sui_system_request_withdraw_stake) +- [Function `convert_to_fungible_staked_sui`](#0x3_sui_system_convert_to_fungible_staked_sui) +- [Function `redeem_fungible_staked_sui`](#0x3_sui_system_redeem_fungible_staked_sui) - [Function `request_withdraw_stake_non_entry`](#0x3_sui_system_request_withdraw_stake_non_entry) - [Function `report_validator`](#0x3_sui_system_report_validator) - [Function `undo_report_validator`](#0x3_sui_system_undo_report_validator) @@ -616,6 +618,66 @@ Withdraw stake from a validator's staking pool. + + + + +## Function `convert_to_fungible_staked_sui` + +Convert StakedSui into a FungibleStakedSui object. + + +
public fun convert_to_fungible_staked_sui(wrapper: &mut sui_system::SuiSystemState, staked_sui: staking_pool::StakedSui, ctx: &mut tx_context::TxContext): staking_pool::FungibleStakedSui
+
+ + + +
+Implementation + + +
public fun convert_to_fungible_staked_sui(
+    wrapper: &mut SuiSystemState,
+    staked_sui: StakedSui,
+    ctx: &mut TxContext,
+): FungibleStakedSui {
+    let self = load_system_state_mut(wrapper);
+    self.convert_to_fungible_staked_sui(staked_sui, ctx)
+}
+
+ + + +
+ + + +## Function `redeem_fungible_staked_sui` + +Convert FungibleStakedSui into a StakedSui object. + + +
public fun redeem_fungible_staked_sui(wrapper: &mut sui_system::SuiSystemState, fungible_staked_sui: staking_pool::FungibleStakedSui, ctx: &tx_context::TxContext): balance::Balance<sui::SUI>
+
+ + + +
+Implementation + + +
public fun redeem_fungible_staked_sui(
+    wrapper: &mut SuiSystemState,
+    fungible_staked_sui: FungibleStakedSui,
+    ctx: &TxContext,
+): Balance<SUI> {
+    let self = load_system_state_mut(wrapper);
+    self.redeem_fungible_staked_sui(fungible_staked_sui, ctx)
+}
+
+ + +
diff --git a/crates/sui-framework/docs/sui-system/sui_system_state_inner.md b/crates/sui-framework/docs/sui-system/sui_system_state_inner.md index c35887139f668..f4357743de41d 100644 --- a/crates/sui-framework/docs/sui-system/sui_system_state_inner.md +++ b/crates/sui-framework/docs/sui-system/sui_system_state_inner.md @@ -24,6 +24,8 @@ title: Module `0x3::sui_system_state_inner` - [Function `request_add_stake`](#0x3_sui_system_state_inner_request_add_stake) - [Function `request_add_stake_mul_coin`](#0x3_sui_system_state_inner_request_add_stake_mul_coin) - [Function `request_withdraw_stake`](#0x3_sui_system_state_inner_request_withdraw_stake) +- [Function `convert_to_fungible_staked_sui`](#0x3_sui_system_state_inner_convert_to_fungible_staked_sui) +- [Function `redeem_fungible_staked_sui`](#0x3_sui_system_state_inner_redeem_fungible_staked_sui) - [Function `report_validator`](#0x3_sui_system_state_inner_report_validator) - [Function `undo_report_validator`](#0x3_sui_system_state_inner_undo_report_validator) - [Function `report_validator_impl`](#0x3_sui_system_state_inner_report_validator_impl) @@ -1300,6 +1302,62 @@ Withdraw some portion of a stake from a validator's staking pool. + + + + +## Function `convert_to_fungible_staked_sui` + + + +
public(friend) fun convert_to_fungible_staked_sui(self: &mut sui_system_state_inner::SuiSystemStateInnerV2, staked_sui: staking_pool::StakedSui, ctx: &mut tx_context::TxContext): staking_pool::FungibleStakedSui
+
+ + + +
+Implementation + + +
public(package) fun convert_to_fungible_staked_sui(
+    self: &mut SuiSystemStateInnerV2,
+    staked_sui: StakedSui,
+    ctx: &mut TxContext,
+) : FungibleStakedSui {
+    self.validators.convert_to_fungible_staked_sui(staked_sui, ctx)
+}
+
+ + + +
+ + + +## Function `redeem_fungible_staked_sui` + + + +
public(friend) fun redeem_fungible_staked_sui(self: &mut sui_system_state_inner::SuiSystemStateInnerV2, fungible_staked_sui: staking_pool::FungibleStakedSui, ctx: &tx_context::TxContext): balance::Balance<sui::SUI>
+
+ + + +
+Implementation + + +
public(package) fun redeem_fungible_staked_sui(
+    self: &mut SuiSystemStateInnerV2,
+    fungible_staked_sui: FungibleStakedSui,
+    ctx: &TxContext,
+) : Balance<SUI> {
+    self.validators.redeem_fungible_staked_sui(fungible_staked_sui, ctx)
+}
+
+ + +
diff --git a/crates/sui-framework/docs/sui-system/validator.md b/crates/sui-framework/docs/sui-system/validator.md index bda8ac01894c0..db50fa3400a9d 100644 --- a/crates/sui-framework/docs/sui-system/validator.md +++ b/crates/sui-framework/docs/sui-system/validator.md @@ -8,6 +8,8 @@ title: Module `0x3::validator` - [Struct `Validator`](#0x3_validator_Validator) - [Struct `StakingRequestEvent`](#0x3_validator_StakingRequestEvent) - [Struct `UnstakingRequestEvent`](#0x3_validator_UnstakingRequestEvent) +- [Struct `ConvertingToFungibleStakedSuiEvent`](#0x3_validator_ConvertingToFungibleStakedSuiEvent) +- [Struct `RedeemingFungibleStakedSuiEvent`](#0x3_validator_RedeemingFungibleStakedSuiEvent) - [Constants](#@Constants_0) - [Function `new_metadata`](#0x3_validator_new_metadata) - [Function `new`](#0x3_validator_new) @@ -15,6 +17,8 @@ title: Module `0x3::validator` - [Function `activate`](#0x3_validator_activate) - [Function `adjust_stake_and_gas_price`](#0x3_validator_adjust_stake_and_gas_price) - [Function `request_add_stake`](#0x3_validator_request_add_stake) +- [Function `convert_to_fungible_staked_sui`](#0x3_validator_convert_to_fungible_staked_sui) +- [Function `redeem_fungible_staked_sui`](#0x3_validator_redeem_fungible_staked_sui) - [Function `request_add_stake_at_genesis`](#0x3_validator_request_add_stake_at_genesis) - [Function `request_withdraw_stake`](#0x3_validator_request_withdraw_stake) - [Function `request_set_gas_price`](#0x3_validator_request_set_gas_price) @@ -459,6 +463,92 @@ Event emitted when a new unstake request is received. + + + + +## Struct `ConvertingToFungibleStakedSuiEvent` + +Event emitted when a staked SUI is converted to a fungible staked SUI. + + +
struct ConvertingToFungibleStakedSuiEvent has copy, drop
+
+ + + +
+Fields + + +
+
+pool_id: object::ID +
+
+ +
+
+stake_activation_epoch: u64 +
+
+ +
+
+staked_sui_principal_amount: u64 +
+
+ +
+
+fungible_staked_sui_amount: u64 +
+
+ +
+
+ + +
+ + + +## Struct `RedeemingFungibleStakedSuiEvent` + +Event emitted when a fungible staked SUI is redeemed. + + +
struct RedeemingFungibleStakedSuiEvent has copy, drop
+
+ + + +
+Fields + + +
+
+pool_id: object::ID +
+
+ +
+
+fungible_staked_sui_amount: u64 +
+
+ +
+
+sui_amount: u64 +
+
+ +
+
+ +
@@ -918,6 +1008,88 @@ Request to add stake to the validator's staking pool, processed at the end of th + + + + +## Function `convert_to_fungible_staked_sui` + + + +
public(friend) fun convert_to_fungible_staked_sui(self: &mut validator::Validator, staked_sui: staking_pool::StakedSui, ctx: &mut tx_context::TxContext): staking_pool::FungibleStakedSui
+
+ + + +
+Implementation + + +
public(package) fun convert_to_fungible_staked_sui(
+    self: &mut Validator,
+    staked_sui: StakedSui,
+    ctx: &mut TxContext,
+) : FungibleStakedSui {
+    let stake_activation_epoch = staked_sui.stake_activation_epoch();
+    let staked_sui_principal_amount = staked_sui.staked_sui_amount();
+
+    let fungible_staked_sui = self.staking_pool.convert_to_fungible_staked_sui(staked_sui, ctx);
+
+    event::emit(
+        ConvertingToFungibleStakedSuiEvent {
+            pool_id: self.staking_pool_id(),
+            stake_activation_epoch,
+            staked_sui_principal_amount,
+            fungible_staked_sui_amount: fungible_staked_sui.value(),
+        }
+    );
+
+    fungible_staked_sui
+}
+
+ + + +
+ + + +## Function `redeem_fungible_staked_sui` + + + +
public(friend) fun redeem_fungible_staked_sui(self: &mut validator::Validator, fungible_staked_sui: staking_pool::FungibleStakedSui, ctx: &tx_context::TxContext): balance::Balance<sui::SUI>
+
+ + + +
+Implementation + + +
public(package) fun redeem_fungible_staked_sui(
+    self: &mut Validator,
+    fungible_staked_sui: FungibleStakedSui,
+    ctx: &TxContext,
+) : Balance<SUI> {
+    let fungible_staked_sui_amount = fungible_staked_sui.value();
+
+    let sui = self.staking_pool.redeem_fungible_staked_sui(fungible_staked_sui, ctx);
+
+    event::emit(
+        RedeemingFungibleStakedSuiEvent {
+            pool_id: self.staking_pool_id(),
+            fungible_staked_sui_amount,
+            sui_amount: sui.value(),
+        }
+    );
+
+    sui
+}
+
+ + +
diff --git a/crates/sui-framework/docs/sui-system/validator_set.md b/crates/sui-framework/docs/sui-system/validator_set.md index ed59f156c459d..5e38d48f25c0c 100644 --- a/crates/sui-framework/docs/sui-system/validator_set.md +++ b/crates/sui-framework/docs/sui-system/validator_set.md @@ -18,6 +18,8 @@ title: Module `0x3::validator_set` - [Function `request_remove_validator`](#0x3_validator_set_request_remove_validator) - [Function `request_add_stake`](#0x3_validator_set_request_add_stake) - [Function `request_withdraw_stake`](#0x3_validator_set_request_withdraw_stake) +- [Function `convert_to_fungible_staked_sui`](#0x3_validator_set_convert_to_fungible_staked_sui) +- [Function `redeem_fungible_staked_sui`](#0x3_validator_set_redeem_fungible_staked_sui) - [Function `request_set_commission_rate`](#0x3_validator_set_request_set_commission_rate) - [Function `advance_epoch`](#0x3_validator_set_advance_epoch) - [Function `update_and_process_low_stake_departures`](#0x3_validator_set_update_and_process_low_stake_departures) @@ -949,6 +951,85 @@ the stake and any rewards corresponding to it will be immediately processed. + + + + +## Function `convert_to_fungible_staked_sui` + + + +
public(friend) fun convert_to_fungible_staked_sui(self: &mut validator_set::ValidatorSet, staked_sui: staking_pool::StakedSui, ctx: &mut tx_context::TxContext): staking_pool::FungibleStakedSui
+
+ + + +
+Implementation + + +
public(package) fun convert_to_fungible_staked_sui(
+    self: &mut ValidatorSet,
+    staked_sui: StakedSui,
+    ctx: &mut TxContext,
+) : FungibleStakedSui {
+    let staking_pool_id = pool_id(&staked_sui);
+    let validator =
+        if (self.staking_pool_mappings.contains(staking_pool_id)) { // This is an active validator.
+            let validator_address = self.staking_pool_mappings[staking_pool_id];
+            get_candidate_or_active_validator_mut(self, validator_address)
+        } else { // This is an inactive pool.
+            assert!(self.inactive_validators.contains(staking_pool_id), ENoPoolFound);
+            let wrapper = &mut self.inactive_validators[staking_pool_id];
+            wrapper.load_validator_maybe_upgrade()
+        };
+
+    validator.convert_to_fungible_staked_sui(staked_sui, ctx)
+}
+
+ + + +
+ + + +## Function `redeem_fungible_staked_sui` + + + +
public(friend) fun redeem_fungible_staked_sui(self: &mut validator_set::ValidatorSet, fungible_staked_sui: staking_pool::FungibleStakedSui, ctx: &tx_context::TxContext): balance::Balance<sui::SUI>
+
+ + + +
+Implementation + + +
public(package) fun redeem_fungible_staked_sui(
+    self: &mut ValidatorSet,
+    fungible_staked_sui: FungibleStakedSui,
+    ctx: &TxContext,
+) : Balance<SUI> {
+    let staking_pool_id = fungible_staked_sui_pool_id(&fungible_staked_sui);
+
+    let validator =
+        if (self.staking_pool_mappings.contains(staking_pool_id)) { // This is an active validator.
+            let validator_address = self.staking_pool_mappings[staking_pool_id];
+            get_candidate_or_active_validator_mut(self, validator_address)
+        } else { // This is an inactive pool.
+            assert!(self.inactive_validators.contains(staking_pool_id), ENoPoolFound);
+            let wrapper = &mut self.inactive_validators[staking_pool_id];
+            wrapper.load_validator_maybe_upgrade()
+        };
+
+    validator.redeem_fungible_staked_sui(fungible_staked_sui, ctx)
+}
+
+ + +
diff --git a/crates/sui-framework/packages/move-stdlib/sources/address.move b/crates/sui-framework/packages/move-stdlib/sources/address.move index ec1416b4473bf..e33c9e5429c81 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/address.move +++ b/crates/sui-framework/packages/move-stdlib/sources/address.move @@ -3,10 +3,10 @@ /// Provides a way to get address length since it's a /// platform-specific parameter. -module std::address { - /// Should be converted to a native function. - /// Current implementation only works for Sui. - public fun length(): u64 { - 32 - } +module std::address; + +/// Should be converted to a native function. +/// Current implementation only works for Sui. +public fun length(): u64 { + 32 } diff --git a/crates/sui-framework/packages/move-stdlib/sources/ascii.move b/crates/sui-framework/packages/move-stdlib/sources/ascii.move index 60564b49893a1..5c6ffaf7e3655 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/ascii.move +++ b/crates/sui-framework/packages/move-stdlib/sources/ascii.move @@ -3,164 +3,164 @@ /// The `ASCII` module defines basic string and char newtypes in Move that verify /// that characters are valid ASCII, and that strings consist of only valid ASCII characters. -module std::ascii { - // Allows calling `.to_string()` to convert an `ascii::String` into as `string::String` - public use fun std::string::from_ascii as String.to_string; - - /// An invalid ASCII character was encountered when creating an ASCII string. - const EInvalidASCIICharacter: u64 = 0x10000; - /// An invalid index was encountered when creating a substring. - const EInvalidIndex: u64 = 0x10001; - - /// The `String` struct holds a vector of bytes that all represent - /// valid ASCII characters. Note that these ASCII characters may not all - /// be printable. To determine if a `String` contains only "printable" - /// characters you should use the `all_characters_printable` predicate - /// defined in this module. - public struct String has copy, drop, store { - bytes: vector, - } - - /// An ASCII character. - public struct Char has copy, drop, store { - byte: u8, - } - - /// Convert a `byte` into a `Char` that is checked to make sure it is valid ASCII. - public fun char(byte: u8): Char { - assert!(is_valid_char(byte), EInvalidASCIICharacter); - Char { byte } - } - - /// Convert a vector of bytes `bytes` into an `String`. Aborts if - /// `bytes` contains non-ASCII characters. - public fun string(bytes: vector): String { - let x = try_string(bytes); - assert!(x.is_some(), EInvalidASCIICharacter); - x.destroy_some() - } - - /// Convert a vector of bytes `bytes` into an `String`. Returns - /// `Some()` if the `bytes` contains all valid ASCII - /// characters. Otherwise returns `None`. - public fun try_string(bytes: vector): Option { - let is_valid = bytes.all!(|byte| is_valid_char(*byte)); - if (is_valid) option::some(String { bytes }) - else option::none() - } - - /// Returns `true` if all characters in `string` are printable characters - /// Returns `false` otherwise. Not all `String`s are printable strings. - public fun all_characters_printable(string: &String): bool { - string.bytes.all!(|byte| is_printable_char(*byte)) - } - - /// Push a `Char` to the end of the `string`. - public fun push_char(string: &mut String, char: Char) { - string.bytes.push_back(char.byte); - } - - /// Pop a `Char` from the end of the `string`. - public fun pop_char(string: &mut String): Char { - Char { byte: string.bytes.pop_back() } - } - - /// Returns the length of the `string` in bytes. - public fun length(string: &String): u64 { - string.as_bytes().length() - } - - /// Append the `other` string to the end of `string`. - public fun append(string: &mut String, other: String) { - string.bytes.append(other.into_bytes()) - } - - /// Insert the `other` string at the `at` index of `string`. - public fun insert(s: &mut String, at: u64, o: String) { - assert!(at <= s.length(), EInvalidIndex); - o.into_bytes().destroy!(|e| s.bytes.insert(e, at)); - } - - /// Copy the slice of the `string` from `i` to `j` into a new `String`. - public fun substring(string: &String, i: u64, j: u64): String { - assert!(i <= j && j <= string.length(), EInvalidIndex); - let mut bytes = vector[]; - i.range_do!(j, |i| bytes.push_back(string.bytes[i])); - String { bytes } - } - - /// Get the inner bytes of the `string` as a reference - public fun as_bytes(string: &String): &vector { - &string.bytes - } - - /// Unpack the `string` to get its backing bytes - public fun into_bytes(string: String): vector { - let String { bytes } = string; - bytes - } - - /// Unpack the `char` into its underlying bytes. - public fun byte(char: Char): u8 { - let Char { byte } = char; - byte - } - - /// Returns `true` if `b` is a valid ASCII character. - /// Returns `false` otherwise. - public fun is_valid_char(b: u8): bool { - b <= 0x7F - } - - /// Returns `true` if `byte` is an printable ASCII character. - /// Returns `false` otherwise. - public fun is_printable_char(byte: u8): bool { - byte >= 0x20 && // Disallow metacharacters +module std::ascii; + +// Allows calling `.to_string()` to convert an `ascii::String` into as `string::String` +public use fun std::string::from_ascii as String.to_string; + +/// An invalid ASCII character was encountered when creating an ASCII string. +const EInvalidASCIICharacter: u64 = 0x10000; +/// An invalid index was encountered when creating a substring. +const EInvalidIndex: u64 = 0x10001; + +/// The `String` struct holds a vector of bytes that all represent +/// valid ASCII characters. Note that these ASCII characters may not all +/// be printable. To determine if a `String` contains only "printable" +/// characters you should use the `all_characters_printable` predicate +/// defined in this module. +public struct String has copy, drop, store { + bytes: vector, +} + +/// An ASCII character. +public struct Char has copy, drop, store { + byte: u8, +} + +/// Convert a `byte` into a `Char` that is checked to make sure it is valid ASCII. +public fun char(byte: u8): Char { + assert!(is_valid_char(byte), EInvalidASCIICharacter); + Char { byte } +} + +/// Convert a vector of bytes `bytes` into an `String`. Aborts if +/// `bytes` contains non-ASCII characters. +public fun string(bytes: vector): String { + let x = try_string(bytes); + assert!(x.is_some(), EInvalidASCIICharacter); + x.destroy_some() +} + +/// Convert a vector of bytes `bytes` into an `String`. Returns +/// `Some()` if the `bytes` contains all valid ASCII +/// characters. Otherwise returns `None`. +public fun try_string(bytes: vector): Option { + let is_valid = bytes.all!(|byte| is_valid_char(*byte)); + if (is_valid) option::some(String { bytes }) + else option::none() +} + +/// Returns `true` if all characters in `string` are printable characters +/// Returns `false` otherwise. Not all `String`s are printable strings. +public fun all_characters_printable(string: &String): bool { + string.bytes.all!(|byte| is_printable_char(*byte)) +} + +/// Push a `Char` to the end of the `string`. +public fun push_char(string: &mut String, char: Char) { + string.bytes.push_back(char.byte); +} + +/// Pop a `Char` from the end of the `string`. +public fun pop_char(string: &mut String): Char { + Char { byte: string.bytes.pop_back() } +} + +/// Returns the length of the `string` in bytes. +public fun length(string: &String): u64 { + string.as_bytes().length() +} + +/// Append the `other` string to the end of `string`. +public fun append(string: &mut String, other: String) { + string.bytes.append(other.into_bytes()) +} + +/// Insert the `other` string at the `at` index of `string`. +public fun insert(s: &mut String, at: u64, o: String) { + assert!(at <= s.length(), EInvalidIndex); + o.into_bytes().destroy!(|e| s.bytes.insert(e, at)); +} + +/// Copy the slice of the `string` from `i` to `j` into a new `String`. +public fun substring(string: &String, i: u64, j: u64): String { + assert!(i <= j && j <= string.length(), EInvalidIndex); + let mut bytes = vector[]; + i.range_do!(j, |i| bytes.push_back(string.bytes[i])); + String { bytes } +} + +/// Get the inner bytes of the `string` as a reference +public fun as_bytes(string: &String): &vector { + &string.bytes +} + +/// Unpack the `string` to get its backing bytes +public fun into_bytes(string: String): vector { + let String { bytes } = string; + bytes +} + +/// Unpack the `char` into its underlying bytes. +public fun byte(char: Char): u8 { + let Char { byte } = char; + byte +} + +/// Returns `true` if `b` is a valid ASCII character. +/// Returns `false` otherwise. +public fun is_valid_char(b: u8): bool { + b <= 0x7F +} + +/// Returns `true` if `byte` is an printable ASCII character. +/// Returns `false` otherwise. +public fun is_printable_char(byte: u8): bool { + byte >= 0x20 && // Disallow metacharacters byte <= 0x7E // Don't allow DEL metacharacter - } - - /// Returns `true` if `string` is empty. - public fun is_empty(string: &String): bool { - string.bytes.is_empty() - } - - /// Convert a `string` to its uppercase equivalent. - public fun to_uppercase(string: &String): String { - let bytes = string.as_bytes().map_ref!(|byte| char_to_uppercase(*byte)); - String { bytes } - } - - /// Convert a `string` to its lowercase equivalent. - public fun to_lowercase(string: &String): String { - let bytes = string.as_bytes().map_ref!(|byte| char_to_lowercase(*byte)); - String { bytes } - } - - /// Computes the index of the first occurrence of the `substr` in the `string`. - /// Returns the length of the `string` if the `substr` is not found. - /// Returns 0 if the `substr` is empty. - public fun index_of(string: &String, substr: &String): u64 { - let mut i = 0; - let (n, m) = (string.length(), substr.length()); - if (n < m) return n; - while (i <= n - m) { - let mut j = 0; - while (j < m && string.bytes[i + j] == substr.bytes[j]) j = j + 1; - if (j == m) return i; - i = i + 1; - }; - n - } - - /// Convert a `char` to its lowercase equivalent. - fun char_to_uppercase(byte: u8): u8 { - if (byte >= 0x61 && byte <= 0x7A) byte - 0x20 - else byte - } - - /// Convert a `char` to its lowercase equivalent. - fun char_to_lowercase(byte: u8): u8 { - if (byte >= 0x41 && byte <= 0x5A) byte + 0x20 - else byte - } +} + +/// Returns `true` if `string` is empty. +public fun is_empty(string: &String): bool { + string.bytes.is_empty() +} + +/// Convert a `string` to its uppercase equivalent. +public fun to_uppercase(string: &String): String { + let bytes = string.as_bytes().map_ref!(|byte| char_to_uppercase(*byte)); + String { bytes } +} + +/// Convert a `string` to its lowercase equivalent. +public fun to_lowercase(string: &String): String { + let bytes = string.as_bytes().map_ref!(|byte| char_to_lowercase(*byte)); + String { bytes } +} + +/// Computes the index of the first occurrence of the `substr` in the `string`. +/// Returns the length of the `string` if the `substr` is not found. +/// Returns 0 if the `substr` is empty. +public fun index_of(string: &String, substr: &String): u64 { + let mut i = 0; + let (n, m) = (string.length(), substr.length()); + if (n < m) return n; + while (i <= n - m) { + let mut j = 0; + while (j < m && string.bytes[i + j] == substr.bytes[j]) j = j + 1; + if (j == m) return i; + i = i + 1; + }; + n +} + +/// Convert a `char` to its lowercase equivalent. +fun char_to_uppercase(byte: u8): u8 { + if (byte >= 0x61 && byte <= 0x7A) byte - 0x20 + else byte +} + +/// Convert a `char` to its lowercase equivalent. +fun char_to_lowercase(byte: u8): u8 { + if (byte >= 0x41 && byte <= 0x5A) byte + 0x20 + else byte } diff --git a/crates/sui-framework/packages/move-stdlib/sources/bcs.move b/crates/sui-framework/packages/move-stdlib/sources/bcs.move index 8e07273cf1ee5..7e0cec97d2a6d 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/bcs.move +++ b/crates/sui-framework/packages/move-stdlib/sources/bcs.move @@ -5,7 +5,7 @@ /// Serialization). BCS is the binary encoding for Move resources and other non-module values /// published on-chain. See https://github.com/diem/bcs#binary-canonical-serialization-bcs for more /// details on BCS. -module std::bcs { - /// Return the binary representation of `v` in BCS (Binary Canonical Serialization) format - native public fun to_bytes(v: &MoveValue): vector; -} +module std::bcs; + +/// Return the binary representation of `v` in BCS (Binary Canonical Serialization) format +public native fun to_bytes(v: &MoveValue): vector; diff --git a/crates/sui-framework/packages/move-stdlib/sources/bit_vector.move b/crates/sui-framework/packages/move-stdlib/sources/bit_vector.move index 354b72c492872..0e834f0d89d31 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/bit_vector.move +++ b/crates/sui-framework/packages/move-stdlib/sources/bit_vector.move @@ -1,111 +1,111 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -module std::bit_vector { - /// The provided index is out of bounds - const EINDEX: u64 = 0x20000; - /// An invalid length of bitvector was given - const ELENGTH: u64 = 0x20001; - - #[allow(unused_const)] - const WORD_SIZE: u64 = 1; - /// The maximum allowed bitvector size - const MAX_SIZE: u64 = 1024; - - public struct BitVector has copy, drop, store { - length: u64, - bit_field: vector, - } +module std::bit_vector; - public fun new(length: u64): BitVector { - assert!(length > 0, ELENGTH); - assert!(length < MAX_SIZE, ELENGTH); - let mut counter = 0; - let mut bit_field = vector::empty(); - while (counter < length) { - bit_field.push_back(false); - counter = counter + 1; - }; +/// The provided index is out of bounds +const EINDEX: u64 = 0x20000; +/// An invalid length of bitvector was given +const ELENGTH: u64 = 0x20001; - BitVector { - length, - bit_field, - } - } +#[allow(unused_const)] +const WORD_SIZE: u64 = 1; +/// The maximum allowed bitvector size +const MAX_SIZE: u64 = 1024; - /// Set the bit at `bit_index` in the `bitvector` regardless of its previous state. - public fun set(bitvector: &mut BitVector, bit_index: u64) { - assert!(bit_index < bitvector.bit_field.length(), EINDEX); - let x = &mut bitvector.bit_field[bit_index]; - *x = true; - } +public struct BitVector has copy, drop, store { + length: u64, + bit_field: vector, +} - /// Unset the bit at `bit_index` in the `bitvector` regardless of its previous state. - public fun unset(bitvector: &mut BitVector, bit_index: u64) { - assert!(bit_index < bitvector.bit_field.length(), EINDEX); - let x = &mut bitvector.bit_field[bit_index]; - *x = false; +public fun new(length: u64): BitVector { + assert!(length > 0, ELENGTH); + assert!(length < MAX_SIZE, ELENGTH); + let mut counter = 0; + let mut bit_field = vector::empty(); + while (counter < length) { + bit_field.push_back(false); + counter = counter + 1; + }; + + BitVector { + length, + bit_field, } +} - /// Shift the `bitvector` left by `amount`. If `amount` is greater than the - /// bitvector's length the bitvector will be zeroed out. - public fun shift_left(bitvector: &mut BitVector, amount: u64) { - if (amount >= bitvector.length) { - let len = bitvector.bit_field.length(); - let mut i = 0; - while (i < len) { - let elem = &mut bitvector.bit_field[i]; - *elem = false; - i = i + 1; - }; - } else { - let mut i = amount; - - while (i < bitvector.length) { - if (bitvector.is_index_set(i)) bitvector.set(i - amount) - else bitvector.unset(i - amount); - i = i + 1; - }; - - i = bitvector.length - amount; - - while (i < bitvector.length) { - unset(bitvector, i); - i = i + 1; - }; - } - } +/// Set the bit at `bit_index` in the `bitvector` regardless of its previous state. +public fun set(bitvector: &mut BitVector, bit_index: u64) { + assert!(bit_index < bitvector.bit_field.length(), EINDEX); + let x = &mut bitvector.bit_field[bit_index]; + *x = true; +} - /// Return the value of the bit at `bit_index` in the `bitvector`. `true` - /// represents "1" and `false` represents a 0 - public fun is_index_set(bitvector: &BitVector, bit_index: u64): bool { - assert!(bit_index < bitvector.bit_field.length(), EINDEX); - bitvector.bit_field[bit_index] - } +/// Unset the bit at `bit_index` in the `bitvector` regardless of its previous state. +public fun unset(bitvector: &mut BitVector, bit_index: u64) { + assert!(bit_index < bitvector.bit_field.length(), EINDEX); + let x = &mut bitvector.bit_field[bit_index]; + *x = false; +} - /// Return the length (number of usable bits) of this bitvector - public fun length(bitvector: &BitVector): u64 { - bitvector.bit_field.length() - } +/// Shift the `bitvector` left by `amount`. If `amount` is greater than the +/// bitvector's length the bitvector will be zeroed out. +public fun shift_left(bitvector: &mut BitVector, amount: u64) { + if (amount >= bitvector.length) { + let len = bitvector.bit_field.length(); + let mut i = 0; + while (i < len) { + let elem = &mut bitvector.bit_field[i]; + *elem = false; + i = i + 1; + }; + } else { + let mut i = amount; - /// Returns the length of the longest sequence of set bits starting at (and - /// including) `start_index` in the `bitvector`. If there is no such - /// sequence, then `0` is returned. - public fun longest_set_sequence_starting_at(bitvector: &BitVector, start_index: u64): u64 { - assert!(start_index < bitvector.length, EINDEX); - let mut index = start_index; - - // Find the greatest index in the vector such that all indices less than it are set. - while (index < bitvector.length) { - if (!bitvector.is_index_set(index)) break; - index = index + 1; + while (i < bitvector.length) { + if (bitvector.is_index_set(i)) bitvector.set(i - amount) + else bitvector.unset(i - amount); + i = i + 1; }; - index - start_index - } + i = bitvector.length - amount; - #[test_only] - public fun word_size(): u64 { - WORD_SIZE + while (i < bitvector.length) { + unset(bitvector, i); + i = i + 1; + }; } } + +/// Return the value of the bit at `bit_index` in the `bitvector`. `true` +/// represents "1" and `false` represents a 0 +public fun is_index_set(bitvector: &BitVector, bit_index: u64): bool { + assert!(bit_index < bitvector.bit_field.length(), EINDEX); + bitvector.bit_field[bit_index] +} + +/// Return the length (number of usable bits) of this bitvector +public fun length(bitvector: &BitVector): u64 { + bitvector.bit_field.length() +} + +/// Returns the length of the longest sequence of set bits starting at (and +/// including) `start_index` in the `bitvector`. If there is no such +/// sequence, then `0` is returned. +public fun longest_set_sequence_starting_at(bitvector: &BitVector, start_index: u64): u64 { + assert!(start_index < bitvector.length, EINDEX); + let mut index = start_index; + + // Find the greatest index in the vector such that all indices less than it are set. + while (index < bitvector.length) { + if (!bitvector.is_index_set(index)) break; + index = index + 1; + }; + + index - start_index +} + +#[test_only] +public fun word_size(): u64 { + WORD_SIZE +} diff --git a/crates/sui-framework/packages/move-stdlib/sources/debug.move b/crates/sui-framework/packages/move-stdlib/sources/debug.move index dc9d236a8d07d..b14064b250243 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/debug.move +++ b/crates/sui-framework/packages/move-stdlib/sources/debug.move @@ -2,8 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 /// Module providing debug functionality. -module std::debug { - native public fun print(x: &T); +module std::debug; - native public fun print_stack_trace(); -} +public native fun print(x: &T); + +public native fun print_stack_trace(); diff --git a/crates/sui-framework/packages/move-stdlib/sources/fixed_point32.move b/crates/sui-framework/packages/move-stdlib/sources/fixed_point32.move index d25eb58ed3b1c..9b1a2fe577010 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/fixed_point32.move +++ b/crates/sui-framework/packages/move-stdlib/sources/fixed_point32.move @@ -3,107 +3,106 @@ /// Defines a fixed-point numeric type with a 32-bit integer part and /// a 32-bit fractional part. +#[deprecated(note = b"Use `std::uq32_32` instead. If you need to convert from a `FixedPoint32` to a `UQ32_32`, you can use the `std::fixed_point32::get_raw_value` with `std::uq32_32::from_raw_value`.")] +module std::fixed_point32; -module std::fixed_point32 { +/// Define a fixed-point numeric type with 32 fractional bits. +/// This is just a u64 integer but it is wrapped in a struct to +/// make a unique type. This is a binary representation, so decimal +/// values may not be exactly representable, but it provides more +/// than 9 decimal digits of precision both before and after the +/// decimal point (18 digits total). For comparison, double precision +/// floating-point has less than 16 decimal digits of precision, so +/// be careful about using floating-point to convert these values to +/// decimal. +public struct FixedPoint32 has copy, drop, store { value: u64 } - /// Define a fixed-point numeric type with 32 fractional bits. - /// This is just a u64 integer but it is wrapped in a struct to - /// make a unique type. This is a binary representation, so decimal - /// values may not be exactly representable, but it provides more - /// than 9 decimal digits of precision both before and after the - /// decimal point (18 digits total). For comparison, double precision - /// floating-point has less than 16 decimal digits of precision, so - /// be careful about using floating-point to convert these values to - /// decimal. - public struct FixedPoint32 has copy, drop, store { value: u64 } +///> TODO: This is a basic constant and should be provided somewhere centrally in the framework. +const MAX_U64: u128 = 18446744073709551615; - ///> TODO: This is a basic constant and should be provided somewhere centrally in the framework. - const MAX_U64: u128 = 18446744073709551615; +/// The denominator provided was zero +const EDENOMINATOR: u64 = 0x10001; +/// The quotient value would be too large to be held in a `u64` +const EDIVISION: u64 = 0x20002; +/// The multiplied value would be too large to be held in a `u64` +const EMULTIPLICATION: u64 = 0x20003; +/// A division by zero was encountered +const EDIVISION_BY_ZERO: u64 = 0x10004; +/// The computed ratio when converting to a `FixedPoint32` would be unrepresentable +const ERATIO_OUT_OF_RANGE: u64 = 0x20005; - /// The denominator provided was zero - const EDENOMINATOR: u64 = 0x10001; - /// The quotient value would be too large to be held in a `u64` - const EDIVISION: u64 = 0x20002; - /// The multiplied value would be too large to be held in a `u64` - const EMULTIPLICATION: u64 = 0x20003; - /// A division by zero was encountered - const EDIVISION_BY_ZERO: u64 = 0x10004; - /// The computed ratio when converting to a `FixedPoint32` would be unrepresentable - const ERATIO_OUT_OF_RANGE: u64 = 0x20005; - - /// Multiply a u64 integer by a fixed-point number, truncating any - /// fractional part of the product. This will abort if the product - /// overflows. - public fun multiply_u64(val: u64, multiplier: FixedPoint32): u64 { - // The product of two 64 bit values has 128 bits, so perform the - // multiplication with u128 types and keep the full 128 bit product - // to avoid losing accuracy. - let unscaled_product = val as u128 * (multiplier.value as u128); - // The unscaled product has 32 fractional bits (from the multiplier) - // so rescale it by shifting away the low bits. - let product = unscaled_product >> 32; - // Check whether the value is too large. - assert!(product <= MAX_U64, EMULTIPLICATION); - product as u64 - } +/// Multiply a u64 integer by a fixed-point number, truncating any +/// fractional part of the product. This will abort if the product +/// overflows. +public fun multiply_u64(val: u64, multiplier: FixedPoint32): u64 { + // The product of two 64 bit values has 128 bits, so perform the + // multiplication with u128 types and keep the full 128 bit product + // to avoid losing accuracy. + let unscaled_product = val as u128 * (multiplier.value as u128); + // The unscaled product has 32 fractional bits (from the multiplier) + // so rescale it by shifting away the low bits. + let product = unscaled_product >> 32; + // Check whether the value is too large. + assert!(product <= MAX_U64, EMULTIPLICATION); + product as u64 +} - /// Divide a u64 integer by a fixed-point number, truncating any - /// fractional part of the quotient. This will abort if the divisor - /// is zero or if the quotient overflows. - public fun divide_u64(val: u64, divisor: FixedPoint32): u64 { - // Check for division by zero. - assert!(divisor.value != 0, EDIVISION_BY_ZERO); - // First convert to 128 bits and then shift left to - // add 32 fractional zero bits to the dividend. - let scaled_value = val as u128 << 32; - let quotient = scaled_value / (divisor.value as u128); - // Check whether the value is too large. - assert!(quotient <= MAX_U64, EDIVISION); - // the value may be too large, which will cause the cast to fail - // with an arithmetic error. - quotient as u64 - } +/// Divide a u64 integer by a fixed-point number, truncating any +/// fractional part of the quotient. This will abort if the divisor +/// is zero or if the quotient overflows. +public fun divide_u64(val: u64, divisor: FixedPoint32): u64 { + // Check for division by zero. + assert!(divisor.value != 0, EDIVISION_BY_ZERO); + // First convert to 128 bits and then shift left to + // add 32 fractional zero bits to the dividend. + let scaled_value = val as u128 << 32; + let quotient = scaled_value / (divisor.value as u128); + // Check whether the value is too large. + assert!(quotient <= MAX_U64, EDIVISION); + // the value may be too large, which will cause the cast to fail + // with an arithmetic error. + quotient as u64 +} - /// Create a fixed-point value from a rational number specified by its - /// numerator and denominator. Calling this function should be preferred - /// for using `Self::create_from_raw_value` which is also available. - /// This will abort if the denominator is zero. It will also - /// abort if the numerator is nonzero and the ratio is not in the range - /// 2^-32 .. 2^32-1. When specifying decimal fractions, be careful about - /// rounding errors: if you round to display N digits after the decimal - /// point, you can use a denominator of 10^N to avoid numbers where the - /// very small imprecision in the binary representation could change the - /// rounding, e.g., 0.0125 will round down to 0.012 instead of up to 0.013. - public fun create_from_rational(numerator: u64, denominator: u64): FixedPoint32 { - // If the denominator is zero, this will abort. - // Scale the numerator to have 64 fractional bits and the denominator - // to have 32 fractional bits, so that the quotient will have 32 - // fractional bits. - let scaled_numerator = numerator as u128 << 64; - let scaled_denominator = denominator as u128 << 32; - assert!(scaled_denominator != 0, EDENOMINATOR); - let quotient = scaled_numerator / scaled_denominator; - assert!(quotient != 0 || numerator == 0, ERATIO_OUT_OF_RANGE); - // Return the quotient as a fixed-point number. We first need to check whether the cast - // can succeed. - assert!(quotient <= MAX_U64, ERATIO_OUT_OF_RANGE); - FixedPoint32 { value: quotient as u64 } - } +/// Create a fixed-point value from a rational number specified by its +/// numerator and denominator. Calling this function should be preferred +/// for using `Self::create_from_raw_value` which is also available. +/// This will abort if the denominator is zero. It will also +/// abort if the numerator is nonzero and the ratio is not in the range +/// 2^-32 .. 2^32-1. When specifying decimal fractions, be careful about +/// rounding errors: if you round to display N digits after the decimal +/// point, you can use a denominator of 10^N to avoid numbers where the +/// very small imprecision in the binary representation could change the +/// rounding, e.g., 0.0125 will round down to 0.012 instead of up to 0.013. +public fun create_from_rational(numerator: u64, denominator: u64): FixedPoint32 { + // If the denominator is zero, this will abort. + // Scale the numerator to have 64 fractional bits and the denominator + // to have 32 fractional bits, so that the quotient will have 32 + // fractional bits. + let scaled_numerator = numerator as u128 << 64; + let scaled_denominator = denominator as u128 << 32; + assert!(scaled_denominator != 0, EDENOMINATOR); + let quotient = scaled_numerator / scaled_denominator; + assert!(quotient != 0 || numerator == 0, ERATIO_OUT_OF_RANGE); + // Return the quotient as a fixed-point number. We first need to check whether the cast + // can succeed. + assert!(quotient <= MAX_U64, ERATIO_OUT_OF_RANGE); + FixedPoint32 { value: quotient as u64 } +} - /// Create a fixedpoint value from a raw value. - public fun create_from_raw_value(value: u64): FixedPoint32 { - FixedPoint32 { value } - } +/// Create a fixedpoint value from a raw value. +public fun create_from_raw_value(value: u64): FixedPoint32 { + FixedPoint32 { value } +} - /// Accessor for the raw u64 value. Other less common operations, such as - /// adding or subtracting FixedPoint32 values, can be done using the raw - /// values directly. - public fun get_raw_value(num: FixedPoint32): u64 { - num.value - } +/// Accessor for the raw u64 value. Other less common operations, such as +/// adding or subtracting FixedPoint32 values, can be done using the raw +/// values directly. +public fun get_raw_value(num: FixedPoint32): u64 { + num.value +} - /// Returns true if the ratio is zero. - public fun is_zero(num: FixedPoint32): bool { - num.value == 0 - } +/// Returns true if the ratio is zero. +public fun is_zero(num: FixedPoint32): bool { + num.value == 0 } diff --git a/crates/sui-framework/packages/move-stdlib/sources/hash.move b/crates/sui-framework/packages/move-stdlib/sources/hash.move index ed84f18a9a7fc..8ea9483da019d 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/hash.move +++ b/crates/sui-framework/packages/move-stdlib/sources/hash.move @@ -5,7 +5,7 @@ /// /// The functions in this module are natively declared both in the Move runtime /// as in the Move prover's prelude. -module std::hash { - native public fun sha2_256(data: vector): vector; - native public fun sha3_256(data: vector): vector; -} +module std::hash; + +public native fun sha2_256(data: vector): vector; +public native fun sha3_256(data: vector): vector; diff --git a/crates/sui-framework/packages/move-stdlib/sources/macros.move b/crates/sui-framework/packages/move-stdlib/sources/macros.move index f1713a984d61d..39083993f3896 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/macros.move +++ b/crates/sui-framework/packages/move-stdlib/sources/macros.move @@ -2,148 +2,146 @@ // SPDX-License-Identifier: Apache-2.0 /// This module holds shared implementation of macros used in `std` -module std::macros { - use std::string::String; - - public macro fun num_max($x: _, $y: _): _ { - let x = $x; - let y = $y; - if (x > y) x - else y - } +module std::macros; - public macro fun num_min($x: _, $y: _): _ { - let x = $x; - let y = $y; - if (x < y) x - else y - } +use std::string::String; - public macro fun num_diff($x: _, $y: _): _ { - let x = $x; - let y = $y; - if (x > y) x - y - else y - x - } - - public macro fun num_divide_and_round_up($x: _, $y: _): _ { - let x = $x; - let y = $y; - if (x % y == 0) x / y - else x / y + 1 - } +public macro fun num_max($x: _, $y: _): _ { + let x = $x; + let y = $y; + if (x > y) x + else y +} +public macro fun num_min($x: _, $y: _): _ { + let x = $x; + let y = $y; + if (x < y) x + else y +} - public macro fun num_pow($base: _, $exponent: u8): _ { - let mut base = $base; - let mut exponent = $exponent; - let mut res = 1; - while (exponent >= 1) { - if (exponent % 2 == 0) { - base = base * base; - exponent = exponent / 2; - } else { - res = res * base; - exponent = exponent - 1; - } - }; +public macro fun num_diff($x: _, $y: _): _ { + let x = $x; + let y = $y; + if (x > y) x - y + else y - x +} - res - } +public macro fun num_divide_and_round_up($x: _, $y: _): _ { + let x = $x; + let y = $y; + if (x % y == 0) x / y + else x / y + 1 +} - public macro fun num_sqrt<$T, $U>($x: $T, $bitsize: u8): $T { - let x = $x; - let mut bit = (1: $U) << $bitsize; - let mut res = (0: $U); - let mut x = x as $U; - - while (bit != 0) { - if (x >= res + bit) { - x = x - (res + bit); - res = (res >> 1) + bit; - } else { - res = res >> 1; - }; - bit = bit >> 2; - }; +public macro fun num_pow($base: _, $exponent: u8): _ { + let mut base = $base; + let mut exponent = $exponent; + let mut res = 1; + while (exponent >= 1) { + if (exponent % 2 == 0) { + base = base * base; + exponent = exponent / 2; + } else { + res = res * base; + exponent = exponent - 1; + } + }; - res as $T - } + res +} - public macro fun num_to_string($x: _): String { - let mut x = $x; - if (x == 0) { - return b"0".to_string() +public macro fun num_sqrt<$T, $U>($x: $T, $bitsize: u8): $T { + let x = $x; + let mut bit = (1: $U) << $bitsize; + let mut res = (0: $U); + let mut x = x as $U; + + while (bit != 0) { + if (x >= res + bit) { + x = x - (res + bit); + res = (res >> 1) + bit; + } else { + res = res >> 1; }; - let mut buffer = vector[]; - while (x != 0) { - buffer.push_back(((48 + x % 10) as u8)); - x = x / 10; - }; - buffer.reverse(); - buffer.to_string() - } + bit = bit >> 2; + }; - public macro fun range_do($start: _, $stop: _, $f: |_|) { - let mut i = $start; - let stop = $stop; - while (i < stop) { - $f(i); - i = i + 1; - } - } + res as $T +} - public macro fun range_do_eq($start: _, $stop: _, $f: |_|) { - let mut i = $start; - let stop = $stop; - // we check `i >= stop` inside the loop instead of `i <= stop` as `while` condition to avoid - // incrementing `i` past the MAX integer value. - // Because of this, we need to check if `i > stop` and return early--instead of letting the - // loop bound handle it, like in the `range_do` macro. - if (i > stop) return; - loop { - $f(i); - if (i >= stop) break; - i = i + 1; - } - } +public macro fun num_to_string($x: _): String { + let mut x = $x; + if (x == 0) { + return b"0".to_string() + }; + let mut buffer = vector[]; + while (x != 0) { + buffer.push_back(((48 + x % 10) as u8)); + x = x / 10; + }; + buffer.reverse(); + buffer.to_string() +} - public macro fun do($stop: _, $f: |_|) { - range_do!(0, $stop, $f) +public macro fun range_do($start: _, $stop: _, $f: |_|) { + let mut i = $start; + let stop = $stop; + while (i < stop) { + $f(i); + i = i + 1; } +} - public macro fun do_eq($stop: _, $f: |_|) { - range_do_eq!(0, $stop, $f) +public macro fun range_do_eq($start: _, $stop: _, $f: |_|) { + let mut i = $start; + let stop = $stop; + // we check `i >= stop` inside the loop instead of `i <= stop` as `while` condition to avoid + // incrementing `i` past the MAX integer value. + // Because of this, we need to check if `i > stop` and return early--instead of letting the + // loop bound handle it, like in the `range_do` macro. + if (i > stop) return; + loop { + $f(i); + if (i >= stop) break; + i = i + 1; } +} - public macro fun try_as_u8($x: _): Option { - let x = $x; - if (x > 0xFF) option::none() - else option::some(x as u8) - } +public macro fun do($stop: _, $f: |_|) { + range_do!(0, $stop, $f) +} - public macro fun try_as_u16($x: _): Option { - let x = $x; - if (x > 0xFFFF) option::none() - else option::some(x as u16) - } +public macro fun do_eq($stop: _, $f: |_|) { + range_do_eq!(0, $stop, $f) +} - public macro fun try_as_u32($x: _): Option { - let x = $x; - if (x > 0xFFFF_FFFF) option::none() - else option::some(x as u32) - } +public macro fun try_as_u8($x: _): Option { + let x = $x; + if (x > 0xFF) option::none() + else option::some(x as u8) +} - public macro fun try_as_u64($x: _): Option { - let x = $x; - if (x > 0xFFFF_FFFF_FFFF_FFFF) option::none() - else option::some(x as u64) - } +public macro fun try_as_u16($x: _): Option { + let x = $x; + if (x > 0xFFFF) option::none() + else option::some(x as u16) +} - public macro fun try_as_u128($x: _): Option { - let x = $x; - if (x > 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF) option::none() - else option::some(x as u128) - } +public macro fun try_as_u32($x: _): Option { + let x = $x; + if (x > 0xFFFF_FFFF) option::none() + else option::some(x as u32) +} + +public macro fun try_as_u64($x: _): Option { + let x = $x; + if (x > 0xFFFF_FFFF_FFFF_FFFF) option::none() + else option::some(x as u64) +} +public macro fun try_as_u128($x: _): Option { + let x = $x; + if (x > 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF) option::none() + else option::some(x as u128) } diff --git a/crates/sui-framework/packages/move-stdlib/sources/option.move b/crates/sui-framework/packages/move-stdlib/sources/option.move index 00d5b9a20686f..857e76ae0d2bb 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/option.move +++ b/crates/sui-framework/packages/move-stdlib/sources/option.move @@ -2,252 +2,250 @@ // SPDX-License-Identifier: Apache-2.0 /// This module defines the Option type and its methods to represent and handle an optional value. -module std::option { - /// Abstraction of a value that may or may not be present. Implemented with a vector of size - /// zero or one because Move bytecode does not have ADTs. - public struct Option has copy, drop, store { - vec: vector - } +module std::option; - /// The `Option` is in an invalid state for the operation attempted. - /// The `Option` is `Some` while it should be `None`. - const EOPTION_IS_SET: u64 = 0x40000; - /// The `Option` is in an invalid state for the operation attempted. - /// The `Option` is `None` while it should be `Some`. - const EOPTION_NOT_SET: u64 = 0x40001; +/// Abstraction of a value that may or may not be present. Implemented with a vector of size +/// zero or one because Move bytecode does not have ADTs. +public struct Option has copy, drop, store { + vec: vector, +} - /// Return an empty `Option` - public fun none(): Option { - Option { vec: vector::empty() } - } +/// The `Option` is in an invalid state for the operation attempted. +/// The `Option` is `Some` while it should be `None`. +const EOPTION_IS_SET: u64 = 0x40000; +/// The `Option` is in an invalid state for the operation attempted. +/// The `Option` is `None` while it should be `Some`. +const EOPTION_NOT_SET: u64 = 0x40001; - /// Return an `Option` containing `e` - public fun some(e: Element): Option { - Option { vec: vector::singleton(e) } - } +/// Return an empty `Option` +public fun none(): Option { + Option { vec: vector::empty() } +} - /// Return true if `t` does not hold a value - public fun is_none(t: &Option): bool { - t.vec.is_empty() - } +/// Return an `Option` containing `e` +public fun some(e: Element): Option { + Option { vec: vector::singleton(e) } +} - /// Return true if `t` holds a value - public fun is_some(t: &Option): bool { - !t.vec.is_empty() - } +/// Return true if `t` does not hold a value +public fun is_none(t: &Option): bool { + t.vec.is_empty() +} - /// Return true if the value in `t` is equal to `e_ref` - /// Always returns `false` if `t` does not hold a value - public fun contains(t: &Option, e_ref: &Element): bool { - t.vec.contains(e_ref) - } +/// Return true if `t` holds a value +public fun is_some(t: &Option): bool { + !t.vec.is_empty() +} - /// Return an immutable reference to the value inside `t` - /// Aborts if `t` does not hold a value - public fun borrow(t: &Option): &Element { - assert!(t.is_some(), EOPTION_NOT_SET); - &t.vec[0] - } +/// Return true if the value in `t` is equal to `e_ref` +/// Always returns `false` if `t` does not hold a value +public fun contains(t: &Option, e_ref: &Element): bool { + t.vec.contains(e_ref) +} - /// Return a reference to the value inside `t` if it holds one - /// Return `default_ref` if `t` does not hold a value - public fun borrow_with_default(t: &Option, default_ref: &Element): &Element { - let vec_ref = &t.vec; - if (vec_ref.is_empty()) default_ref - else &vec_ref[0] - } +/// Return an immutable reference to the value inside `t` +/// Aborts if `t` does not hold a value +public fun borrow(t: &Option): &Element { + assert!(t.is_some(), EOPTION_NOT_SET); + &t.vec[0] +} - /// Return the value inside `t` if it holds one - /// Return `default` if `t` does not hold a value - public fun get_with_default( - t: &Option, - default: Element, - ): Element { - let vec_ref = &t.vec; - if (vec_ref.is_empty()) default - else vec_ref[0] - } +/// Return a reference to the value inside `t` if it holds one +/// Return `default_ref` if `t` does not hold a value +public fun borrow_with_default(t: &Option, default_ref: &Element): &Element { + let vec_ref = &t.vec; + if (vec_ref.is_empty()) default_ref + else &vec_ref[0] +} - /// Convert the none option `t` to a some option by adding `e`. - /// Aborts if `t` already holds a value - public fun fill(t: &mut Option, e: Element) { - let vec_ref = &mut t.vec; - if (vec_ref.is_empty()) vec_ref.push_back(e) - else abort EOPTION_IS_SET - } +/// Return the value inside `t` if it holds one +/// Return `default` if `t` does not hold a value +public fun get_with_default(t: &Option, default: Element): Element { + let vec_ref = &t.vec; + if (vec_ref.is_empty()) default + else vec_ref[0] +} - /// Convert a `some` option to a `none` by removing and returning the value stored inside `t` - /// Aborts if `t` does not hold a value - public fun extract(t: &mut Option): Element { - assert!(t.is_some(), EOPTION_NOT_SET); - t.vec.pop_back() - } +/// Convert the none option `t` to a some option by adding `e`. +/// Aborts if `t` already holds a value +public fun fill(t: &mut Option, e: Element) { + let vec_ref = &mut t.vec; + if (vec_ref.is_empty()) vec_ref.push_back(e) + else abort EOPTION_IS_SET +} - /// Return a mutable reference to the value inside `t` - /// Aborts if `t` does not hold a value - public fun borrow_mut(t: &mut Option): &mut Element { - assert!(t.is_some(), EOPTION_NOT_SET); - &mut t.vec[0] - } +/// Convert a `some` option to a `none` by removing and returning the value stored inside `t` +/// Aborts if `t` does not hold a value +public fun extract(t: &mut Option): Element { + assert!(t.is_some(), EOPTION_NOT_SET); + t.vec.pop_back() +} - /// Swap the old value inside `t` with `e` and return the old value - /// Aborts if `t` does not hold a value - public fun swap(t: &mut Option, e: Element): Element { - assert!(t.is_some(), EOPTION_NOT_SET); - let vec_ref = &mut t.vec; - let old_value = vec_ref.pop_back(); - vec_ref.push_back(e); - old_value - } +/// Return a mutable reference to the value inside `t` +/// Aborts if `t` does not hold a value +public fun borrow_mut(t: &mut Option): &mut Element { + assert!(t.is_some(), EOPTION_NOT_SET); + &mut t.vec[0] +} - /// Swap the old value inside `t` with `e` and return the old value; - /// or if there is no old value, fill it with `e`. - /// Different from swap(), swap_or_fill() allows for `t` not holding a value. - public fun swap_or_fill(t: &mut Option, e: Element): Option { - let vec_ref = &mut t.vec; - let old_value = if (vec_ref.is_empty()) none() - else some(vec_ref.pop_back()); - vec_ref.push_back(e); - old_value - } +/// Swap the old value inside `t` with `e` and return the old value +/// Aborts if `t` does not hold a value +public fun swap(t: &mut Option, e: Element): Element { + assert!(t.is_some(), EOPTION_NOT_SET); + let vec_ref = &mut t.vec; + let old_value = vec_ref.pop_back(); + vec_ref.push_back(e); + old_value +} - /// Destroys `t.` If `t` holds a value, return it. Returns `default` otherwise - public fun destroy_with_default(t: Option, default: Element): Element { - let Option { mut vec } = t; - if (vec.is_empty()) default - else vec.pop_back() - } +/// Swap the old value inside `t` with `e` and return the old value; +/// or if there is no old value, fill it with `e`. +/// Different from swap(), swap_or_fill() allows for `t` not holding a value. +public fun swap_or_fill(t: &mut Option, e: Element): Option { + let vec_ref = &mut t.vec; + let old_value = if (vec_ref.is_empty()) none() + else some(vec_ref.pop_back()); + vec_ref.push_back(e); + old_value +} - /// Unpack `t` and return its contents - /// Aborts if `t` does not hold a value - public fun destroy_some(t: Option): Element { - assert!(t.is_some(), EOPTION_NOT_SET); - let Option { mut vec } = t; - let elem = vec.pop_back(); - vec.destroy_empty(); - elem - } +/// Destroys `t.` If `t` holds a value, return it. Returns `default` otherwise +public fun destroy_with_default(t: Option, default: Element): Element { + let Option { mut vec } = t; + if (vec.is_empty()) default + else vec.pop_back() +} - /// Unpack `t` - /// Aborts if `t` holds a value - public fun destroy_none(t: Option) { - assert!(t.is_none(), EOPTION_IS_SET); - let Option { vec } = t; - vec.destroy_empty() - } - /// Convert `t` into a vector of length 1 if it is `Some`, - /// and an empty vector otherwise - public fun to_vec(t: Option): vector { - let Option { vec } = t; - vec - } +/// Unpack `t` and return its contents +/// Aborts if `t` does not hold a value +public fun destroy_some(t: Option): Element { + assert!(t.is_some(), EOPTION_NOT_SET); + let Option { mut vec } = t; + let elem = vec.pop_back(); + vec.destroy_empty(); + elem +} - // === Macro Functions === +/// Unpack `t` +/// Aborts if `t` holds a value +public fun destroy_none(t: Option) { + assert!(t.is_none(), EOPTION_IS_SET); + let Option { vec } = t; + vec.destroy_empty() +} - /// Destroy `Option` and call the closure `f` on the value inside if it holds one. - public macro fun destroy<$T>($o: Option<$T>, $f: |$T|) { - let o = $o; - o.do!($f); - } +/// Convert `t` into a vector of length 1 if it is `Some`, +/// and an empty vector otherwise +public fun to_vec(t: Option): vector { + let Option { vec } = t; + vec +} - /// Destroy `Option` and call the closure `f` on the value inside if it holds one. - public macro fun do<$T>($o: Option<$T>, $f: |$T|) { - let o = $o; - if (o.is_some()) $f(o.destroy_some()) - else o.destroy_none() - } +// === Macro Functions === - /// Execute a closure on the value inside `t` if it holds one. - public macro fun do_ref<$T>($o: &Option<$T>, $f: |&$T|) { - let o = $o; - if (o.is_some()) $f(o.borrow()); - } +/// Destroy `Option` and call the closure `f` on the value inside if it holds one. +public macro fun destroy<$T>($o: Option<$T>, $f: |$T|) { + let o = $o; + o.do!($f); +} - /// Execute a closure on the mutable reference to the value inside `t` if it holds one. - public macro fun do_mut<$T>($o: &mut Option<$T>, $f: |&mut $T|) { - let o = $o; - if (o.is_some()) $f(o.borrow_mut()); - } +/// Destroy `Option` and call the closure `f` on the value inside if it holds one. +public macro fun do<$T>($o: Option<$T>, $f: |$T|) { + let o = $o; + if (o.is_some()) $f(o.destroy_some()) + else o.destroy_none() +} - /// Select the first `Some` value from the two options, or `None` if both are `None`. - /// Equivalent to Rust's `a.or(b)`. - public macro fun or<$T>($o: Option<$T>, $default: Option<$T>): Option<$T> { - let o = $o; - if (o.is_some()) { - o - } else { - o.destroy_none(); - $default - } - } +/// Execute a closure on the value inside `t` if it holds one. +public macro fun do_ref<$T>($o: &Option<$T>, $f: |&$T|) { + let o = $o; + if (o.is_some()) $f(o.borrow()); +} - /// If the value is `Some`, call the closure `f` on it. Otherwise, return `None`. - /// Equivalent to Rust's `t.and_then(f)`. - public macro fun and<$T, $U>($o: Option<$T>, $f: |$T| -> Option<$U>): Option<$U> { - let o = $o; - if (o.is_some()) { - $f(o.destroy_some()) - } else { - o.destroy_none(); - none() - } - } +/// Execute a closure on the mutable reference to the value inside `t` if it holds one. +public macro fun do_mut<$T>($o: &mut Option<$T>, $f: |&mut $T|) { + let o = $o; + if (o.is_some()) $f(o.borrow_mut()); +} - /// If the value is `Some`, call the closure `f` on it. Otherwise, return `None`. - /// Equivalent to Rust's `t.and_then(f)`. - public macro fun and_ref<$T, $U>($o: &Option<$T>, $f: |&$T| -> Option<$U>): Option<$U> { - let o = $o; - if (o.is_some()) $f(o.borrow()) - else none() +/// Select the first `Some` value from the two options, or `None` if both are `None`. +/// Equivalent to Rust's `a.or(b)`. +public macro fun or<$T>($o: Option<$T>, $default: Option<$T>): Option<$T> { + let o = $o; + if (o.is_some()) { + o + } else { + o.destroy_none(); + $default } +} - /// Map an `Option` to `Option` by applying a function to a contained value. - /// Equivalent to Rust's `t.map(f)`. - public macro fun map<$T, $U>($o: Option<$T>, $f: |$T| -> $U): Option<$U> { - let o = $o; - if (o.is_some()) { - some($f(o.destroy_some())) - } else { - o.destroy_none(); - none() - } +/// If the value is `Some`, call the closure `f` on it. Otherwise, return `None`. +/// Equivalent to Rust's `t.and_then(f)`. +public macro fun and<$T, $U>($o: Option<$T>, $f: |$T| -> Option<$U>): Option<$U> { + let o = $o; + if (o.is_some()) { + $f(o.destroy_some()) + } else { + o.destroy_none(); + none() } +} - /// Map an `Option` value to `Option` by applying a function to a contained value by reference. - /// Original `Option` is preserved. - /// Equivalent to Rust's `t.map(f)`. - public macro fun map_ref<$T, $U>($o: &Option<$T>, $f: |&$T| -> $U): Option<$U> { - let o = $o; - if (o.is_some()) some($f(o.borrow())) - else none() - } +/// If the value is `Some`, call the closure `f` on it. Otherwise, return `None`. +/// Equivalent to Rust's `t.and_then(f)`. +public macro fun and_ref<$T, $U>($o: &Option<$T>, $f: |&$T| -> Option<$U>): Option<$U> { + let o = $o; + if (o.is_some()) $f(o.borrow()) + else none() +} - /// Return `None` if the value is `None`, otherwise return `Option` if the predicate `f` returns true. - public macro fun filter<$T: drop>($o: Option<$T>, $f: |&$T| -> bool): Option<$T> { - let o = $o; - if (o.is_some() && $f(o.borrow())) o - else none() +/// Map an `Option` to `Option` by applying a function to a contained value. +/// Equivalent to Rust's `t.map(f)`. +public macro fun map<$T, $U>($o: Option<$T>, $f: |$T| -> $U): Option<$U> { + let o = $o; + if (o.is_some()) { + some($f(o.destroy_some())) + } else { + o.destroy_none(); + none() } +} - /// Return `false` if the value is `None`, otherwise return the result of the predicate `f`. - public macro fun is_some_and<$T>($o: &Option<$T>, $f: |&$T| -> bool): bool { - let o = $o; - o.is_some() && $f(o.borrow()) - } +/// Map an `Option` value to `Option` by applying a function to a contained value by reference. +/// Original `Option` is preserved. +/// Equivalent to Rust's `t.map(f)`. +public macro fun map_ref<$T, $U>($o: &Option<$T>, $f: |&$T| -> $U): Option<$U> { + let o = $o; + if (o.is_some()) some($f(o.borrow())) + else none() +} + +/// Return `None` if the value is `None`, otherwise return `Option` if the predicate `f` returns true. +public macro fun filter<$T: drop>($o: Option<$T>, $f: |&$T| -> bool): Option<$T> { + let o = $o; + if (o.is_some() && $f(o.borrow())) o + else none() +} + +/// Return `false` if the value is `None`, otherwise return the result of the predicate `f`. +public macro fun is_some_and<$T>($o: &Option<$T>, $f: |&$T| -> bool): bool { + let o = $o; + o.is_some() && $f(o.borrow()) +} - /// Destroy `Option` and return the value inside if it holds one, or `default` otherwise. - /// Equivalent to Rust's `t.unwrap_or(default)`. - /// - /// Note: this function is a more efficient version of `destroy_with_default`, as it does not - /// evaluate the default value unless necessary. The `destroy_with_default` function should be - /// deprecated in favor of this function. - public macro fun destroy_or<$T>($o: Option<$T>, $default: $T): $T { - let o = $o; - if (o.is_some()) { - o.destroy_some() - } else { - o.destroy_none(); - $default - } +/// Destroy `Option` and return the value inside if it holds one, or `default` otherwise. +/// Equivalent to Rust's `t.unwrap_or(default)`. +/// +/// Note: this function is a more efficient version of `destroy_with_default`, as it does not +/// evaluate the default value unless necessary. The `destroy_with_default` function should be +/// deprecated in favor of this function. +public macro fun destroy_or<$T>($o: Option<$T>, $default: $T): $T { + let o = $o; + if (o.is_some()) { + o.destroy_some() + } else { + o.destroy_none(); + $default } } diff --git a/crates/sui-framework/packages/move-stdlib/sources/string.move b/crates/sui-framework/packages/move-stdlib/sources/string.move index 8914fa36856b1..3538c8285966a 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/string.move +++ b/crates/sui-framework/packages/move-stdlib/sources/string.move @@ -3,130 +3,127 @@ /// The `string` module defines the `String` type which represents UTF8 encoded /// strings. -module std::string { - use std::ascii; - - /// An invalid UTF8 encoding. - const EInvalidUTF8: u64 = 1; - - /// Index out of range. - const EInvalidIndex: u64 = 2; - - /// A `String` holds a sequence of bytes which is guaranteed to be in utf8 - /// format. - public struct String has copy, drop, store { - bytes: vector, - } - - /// Creates a new string from a sequence of bytes. Aborts if the bytes do - /// not represent valid utf8. - public fun utf8(bytes: vector): String { - assert!(internal_check_utf8(&bytes), EInvalidUTF8); - String { bytes } - } - - /// Convert an ASCII string to a UTF8 string - public fun from_ascii(s: ascii::String): String { - String { bytes: s.into_bytes() } - } - - /// Convert an UTF8 string to an ASCII string. - /// Aborts if `s` is not valid ASCII - public fun to_ascii(s: String): ascii::String { - let String { bytes } = s; - bytes.to_ascii_string() - } - - /// Tries to create a new string from a sequence of bytes. - public fun try_utf8(bytes: vector): Option { - if (internal_check_utf8(&bytes)) option::some(String { bytes }) - else option::none() - } - - /// Returns a reference to the underlying byte vector. - public fun as_bytes(s: &String): &vector { - &s.bytes - } - - /// Unpack the `string` to get its underlying bytes. - public fun into_bytes(s: String): vector { - let String { bytes } = s; - bytes - } - - /// Checks whether this string is empty. - public fun is_empty(s: &String): bool { - s.bytes.is_empty() - } - - /// Returns the length of this string, in bytes. - public fun length(s: &String): u64 { - s.bytes.length() - } - - /// Appends a string. - public fun append(s: &mut String, r: String) { - s.bytes.append(r.bytes) - } - - /// Appends bytes which must be in valid utf8 format. - public fun append_utf8(s: &mut String, bytes: vector) { - s.append(utf8(bytes)) - } - - /// Insert the other string at the byte index in given string. The index - /// must be at a valid utf8 char boundary. - public fun insert(s: &mut String, at: u64, o: String) { - let bytes = &s.bytes; - assert!( - at <= bytes.length() && internal_is_char_boundary(bytes, at), - EInvalidIndex, - ); - let l = s.length(); - let mut front = s.substring(0, at); - let end = s.substring(at, l); - front.append(o); - front.append(end); - *s = front; - } - - /// Returns a sub-string using the given byte indices, where `i` is the first - /// byte position and `j` is the start of the first byte not included (or the - /// length of the string). The indices must be at valid utf8 char boundaries, - /// guaranteeing that the result is valid utf8. - public fun substring(s: &String, i: u64, j: u64): String { - let bytes = &s.bytes; - let l = bytes.length(); - assert!( - j <= l && +module std::string; + +use std::ascii; + +/// An invalid UTF8 encoding. +const EInvalidUTF8: u64 = 1; + +/// Index out of range. +const EInvalidIndex: u64 = 2; + +/// A `String` holds a sequence of bytes which is guaranteed to be in utf8 +/// format. +public struct String has copy, drop, store { + bytes: vector, +} + +/// Creates a new string from a sequence of bytes. Aborts if the bytes do +/// not represent valid utf8. +public fun utf8(bytes: vector): String { + assert!(internal_check_utf8(&bytes), EInvalidUTF8); + String { bytes } +} + +/// Convert an ASCII string to a UTF8 string +public fun from_ascii(s: ascii::String): String { + String { bytes: s.into_bytes() } +} + +/// Convert an UTF8 string to an ASCII string. +/// Aborts if `s` is not valid ASCII +public fun to_ascii(s: String): ascii::String { + let String { bytes } = s; + bytes.to_ascii_string() +} + +/// Tries to create a new string from a sequence of bytes. +public fun try_utf8(bytes: vector): Option { + if (internal_check_utf8(&bytes)) option::some(String { bytes }) + else option::none() +} + +/// Returns a reference to the underlying byte vector. +public fun as_bytes(s: &String): &vector { + &s.bytes +} + +/// Unpack the `string` to get its underlying bytes. +public fun into_bytes(s: String): vector { + let String { bytes } = s; + bytes +} + +/// Checks whether this string is empty. +public fun is_empty(s: &String): bool { + s.bytes.is_empty() +} + +/// Returns the length of this string, in bytes. +public fun length(s: &String): u64 { + s.bytes.length() +} + +/// Appends a string. +public fun append(s: &mut String, r: String) { + s.bytes.append(r.bytes) +} + +/// Appends bytes which must be in valid utf8 format. +public fun append_utf8(s: &mut String, bytes: vector) { + s.append(utf8(bytes)) +} + +/// Insert the other string at the byte index in given string. The index +/// must be at a valid utf8 char boundary. +public fun insert(s: &mut String, at: u64, o: String) { + let bytes = &s.bytes; + assert!(at <= bytes.length() && internal_is_char_boundary(bytes, at), EInvalidIndex); + let l = s.length(); + let mut front = s.substring(0, at); + let end = s.substring(at, l); + front.append(o); + front.append(end); + *s = front; +} + +/// Returns a sub-string using the given byte indices, where `i` is the first +/// byte position and `j` is the start of the first byte not included (or the +/// length of the string). The indices must be at valid utf8 char boundaries, +/// guaranteeing that the result is valid utf8. +public fun substring(s: &String, i: u64, j: u64): String { + let bytes = &s.bytes; + let l = bytes.length(); + assert!( + j <= l && i <= j && internal_is_char_boundary(bytes, i) && internal_is_char_boundary(bytes, j), - EInvalidIndex, - ); - String { bytes: internal_sub_string(bytes, i, j) } - } + EInvalidIndex, + ); + String { bytes: internal_sub_string(bytes, i, j) } +} - /// Computes the index of the first occurrence of a string. Returns `s.length()` - /// if no occurrence found. - public fun index_of(s: &String, r: &String): u64 { - internal_index_of(&s.bytes, &r.bytes) - } +/// Computes the index of the first occurrence of a string. Returns `s.length()` +/// if no occurrence found. +public fun index_of(s: &String, r: &String): u64 { + internal_index_of(&s.bytes, &r.bytes) +} - // Native API +// Native API - native fun internal_check_utf8(v: &vector): bool; - native fun internal_is_char_boundary(v: &vector, i: u64): bool; - native fun internal_sub_string(v: &vector, i: u64, j: u64): vector; - native fun internal_index_of(v: &vector, r: &vector): u64; +native fun internal_check_utf8(v: &vector): bool; +native fun internal_is_char_boundary(v: &vector, i: u64): bool; +native fun internal_sub_string(v: &vector, i: u64, j: u64): vector; +native fun internal_index_of(v: &vector, r: &vector): u64; - // === Deprecated === +// === Deprecated === - #[deprecated(note = b"Use `std::string::as_bytes` instead.")] - public fun bytes(s: &String): &vector { s.as_bytes() } +#[deprecated(note = b"Use `std::string::as_bytes` instead.")] +public fun bytes(s: &String): &vector { s.as_bytes() } - #[deprecated(note = b"Use `std::string::substring` instead.")] - public fun sub_string(s: &String, i: u64, j: u64): String { - s.substring(i, j) - } +#[deprecated(note = b"Use `std::string::substring` instead.")] +public fun sub_string(s: &String, i: u64, j: u64): String { + s.substring(i, j) } diff --git a/crates/sui-framework/packages/move-stdlib/sources/type_name.move b/crates/sui-framework/packages/move-stdlib/sources/type_name.move index 70cc4407a8c5c..9b330c1c66179 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/type_name.move +++ b/crates/sui-framework/packages/move-stdlib/sources/type_name.move @@ -2,58 +2,59 @@ // SPDX-License-Identifier: Apache-2.0 /// Functionality for converting Move types into values. Use with care! -module std::type_name { - use std::ascii::{Self, String}; - use std::address; - - /// ASCII Character code for the `:` (colon) symbol. - const ASCII_COLON: u8 = 58; - - /// ASCII Character code for the `v` (lowercase v) symbol. - const ASCII_V: u8 = 118; - /// ASCII Character code for the `e` (lowercase e) symbol. - const ASCII_E: u8 = 101; - /// ASCII Character code for the `c` (lowercase c) symbol. - const ASCII_C: u8 = 99; - /// ASCII Character code for the `t` (lowercase t) symbol. - const ASCII_T: u8 = 116; - /// ASCII Character code for the `o` (lowercase o) symbol. - const ASCII_O: u8 = 111; - /// ASCII Character code for the `r` (lowercase r) symbol. - const ASCII_R: u8 = 114; - - /// The type is not from a package/module. It is a primitive type. - const ENonModuleType: u64 = 0; - - public struct TypeName has copy, drop, store { - /// String representation of the type. All types are represented - /// using their source syntax: - /// "u8", "u64", "bool", "address", "vector", and so on for primitive types. - /// Struct types are represented as fully qualified type names; e.g. - /// `00000000000000000000000000000001::string::String` or - /// `0000000000000000000000000000000a::module_name1::type_name1<0000000000000000000000000000000a::module_name2::type_name2>` - /// Addresses are hex-encoded lowercase values of length ADDRESS_LENGTH (16, 20, or 32 depending on the Move platform) - name: String, - } - - /// Return a value representation of the type `T`. Package IDs - /// that appear in fully qualified type names in the output from - /// this function are defining IDs (the ID of the package in - /// storage that first introduced the type). - public native fun get(): TypeName; - - /// Return a value representation of the type `T`. Package IDs - /// that appear in fully qualified type names in the output from - /// this function are original IDs (the ID of the first version of - /// the package, even if the type in question was introduced in a - /// later upgrade). - public native fun get_with_original_ids(): TypeName; - - /// Returns true iff the TypeName represents a primitive type, i.e. one of - /// u8, u16, u32, u64, u128, u256, bool, address, vector. - public fun is_primitive(self: &TypeName): bool { - let bytes = self.name.as_bytes(); - bytes == &b"bool" || +module std::type_name; + +use std::address; +use std::ascii::{Self, String}; + +/// ASCII Character code for the `:` (colon) symbol. +const ASCII_COLON: u8 = 58; + +/// ASCII Character code for the `v` (lowercase v) symbol. +const ASCII_V: u8 = 118; +/// ASCII Character code for the `e` (lowercase e) symbol. +const ASCII_E: u8 = 101; +/// ASCII Character code for the `c` (lowercase c) symbol. +const ASCII_C: u8 = 99; +/// ASCII Character code for the `t` (lowercase t) symbol. +const ASCII_T: u8 = 116; +/// ASCII Character code for the `o` (lowercase o) symbol. +const ASCII_O: u8 = 111; +/// ASCII Character code for the `r` (lowercase r) symbol. +const ASCII_R: u8 = 114; + +/// The type is not from a package/module. It is a primitive type. +const ENonModuleType: u64 = 0; + +public struct TypeName has copy, drop, store { + /// String representation of the type. All types are represented + /// using their source syntax: + /// "u8", "u64", "bool", "address", "vector", and so on for primitive types. + /// Struct types are represented as fully qualified type names; e.g. + /// `00000000000000000000000000000001::string::String` or + /// `0000000000000000000000000000000a::module_name1::type_name1<0000000000000000000000000000000a::module_name2::type_name2>` + /// Addresses are hex-encoded lowercase values of length ADDRESS_LENGTH (16, 20, or 32 depending on the Move platform) + name: String, +} + +/// Return a value representation of the type `T`. Package IDs +/// that appear in fully qualified type names in the output from +/// this function are defining IDs (the ID of the package in +/// storage that first introduced the type). +public native fun get(): TypeName; + +/// Return a value representation of the type `T`. Package IDs +/// that appear in fully qualified type names in the output from +/// this function are original IDs (the ID of the first version of +/// the package, even if the type in question was introduced in a +/// later upgrade). +public native fun get_with_original_ids(): TypeName; + +/// Returns true iff the TypeName represents a primitive type, i.e. one of +/// u8, u16, u32, u64, u128, u256, bool, address, vector. +public fun is_primitive(self: &TypeName): bool { + let bytes = self.name.as_bytes(); + bytes == &b"bool" || bytes == &b"u8" || bytes == &b"u16" || bytes == &b"u32" || @@ -70,58 +71,57 @@ module std::type_name { bytes[4] == ASCII_O && bytes[5] == ASCII_R, ) - } - - /// Get the String representation of `self` - public fun borrow_string(self: &TypeName): &String { - &self.name - } - - /// Get Address string (Base16 encoded), first part of the TypeName. - /// Aborts if given a primitive type. - public fun get_address(self: &TypeName): String { - assert!(!self.is_primitive(), ENonModuleType); - - // Base16 (string) representation of an address has 2 symbols per byte. - let len = address::length() * 2; - let str_bytes = self.name.as_bytes(); - let mut addr_bytes = vector[]; - let mut i = 0; - - // Read `len` bytes from the type name and push them to addr_bytes. - while (i < len) { - addr_bytes.push_back(str_bytes[i]); +} + +/// Get the String representation of `self` +public fun borrow_string(self: &TypeName): &String { + &self.name +} + +/// Get Address string (Base16 encoded), first part of the TypeName. +/// Aborts if given a primitive type. +public fun get_address(self: &TypeName): String { + assert!(!self.is_primitive(), ENonModuleType); + + // Base16 (string) representation of an address has 2 symbols per byte. + let len = address::length() * 2; + let str_bytes = self.name.as_bytes(); + let mut addr_bytes = vector[]; + let mut i = 0; + + // Read `len` bytes from the type name and push them to addr_bytes. + while (i < len) { + addr_bytes.push_back(str_bytes[i]); + i = i + 1; + }; + + ascii::string(addr_bytes) +} + +/// Get name of the module. +/// Aborts if given a primitive type. +public fun get_module(self: &TypeName): String { + assert!(!self.is_primitive(), ENonModuleType); + + // Starts after address and a double colon: `::` + let mut i = address::length() * 2 + 2; + let str_bytes = self.name.as_bytes(); + let mut module_name = vector[]; + let colon = ASCII_COLON; + loop { + let char = &str_bytes[i]; + if (char != &colon) { + module_name.push_back(*char); i = i + 1; - }; - - ascii::string(addr_bytes) - } - - /// Get name of the module. - /// Aborts if given a primitive type. - public fun get_module(self: &TypeName): String { - assert!(!self.is_primitive(), ENonModuleType); - - // Starts after address and a double colon: `::` - let mut i = address::length() * 2 + 2; - let str_bytes = self.name.as_bytes(); - let mut module_name = vector[]; - let colon = ASCII_COLON; - loop { - let char = &str_bytes[i]; - if (char != &colon) { - module_name.push_back(*char); - i = i + 1; - } else { - break - } - }; - - ascii::string(module_name) - } - - /// Convert `self` into its inner String - public fun into_string(self: TypeName): String { - self.name - } + } else { + break + } + }; + + ascii::string(module_name) +} + +/// Convert `self` into its inner String +public fun into_string(self: TypeName): String { + self.name } diff --git a/crates/sui-framework/packages/move-stdlib/sources/u128.move b/crates/sui-framework/packages/move-stdlib/sources/u128.move index d197db1b72cf7..1e3c129daad7b 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/u128.move +++ b/crates/sui-framework/packages/move-stdlib/sources/u128.move @@ -2,115 +2,115 @@ // SPDX-License-Identifier: Apache-2.0 #[defines_primitive(u128)] -module std::u128 { - use std::string::String; - - /// Returns the bitwise not of the value. - /// Each bit that is 1 becomes 0. Each bit that is 0 becomes 1. - public fun bitwise_not(x: u128): u128 { - x ^ max_value!() - } - - /// Return the larger of `x` and `y` - public fun max(x: u128, y: u128): u128 { - std::macros::num_max!(x, y) - } - - /// Return the smaller of `x` and `y` - public fun min(x: u128, y: u128): u128 { - std::macros::num_min!(x, y) - } - - /// Return the absolute value of x - y - public fun diff(x: u128, y: u128): u128 { - std::macros::num_diff!(x, y) - } - - /// Calculate x / y, but round up the result. - public fun divide_and_round_up(x: u128, y: u128): u128 { - std::macros::num_divide_and_round_up!(x, y) - } - - /// Return the value of a base raised to a power - public fun pow(base: u128, exponent: u8): u128 { - std::macros::num_pow!(base, exponent) - } - - /// Get a nearest lower integer Square Root for `x`. Given that this - /// function can only operate with integers, it is impossible - /// to get perfect (or precise) integer square root for some numbers. - /// - /// Example: - /// ``` - /// math::sqrt(9) => 3 - /// math::sqrt(8) => 2 // the nearest lower square root is 4; - /// ``` - /// - /// In integer math, one of the possible ways to get results with more - /// precision is to use higher values or temporarily multiply the - /// value by some bigger number. Ideally if this is a square of 10 or 100. - /// - /// Example: - /// ``` - /// math::sqrt(8) => 2; - /// math::sqrt(8 * 10000) => 282; - /// // now we can use this value as if it was 2.82; - /// // but to get the actual result, this value needs - /// // to be divided by 100 (because sqrt(10000)). - /// - /// - /// math::sqrt(8 * 1000000) => 2828; // same as above, 2828 / 1000 (2.828) - /// ``` - public fun sqrt(x: u128): u128 { - std::macros::num_sqrt!(x, 128) - } - - /// Try to convert a `u128` to a `u8`. Returns `None` if the value is too large. - public fun try_as_u8(x: u128): Option { - std::macros::try_as_u8!(x) - } - - /// Try to convert a `u128` to a `u16`. Returns `None` if the value is too large. - public fun try_as_u16(x: u128): Option { - std::macros::try_as_u16!(x) - } - - /// Try to convert a `u128` to a `u32`. Returns `None` if the value is too large. - public fun try_as_u32(x: u128): Option { - std::macros::try_as_u32!(x) - } - - /// Try to convert a `u128` to a `u64`. Returns `None` if the value is too large. - public fun try_as_u64(x: u128): Option { - std::macros::try_as_u64!(x) - } - - public fun to_string(x: u128): String { - std::macros::num_to_string!(x) - } - - /// Maximum value for a `u128` - public macro fun max_value(): u128 { - 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF - } - - /// Loops applying `$f` to each number from `$start` to `$stop` (exclusive) - public macro fun range_do($start: u128, $stop: u128, $f: |u128|) { - std::macros::range_do!($start, $stop, $f) - } - - /// Loops applying `$f` to each number from `$start` to `$stop` (inclusive) - public macro fun range_do_eq($start: u128, $stop: u128, $f: |u128|) { - std::macros::range_do_eq!($start, $stop, $f) - } - - /// Loops applying `$f` to each number from `0` to `$stop` (exclusive) - public macro fun do($stop: u128, $f: |u128|) { - std::macros::do!($stop, $f) - } - - /// Loops applying `$f` to each number from `0` to `$stop` (inclusive) - public macro fun do_eq($stop: u128, $f: |u128|) { - std::macros::do_eq!($stop, $f) - } +module std::u128; + +use std::string::String; + +/// Returns the bitwise not of the value. +/// Each bit that is 1 becomes 0. Each bit that is 0 becomes 1. +public fun bitwise_not(x: u128): u128 { + x ^ max_value!() +} + +/// Return the larger of `x` and `y` +public fun max(x: u128, y: u128): u128 { + std::macros::num_max!(x, y) +} + +/// Return the smaller of `x` and `y` +public fun min(x: u128, y: u128): u128 { + std::macros::num_min!(x, y) +} + +/// Return the absolute value of x - y +public fun diff(x: u128, y: u128): u128 { + std::macros::num_diff!(x, y) +} + +/// Calculate x / y, but round up the result. +public fun divide_and_round_up(x: u128, y: u128): u128 { + std::macros::num_divide_and_round_up!(x, y) +} + +/// Return the value of a base raised to a power +public fun pow(base: u128, exponent: u8): u128 { + std::macros::num_pow!(base, exponent) +} + +/// Get a nearest lower integer Square Root for `x`. Given that this +/// function can only operate with integers, it is impossible +/// to get perfect (or precise) integer square root for some numbers. +/// +/// Example: +/// ``` +/// math::sqrt(9) => 3 +/// math::sqrt(8) => 2 // the nearest lower square root is 4; +/// ``` +/// +/// In integer math, one of the possible ways to get results with more +/// precision is to use higher values or temporarily multiply the +/// value by some bigger number. Ideally if this is a square of 10 or 100. +/// +/// Example: +/// ``` +/// math::sqrt(8) => 2; +/// math::sqrt(8 * 10000) => 282; +/// // now we can use this value as if it was 2.82; +/// // but to get the actual result, this value needs +/// // to be divided by 100 (because sqrt(10000)). +/// +/// +/// math::sqrt(8 * 1000000) => 2828; // same as above, 2828 / 1000 (2.828) +/// ``` +public fun sqrt(x: u128): u128 { + std::macros::num_sqrt!(x, 128) +} + +/// Try to convert a `u128` to a `u8`. Returns `None` if the value is too large. +public fun try_as_u8(x: u128): Option { + std::macros::try_as_u8!(x) +} + +/// Try to convert a `u128` to a `u16`. Returns `None` if the value is too large. +public fun try_as_u16(x: u128): Option { + std::macros::try_as_u16!(x) +} + +/// Try to convert a `u128` to a `u32`. Returns `None` if the value is too large. +public fun try_as_u32(x: u128): Option { + std::macros::try_as_u32!(x) +} + +/// Try to convert a `u128` to a `u64`. Returns `None` if the value is too large. +public fun try_as_u64(x: u128): Option { + std::macros::try_as_u64!(x) +} + +public fun to_string(x: u128): String { + std::macros::num_to_string!(x) +} + +/// Maximum value for a `u128` +public macro fun max_value(): u128 { + 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF +} + +/// Loops applying `$f` to each number from `$start` to `$stop` (exclusive) +public macro fun range_do($start: u128, $stop: u128, $f: |u128|) { + std::macros::range_do!($start, $stop, $f) +} + +/// Loops applying `$f` to each number from `$start` to `$stop` (inclusive) +public macro fun range_do_eq($start: u128, $stop: u128, $f: |u128|) { + std::macros::range_do_eq!($start, $stop, $f) +} + +/// Loops applying `$f` to each number from `0` to `$stop` (exclusive) +public macro fun do($stop: u128, $f: |u128|) { + std::macros::do!($stop, $f) +} + +/// Loops applying `$f` to each number from `0` to `$stop` (inclusive) +public macro fun do_eq($stop: u128, $f: |u128|) { + std::macros::do_eq!($stop, $f) } diff --git a/crates/sui-framework/packages/move-stdlib/sources/u16.move b/crates/sui-framework/packages/move-stdlib/sources/u16.move index 2a8182aa633d8..fffe7440d5502 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/u16.move +++ b/crates/sui-framework/packages/move-stdlib/sources/u16.move @@ -2,100 +2,100 @@ // SPDX-License-Identifier: Apache-2.0 #[defines_primitive(u16)] -module std::u16 { - use std::string::String; - - /// Returns the bitwise not of the value. - /// Each bit that is 1 becomes 0. Each bit that is 0 becomes 1. - public fun bitwise_not(x: u16): u16 { - x ^ max_value!() - } - - /// Return the larger of `x` and `y` - public fun max(x: u16, y: u16): u16 { - std::macros::num_max!(x, y) - } - - /// Return the smaller of `x` and `y` - public fun min(x: u16, y: u16): u16 { - std::macros::num_min!(x, y) - } - - /// Return the absolute value of x - y - public fun diff(x: u16, y: u16): u16 { - std::macros::num_diff!(x, y) - } - - /// Calculate x / y, but round up the result. - public fun divide_and_round_up(x: u16, y: u16): u16 { - std::macros::num_divide_and_round_up!(x, y) - } - - /// Return the value of a base raised to a power - public fun pow(base: u16, exponent: u8): u16 { - std::macros::num_pow!(base, exponent) - } - - /// Get a nearest lower integer Square Root for `x`. Given that this - /// function can only operate with integers, it is impossible - /// to get perfect (or precise) integer square root for some numbers. - /// - /// Example: - /// ``` - /// math::sqrt(9) => 3 - /// math::sqrt(8) => 2 // the nearest lower square root is 4; - /// ``` - /// - /// In integer math, one of the possible ways to get results with more - /// precision is to use higher values or temporarily multiply the - /// value by some bigger number. Ideally if this is a square of 10 or 100. - /// - /// Example: - /// ``` - /// math::sqrt(8) => 2; - /// math::sqrt(8 * 10000) => 282; - /// // now we can use this value as if it was 2.82; - /// // but to get the actual result, this value needs - /// // to be divided by 100 (because sqrt(10000)). - /// - /// - /// math::sqrt(8 * 1000000) => 2828; // same as above, 2828 / 1000 (2.828) - /// ``` - public fun sqrt(x: u16): u16 { - std::macros::num_sqrt!(x, 16) - } - - /// Try to convert a `u16` to a `u8`. Returns `None` if the value is too large. - public fun try_as_u8(x: u16): Option { - std::macros::try_as_u8!(x) - } - - public fun to_string(x: u16): String { - std::macros::num_to_string!(x) - } - - /// Maximum value for a `u16` - public macro fun max_value(): u16 { - 0xFFFF - } - - /// Loops applying `$f` to each number from `$start` to `$stop` (exclusive) - public macro fun range_do($start: u16, $stop: u16, $f: |u16|) { - std::macros::range_do!($start, $stop, $f) - } - - /// Loops applying `$f` to each number from `$start` to `$stop` (inclusive) - public macro fun range_do_eq($start: u16, $stop: u16, $f: |u16|) { - std::macros::range_do_eq!($start, $stop, $f) - } - - /// Loops applying `$f` to each number from `0` to `$stop` (exclusive) - public macro fun do($stop: u16, $f: |u16|) { - std::macros::do!($stop, $f) - } - - /// Loops applying `$f` to each number from `0` to `$stop` (inclusive) - public macro fun do_eq($stop: u16, $f: |u16|) { - std::macros::do_eq!($stop, $f) - } +module std::u16; + +use std::string::String; + +/// Returns the bitwise not of the value. +/// Each bit that is 1 becomes 0. Each bit that is 0 becomes 1. +public fun bitwise_not(x: u16): u16 { + x ^ max_value!() +} + +/// Return the larger of `x` and `y` +public fun max(x: u16, y: u16): u16 { + std::macros::num_max!(x, y) +} + +/// Return the smaller of `x` and `y` +public fun min(x: u16, y: u16): u16 { + std::macros::num_min!(x, y) +} + +/// Return the absolute value of x - y +public fun diff(x: u16, y: u16): u16 { + std::macros::num_diff!(x, y) +} + +/// Calculate x / y, but round up the result. +public fun divide_and_round_up(x: u16, y: u16): u16 { + std::macros::num_divide_and_round_up!(x, y) +} + +/// Return the value of a base raised to a power +public fun pow(base: u16, exponent: u8): u16 { + std::macros::num_pow!(base, exponent) +} + +/// Get a nearest lower integer Square Root for `x`. Given that this +/// function can only operate with integers, it is impossible +/// to get perfect (or precise) integer square root for some numbers. +/// +/// Example: +/// ``` +/// math::sqrt(9) => 3 +/// math::sqrt(8) => 2 // the nearest lower square root is 4; +/// ``` +/// +/// In integer math, one of the possible ways to get results with more +/// precision is to use higher values or temporarily multiply the +/// value by some bigger number. Ideally if this is a square of 10 or 100. +/// +/// Example: +/// ``` +/// math::sqrt(8) => 2; +/// math::sqrt(8 * 10000) => 282; +/// // now we can use this value as if it was 2.82; +/// // but to get the actual result, this value needs +/// // to be divided by 100 (because sqrt(10000)). +/// +/// +/// math::sqrt(8 * 1000000) => 2828; // same as above, 2828 / 1000 (2.828) +/// ``` +public fun sqrt(x: u16): u16 { + std::macros::num_sqrt!(x, 16) +} + +/// Try to convert a `u16` to a `u8`. Returns `None` if the value is too large. +public fun try_as_u8(x: u16): Option { + std::macros::try_as_u8!(x) +} + +public fun to_string(x: u16): String { + std::macros::num_to_string!(x) +} + +/// Maximum value for a `u16` +public macro fun max_value(): u16 { + 0xFFFF +} + +/// Loops applying `$f` to each number from `$start` to `$stop` (exclusive) +public macro fun range_do($start: u16, $stop: u16, $f: |u16|) { + std::macros::range_do!($start, $stop, $f) +} + +/// Loops applying `$f` to each number from `$start` to `$stop` (inclusive) +public macro fun range_do_eq($start: u16, $stop: u16, $f: |u16|) { + std::macros::range_do_eq!($start, $stop, $f) +} + +/// Loops applying `$f` to each number from `0` to `$stop` (exclusive) +public macro fun do($stop: u16, $f: |u16|) { + std::macros::do!($stop, $f) +} + +/// Loops applying `$f` to each number from `0` to `$stop` (inclusive) +public macro fun do_eq($stop: u16, $f: |u16|) { + std::macros::do_eq!($stop, $f) } diff --git a/crates/sui-framework/packages/move-stdlib/sources/u256.move b/crates/sui-framework/packages/move-stdlib/sources/u256.move index 572ca4df503b1..c708d4d603809 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/u256.move +++ b/crates/sui-framework/packages/move-stdlib/sources/u256.move @@ -2,91 +2,91 @@ // SPDX-License-Identifier: Apache-2.0 #[defines_primitive(u256)] -module std::u256 { - use std::string::String; - - /// Returns the bitwise not of the value. - /// Each bit that is 1 becomes 0. Each bit that is 0 becomes 1. - public fun bitwise_not(x: u256): u256 { - x ^ max_value!() - } - - /// Return the larger of `x` and `y` - public fun max(x: u256, y: u256): u256 { - std::macros::num_max!(x, y) - } - - /// Return the smaller of `x` and `y` - public fun min(x: u256, y: u256): u256 { - std::macros::num_min!(x, y) - } - - /// Return the absolute value of x - y - public fun diff(x: u256, y: u256): u256 { - std::macros::num_diff!(x, y) - } - - /// Calculate x / y, but round up the result. - public fun divide_and_round_up(x: u256, y: u256): u256 { - std::macros::num_divide_and_round_up!(x, y) - } - - /// Return the value of a base raised to a power - public fun pow(base: u256, exponent: u8): u256 { - std::macros::num_pow!(base, exponent) - } - - /// Try to convert a `u256` to a `u8`. Returns `None` if the value is too large. - public fun try_as_u8(x: u256): Option { - std::macros::try_as_u8!(x) - } - - /// Try to convert a `u256` to a `u16`. Returns `None` if the value is too large. - public fun try_as_u16(x: u256): Option { - std::macros::try_as_u16!(x) - } - - /// Try to convert a `u256` to a `u32`. Returns `None` if the value is too large. - public fun try_as_u32(x: u256): Option { - std::macros::try_as_u32!(x) - } - - /// Try to convert a `u256` to a `u64`. Returns `None` if the value is too large. - public fun try_as_u64(x: u256): Option { - std::macros::try_as_u64!(x) - } - - /// Try to convert a `u256` to a `u128`. Returns `None` if the value is too large. - public fun try_as_u128(x: u256): Option { - std::macros::try_as_u128!(x) - } - - public fun to_string(x: u256): String { - std::macros::num_to_string!(x) - } - - /// Maximum value for a `u256` - public macro fun max_value(): u256 { - 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF - } - - /// Loops applying `$f` to each number from `$start` to `$stop` (exclusive) - public macro fun range_do($start: u256, $stop: u256, $f: |u256|) { - std::macros::range_do!($start, $stop, $f) - } - - /// Loops applying `$f` to each number from `$start` to `$stop` (inclusive) - public macro fun range_do_eq($start: u256, $stop: u256, $f: |u256|) { - std::macros::range_do_eq!($start, $stop, $f) - } - - /// Loops applying `$f` to each number from `0` to `$stop` (exclusive) - public macro fun do($stop: u256, $f: |u256|) { - std::macros::do!($stop, $f) - } - - /// Loops applying `$f` to each number from `0` to `$stop` (inclusive) - public macro fun do_eq($stop: u256, $f: |u256|) { - std::macros::do_eq!($stop, $f) - } +module std::u256; + +use std::string::String; + +/// Returns the bitwise not of the value. +/// Each bit that is 1 becomes 0. Each bit that is 0 becomes 1. +public fun bitwise_not(x: u256): u256 { + x ^ max_value!() +} + +/// Return the larger of `x` and `y` +public fun max(x: u256, y: u256): u256 { + std::macros::num_max!(x, y) +} + +/// Return the smaller of `x` and `y` +public fun min(x: u256, y: u256): u256 { + std::macros::num_min!(x, y) +} + +/// Return the absolute value of x - y +public fun diff(x: u256, y: u256): u256 { + std::macros::num_diff!(x, y) +} + +/// Calculate x / y, but round up the result. +public fun divide_and_round_up(x: u256, y: u256): u256 { + std::macros::num_divide_and_round_up!(x, y) +} + +/// Return the value of a base raised to a power +public fun pow(base: u256, exponent: u8): u256 { + std::macros::num_pow!(base, exponent) +} + +/// Try to convert a `u256` to a `u8`. Returns `None` if the value is too large. +public fun try_as_u8(x: u256): Option { + std::macros::try_as_u8!(x) +} + +/// Try to convert a `u256` to a `u16`. Returns `None` if the value is too large. +public fun try_as_u16(x: u256): Option { + std::macros::try_as_u16!(x) +} + +/// Try to convert a `u256` to a `u32`. Returns `None` if the value is too large. +public fun try_as_u32(x: u256): Option { + std::macros::try_as_u32!(x) +} + +/// Try to convert a `u256` to a `u64`. Returns `None` if the value is too large. +public fun try_as_u64(x: u256): Option { + std::macros::try_as_u64!(x) +} + +/// Try to convert a `u256` to a `u128`. Returns `None` if the value is too large. +public fun try_as_u128(x: u256): Option { + std::macros::try_as_u128!(x) +} + +public fun to_string(x: u256): String { + std::macros::num_to_string!(x) +} + +/// Maximum value for a `u256` +public macro fun max_value(): u256 { + 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF +} + +/// Loops applying `$f` to each number from `$start` to `$stop` (exclusive) +public macro fun range_do($start: u256, $stop: u256, $f: |u256|) { + std::macros::range_do!($start, $stop, $f) +} + +/// Loops applying `$f` to each number from `$start` to `$stop` (inclusive) +public macro fun range_do_eq($start: u256, $stop: u256, $f: |u256|) { + std::macros::range_do_eq!($start, $stop, $f) +} + +/// Loops applying `$f` to each number from `0` to `$stop` (exclusive) +public macro fun do($stop: u256, $f: |u256|) { + std::macros::do!($stop, $f) +} + +/// Loops applying `$f` to each number from `0` to `$stop` (inclusive) +public macro fun do_eq($stop: u256, $f: |u256|) { + std::macros::do_eq!($stop, $f) } diff --git a/crates/sui-framework/packages/move-stdlib/sources/u32.move b/crates/sui-framework/packages/move-stdlib/sources/u32.move index e969c20c1038a..eab1cadf0804c 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/u32.move +++ b/crates/sui-framework/packages/move-stdlib/sources/u32.move @@ -2,105 +2,105 @@ // SPDX-License-Identifier: Apache-2.0 #[defines_primitive(u32)] -module std::u32 { - use std::string::String; - - /// Returns the bitwise not of the value. - /// Each bit that is 1 becomes 0. Each bit that is 0 becomes 1. - public fun bitwise_not(x: u32): u32 { - x ^ max_value!() - } - - /// Return the larger of `x` and `y` - public fun max(x: u32, y: u32): u32 { - std::macros::num_max!(x, y) - } - - /// Return the smaller of `x` and `y` - public fun min(x: u32, y: u32): u32 { - std::macros::num_min!(x, y) - } - - /// Return the absolute value of x - y - public fun diff(x: u32, y: u32): u32 { - std::macros::num_diff!(x, y) - } - - /// Calculate x / y, but round up the result. - public fun divide_and_round_up(x: u32, y: u32): u32 { - std::macros::num_divide_and_round_up!(x, y) - } - - /// Return the value of a base raised to a power - public fun pow(base: u32, exponent: u8): u32 { - std::macros::num_pow!(base, exponent) - } - - /// Get a nearest lower integer Square Root for `x`. Given that this - /// function can only operate with integers, it is impossible - /// to get perfect (or precise) integer square root for some numbers. - /// - /// Example: - /// ``` - /// math::sqrt(9) => 3 - /// math::sqrt(8) => 2 // the nearest lower square root is 4; - /// ``` - /// - /// In integer math, one of the possible ways to get results with more - /// precision is to use higher values or temporarily multiply the - /// value by some bigger number. Ideally if this is a square of 10 or 100. - /// - /// Example: - /// ``` - /// math::sqrt(8) => 2; - /// math::sqrt(8 * 10000) => 282; - /// // now we can use this value as if it was 2.82; - /// // but to get the actual result, this value needs - /// // to be divided by 100 (because sqrt(10000)). - /// - /// - /// math::sqrt(8 * 1000000) => 2828; // same as above, 2828 / 1000 (2.828) - /// ``` - public fun sqrt(x: u32): u32 { - std::macros::num_sqrt!(x, 32) - } - - /// Try to convert a `u32` to a `u8`. Returns `None` if the value is too large. - public fun try_as_u8(x: u32): Option { - std::macros::try_as_u8!(x) - } - - /// Try to convert a `u32` to a `u16`. Returns `None` if the value is too large. - public fun try_as_u16(x: u32): Option { - std::macros::try_as_u16!(x) - } - - public fun to_string(x: u32): String { - std::macros::num_to_string!(x) - } - - /// Maximum value for a `u32` - public macro fun max_value(): u32 { - 0xFFFF_FFFF - } - - /// Loops applying `$f` to each number from `$start` to `$stop` (exclusive) - public macro fun range_do($start: u32, $stop: u32, $f: |u32|) { - std::macros::range_do!($start, $stop, $f) - } - - /// Loops applying `$f` to each number from `$start` to `$stop` (inclusive) - public macro fun range_do_eq($start: u32, $stop: u32, $f: |u32|) { - std::macros::range_do_eq!($start, $stop, $f) - } - - /// Loops applying `$f` to each number from `0` to `$stop` (exclusive) - public macro fun do($stop: u32, $f: |u32|) { - std::macros::do!($stop, $f) - } - - /// Loops applying `$f` to each number from `0` to `$stop` (inclusive) - public macro fun do_eq($stop: u32, $f: |u32|) { - std::macros::do_eq!($stop, $f) - } +module std::u32; + +use std::string::String; + +/// Returns the bitwise not of the value. +/// Each bit that is 1 becomes 0. Each bit that is 0 becomes 1. +public fun bitwise_not(x: u32): u32 { + x ^ max_value!() +} + +/// Return the larger of `x` and `y` +public fun max(x: u32, y: u32): u32 { + std::macros::num_max!(x, y) +} + +/// Return the smaller of `x` and `y` +public fun min(x: u32, y: u32): u32 { + std::macros::num_min!(x, y) +} + +/// Return the absolute value of x - y +public fun diff(x: u32, y: u32): u32 { + std::macros::num_diff!(x, y) +} + +/// Calculate x / y, but round up the result. +public fun divide_and_round_up(x: u32, y: u32): u32 { + std::macros::num_divide_and_round_up!(x, y) +} + +/// Return the value of a base raised to a power +public fun pow(base: u32, exponent: u8): u32 { + std::macros::num_pow!(base, exponent) +} + +/// Get a nearest lower integer Square Root for `x`. Given that this +/// function can only operate with integers, it is impossible +/// to get perfect (or precise) integer square root for some numbers. +/// +/// Example: +/// ``` +/// math::sqrt(9) => 3 +/// math::sqrt(8) => 2 // the nearest lower square root is 4; +/// ``` +/// +/// In integer math, one of the possible ways to get results with more +/// precision is to use higher values or temporarily multiply the +/// value by some bigger number. Ideally if this is a square of 10 or 100. +/// +/// Example: +/// ``` +/// math::sqrt(8) => 2; +/// math::sqrt(8 * 10000) => 282; +/// // now we can use this value as if it was 2.82; +/// // but to get the actual result, this value needs +/// // to be divided by 100 (because sqrt(10000)). +/// +/// +/// math::sqrt(8 * 1000000) => 2828; // same as above, 2828 / 1000 (2.828) +/// ``` +public fun sqrt(x: u32): u32 { + std::macros::num_sqrt!(x, 32) +} + +/// Try to convert a `u32` to a `u8`. Returns `None` if the value is too large. +public fun try_as_u8(x: u32): Option { + std::macros::try_as_u8!(x) +} + +/// Try to convert a `u32` to a `u16`. Returns `None` if the value is too large. +public fun try_as_u16(x: u32): Option { + std::macros::try_as_u16!(x) +} + +public fun to_string(x: u32): String { + std::macros::num_to_string!(x) +} + +/// Maximum value for a `u32` +public macro fun max_value(): u32 { + 0xFFFF_FFFF +} + +/// Loops applying `$f` to each number from `$start` to `$stop` (exclusive) +public macro fun range_do($start: u32, $stop: u32, $f: |u32|) { + std::macros::range_do!($start, $stop, $f) +} + +/// Loops applying `$f` to each number from `$start` to `$stop` (inclusive) +public macro fun range_do_eq($start: u32, $stop: u32, $f: |u32|) { + std::macros::range_do_eq!($start, $stop, $f) +} + +/// Loops applying `$f` to each number from `0` to `$stop` (exclusive) +public macro fun do($stop: u32, $f: |u32|) { + std::macros::do!($stop, $f) +} + +/// Loops applying `$f` to each number from `0` to `$stop` (inclusive) +public macro fun do_eq($stop: u32, $f: |u32|) { + std::macros::do_eq!($stop, $f) } diff --git a/crates/sui-framework/packages/move-stdlib/sources/u64.move b/crates/sui-framework/packages/move-stdlib/sources/u64.move index c532958dc3803..e3bc76cc45f92 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/u64.move +++ b/crates/sui-framework/packages/move-stdlib/sources/u64.move @@ -2,110 +2,110 @@ // SPDX-License-Identifier: Apache-2.0 #[defines_primitive(u64)] -module std::u64 { - use std::string::String; - - /// Returns the bitwise not of the value. - /// Each bit that is 1 becomes 0. Each bit that is 0 becomes 1. - public fun bitwise_not(x: u64): u64 { - x ^ max_value!() - } - - /// Return the larger of `x` and `y` - public fun max(x: u64, y: u64): u64 { - std::macros::num_max!(x, y) - } - - /// Return the smaller of `x` and `y` - public fun min(x: u64, y: u64): u64 { - std::macros::num_min!(x, y) - } - - /// Return the absolute value of x - y - public fun diff(x: u64, y: u64): u64 { - std::macros::num_diff!(x, y) - } - - /// Calculate x / y, but round up the result. - public fun divide_and_round_up(x: u64, y: u64): u64 { - std::macros::num_divide_and_round_up!(x, y) - } - - /// Return the value of a base raised to a power - public fun pow(base: u64, exponent: u8): u64 { - std::macros::num_pow!(base, exponent) - } - - /// Get a nearest lower integer Square Root for `x`. Given that this - /// function can only operate with integers, it is impossible - /// to get perfect (or precise) integer square root for some numbers. - /// - /// Example: - /// ``` - /// math::sqrt(9) => 3 - /// math::sqrt(8) => 2 // the nearest lower square root is 4; - /// ``` - /// - /// In integer math, one of the possible ways to get results with more - /// precision is to use higher values or temporarily multiply the - /// value by some bigger number. Ideally if this is a square of 10 or 100. - /// - /// Example: - /// ``` - /// math::sqrt(8) => 2; - /// math::sqrt(8 * 10000) => 282; - /// // now we can use this value as if it was 2.82; - /// // but to get the actual result, this value needs - /// // to be divided by 100 (because sqrt(10000)). - /// - /// - /// math::sqrt(8 * 1000000) => 2828; // same as above, 2828 / 1000 (2.828) - /// ``` - public fun sqrt(x: u64): u64 { - std::macros::num_sqrt!(x, 64) - } - - /// Try to convert a `u64` to a `u8`. Returns `None` if the value is too large. - public fun try_as_u8(x: u64): Option { - std::macros::try_as_u8!(x) - } - - /// Try to convert a `u64` to a `u16`. Returns `None` if the value is too large. - public fun try_as_u16(x: u64): Option { - std::macros::try_as_u16!(x) - } - - /// Try to convert a `u64` to a `u32`. Returns `None` if the value is too large. - public fun try_as_u32(x: u64): Option { - std::macros::try_as_u32!(x) - } - - public fun to_string(x: u64): String { - std::macros::num_to_string!(x) - } - - /// Maximum value for a `u64` - public macro fun max_value(): u64 { - 0xFFFF_FFFF_FFFF_FFFF - } - - /// Loops applying `$f` to each number from `$start` to `$stop` (exclusive) - public macro fun range_do($start: u64, $stop: u64, $f: |u64|) { - std::macros::range_do!($start, $stop, $f) - } - - /// Loops applying `$f` to each number from `$start` to `$stop` (inclusive) - public macro fun range_do_eq($start: u64, $stop: u64, $f: |u64|) { - std::macros::range_do_eq!($start, $stop, $f) - } - - /// Loops applying `$f` to each number from `0` to `$stop` (exclusive) - public macro fun do($stop: u64, $f: |u64|) { - std::macros::do!($stop, $f) - } - - /// Loops applying `$f` to each number from `0` to `$stop` (inclusive) - public macro fun do_eq($stop: u64, $f: |u64|) { - std::macros::do_eq!($stop, $f) - } +module std::u64; + +use std::string::String; + +/// Returns the bitwise not of the value. +/// Each bit that is 1 becomes 0. Each bit that is 0 becomes 1. +public fun bitwise_not(x: u64): u64 { + x ^ max_value!() +} + +/// Return the larger of `x` and `y` +public fun max(x: u64, y: u64): u64 { + std::macros::num_max!(x, y) +} + +/// Return the smaller of `x` and `y` +public fun min(x: u64, y: u64): u64 { + std::macros::num_min!(x, y) +} + +/// Return the absolute value of x - y +public fun diff(x: u64, y: u64): u64 { + std::macros::num_diff!(x, y) +} + +/// Calculate x / y, but round up the result. +public fun divide_and_round_up(x: u64, y: u64): u64 { + std::macros::num_divide_and_round_up!(x, y) +} + +/// Return the value of a base raised to a power +public fun pow(base: u64, exponent: u8): u64 { + std::macros::num_pow!(base, exponent) +} + +/// Get a nearest lower integer Square Root for `x`. Given that this +/// function can only operate with integers, it is impossible +/// to get perfect (or precise) integer square root for some numbers. +/// +/// Example: +/// ``` +/// math::sqrt(9) => 3 +/// math::sqrt(8) => 2 // the nearest lower square root is 4; +/// ``` +/// +/// In integer math, one of the possible ways to get results with more +/// precision is to use higher values or temporarily multiply the +/// value by some bigger number. Ideally if this is a square of 10 or 100. +/// +/// Example: +/// ``` +/// math::sqrt(8) => 2; +/// math::sqrt(8 * 10000) => 282; +/// // now we can use this value as if it was 2.82; +/// // but to get the actual result, this value needs +/// // to be divided by 100 (because sqrt(10000)). +/// +/// +/// math::sqrt(8 * 1000000) => 2828; // same as above, 2828 / 1000 (2.828) +/// ``` +public fun sqrt(x: u64): u64 { + std::macros::num_sqrt!(x, 64) +} + +/// Try to convert a `u64` to a `u8`. Returns `None` if the value is too large. +public fun try_as_u8(x: u64): Option { + std::macros::try_as_u8!(x) +} + +/// Try to convert a `u64` to a `u16`. Returns `None` if the value is too large. +public fun try_as_u16(x: u64): Option { + std::macros::try_as_u16!(x) +} + +/// Try to convert a `u64` to a `u32`. Returns `None` if the value is too large. +public fun try_as_u32(x: u64): Option { + std::macros::try_as_u32!(x) +} + +public fun to_string(x: u64): String { + std::macros::num_to_string!(x) +} + +/// Maximum value for a `u64` +public macro fun max_value(): u64 { + 0xFFFF_FFFF_FFFF_FFFF +} + +/// Loops applying `$f` to each number from `$start` to `$stop` (exclusive) +public macro fun range_do($start: u64, $stop: u64, $f: |u64|) { + std::macros::range_do!($start, $stop, $f) +} + +/// Loops applying `$f` to each number from `$start` to `$stop` (inclusive) +public macro fun range_do_eq($start: u64, $stop: u64, $f: |u64|) { + std::macros::range_do_eq!($start, $stop, $f) +} + +/// Loops applying `$f` to each number from `0` to `$stop` (exclusive) +public macro fun do($stop: u64, $f: |u64|) { + std::macros::do!($stop, $f) +} + +/// Loops applying `$f` to each number from `0` to `$stop` (inclusive) +public macro fun do_eq($stop: u64, $f: |u64|) { + std::macros::do_eq!($stop, $f) } diff --git a/crates/sui-framework/packages/move-stdlib/sources/u8.move b/crates/sui-framework/packages/move-stdlib/sources/u8.move index 8bb4e58139017..f0a99d690f58a 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/u8.move +++ b/crates/sui-framework/packages/move-stdlib/sources/u8.move @@ -2,95 +2,95 @@ // SPDX-License-Identifier: Apache-2.0 #[defines_primitive(u8)] -module std::u8 { - use std::string::String; +module std::u8; - /// Returns the bitwise not of the value. - /// Each bit that is 1 becomes 0. Each bit that is 0 becomes 1. - public fun bitwise_not(x: u8): u8 { - x ^ max_value!() - } +use std::string::String; - /// Return the larger of `x` and `y` - public fun max(x: u8, y: u8): u8 { - std::macros::num_max!(x, y) - } +/// Returns the bitwise not of the value. +/// Each bit that is 1 becomes 0. Each bit that is 0 becomes 1. +public fun bitwise_not(x: u8): u8 { + x ^ max_value!() +} - /// Return the smaller of `x` and `y` - public fun min(x: u8, y: u8): u8 { - std::macros::num_min!(x, y) - } +/// Return the larger of `x` and `y` +public fun max(x: u8, y: u8): u8 { + std::macros::num_max!(x, y) +} - /// Return the absolute value of x - y - public fun diff(x: u8, y: u8): u8 { - std::macros::num_diff!(x, y) - } +/// Return the smaller of `x` and `y` +public fun min(x: u8, y: u8): u8 { + std::macros::num_min!(x, y) +} - /// Calculate x / y, but round up the result. - public fun divide_and_round_up(x: u8, y: u8): u8 { - std::macros::num_divide_and_round_up!(x, y) - } +/// Return the absolute value of x - y +public fun diff(x: u8, y: u8): u8 { + std::macros::num_diff!(x, y) +} - /// Return the value of a base raised to a power - public fun pow(base: u8, exponent: u8): u8 { - std::macros::num_pow!(base, exponent) - } +/// Calculate x / y, but round up the result. +public fun divide_and_round_up(x: u8, y: u8): u8 { + std::macros::num_divide_and_round_up!(x, y) +} - /// Get a nearest lower integer Square Root for `x`. Given that this - /// function can only operate with integers, it is impossible - /// to get perfect (or precise) integer square root for some numbers. - /// - /// Example: - /// ``` - /// math::sqrt(9) => 3 - /// math::sqrt(8) => 2 // the nearest lower square root is 4; - /// ``` - /// - /// In integer math, one of the possible ways to get results with more - /// precision is to use higher values or temporarily multiply the - /// value by some bigger number. Ideally if this is a square of 10 or 100. - /// - /// Example: - /// ``` - /// math::sqrt(8) => 2; - /// math::sqrt(8 * 10000) => 282; - /// // now we can use this value as if it was 2.82; - /// // but to get the actual result, this value needs - /// // to be divided by 100 (because sqrt(10000)). - /// - /// - /// math::sqrt(8 * 1000000) => 2828; // same as above, 2828 / 1000 (2.828) - /// ``` - public fun sqrt(x: u8): u8 { - std::macros::num_sqrt!(x, 8) - } +/// Return the value of a base raised to a power +public fun pow(base: u8, exponent: u8): u8 { + std::macros::num_pow!(base, exponent) +} - public fun to_string(x: u8): String { - std::macros::num_to_string!(x) - } +/// Get a nearest lower integer Square Root for `x`. Given that this +/// function can only operate with integers, it is impossible +/// to get perfect (or precise) integer square root for some numbers. +/// +/// Example: +/// ``` +/// math::sqrt(9) => 3 +/// math::sqrt(8) => 2 // the nearest lower square root is 4; +/// ``` +/// +/// In integer math, one of the possible ways to get results with more +/// precision is to use higher values or temporarily multiply the +/// value by some bigger number. Ideally if this is a square of 10 or 100. +/// +/// Example: +/// ``` +/// math::sqrt(8) => 2; +/// math::sqrt(8 * 10000) => 282; +/// // now we can use this value as if it was 2.82; +/// // but to get the actual result, this value needs +/// // to be divided by 100 (because sqrt(10000)). +/// +/// +/// math::sqrt(8 * 1000000) => 2828; // same as above, 2828 / 1000 (2.828) +/// ``` +public fun sqrt(x: u8): u8 { + std::macros::num_sqrt!(x, 8) +} - /// Maximum value for a `u8` - public macro fun max_value(): u8 { - 0xFF - } +public fun to_string(x: u8): String { + std::macros::num_to_string!(x) +} + +/// Maximum value for a `u8` +public macro fun max_value(): u8 { + 0xFF +} - /// Loops applying `$f` to each number from `$start` to `$stop` (exclusive) - public macro fun range_do($start: u8, $stop: u8, $f: |u8|) { - std::macros::range_do!($start, $stop, $f) - } +/// Loops applying `$f` to each number from `$start` to `$stop` (exclusive) +public macro fun range_do($start: u8, $stop: u8, $f: |u8|) { + std::macros::range_do!($start, $stop, $f) +} - /// Loops applying `$f` to each number from `$start` to `$stop` (inclusive) - public macro fun range_do_eq($start: u8, $stop: u8, $f: |u8|) { - std::macros::range_do_eq!($start, $stop, $f) - } +/// Loops applying `$f` to each number from `$start` to `$stop` (inclusive) +public macro fun range_do_eq($start: u8, $stop: u8, $f: |u8|) { + std::macros::range_do_eq!($start, $stop, $f) +} - /// Loops applying `$f` to each number from `0` to `$stop` (exclusive) - public macro fun do($stop: u8, $f: |u8|) { - std::macros::do!($stop, $f) - } +/// Loops applying `$f` to each number from `0` to `$stop` (exclusive) +public macro fun do($stop: u8, $f: |u8|) { + std::macros::do!($stop, $f) +} - /// Loops applying `$f` to each number from `0` to `$stop` (inclusive) - public macro fun do_eq($stop: u8, $f: |u8|) { - std::macros::do_eq!($stop, $f) - } +/// Loops applying `$f` to each number from `0` to `$stop` (inclusive) +public macro fun do_eq($stop: u8, $f: |u8|) { + std::macros::do_eq!($stop, $f) } diff --git a/crates/sui-framework/packages/move-stdlib/sources/unit_test.move b/crates/sui-framework/packages/move-stdlib/sources/unit_test.move index 1deb6a70c0529..6a62e85573d80 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/unit_test.move +++ b/crates/sui-framework/packages/move-stdlib/sources/unit_test.move @@ -3,32 +3,31 @@ #[test_only] /// Module providing testing functionality. Only included for tests. -module std::unit_test { +module std::unit_test; - /// DEPRECATED - native public fun create_signers_for_testing(num_signers: u64): vector; +/// DEPRECATED +public native fun create_signers_for_testing(num_signers: u64): vector; - /// This function is used to poison modules compiled in `test` mode. - /// This will cause a linking failure if an attempt is made to publish a - /// test module in a VM that isn't in unit test mode. - native public fun poison(); +/// This function is used to poison modules compiled in `test` mode. +/// This will cause a linking failure if an attempt is made to publish a +/// test module in a VM that isn't in unit test mode. +public native fun poison(); - public macro fun assert_eq<$T: drop>($t1: $T, $t2: $T) { - let t1 = $t1; - let t2 = $t2; - assert_ref_eq!(&t1, &t2) - } +public macro fun assert_eq<$T: drop>($t1: $T, $t2: $T) { + let t1 = $t1; + let t2 = $t2; + assert_ref_eq!(&t1, &t2) +} - public macro fun assert_ref_eq<$T>($t1: &$T, $t2: &$T) { - let t1 = $t1; - let t2 = $t2; - let res = t1 == t2; - if (!res) { - std::debug::print(&b"Assertion failed:"); - std::debug::print(t1); - std::debug::print(&b"!="); - std::debug::print(t2); - assert!(false); - } +public macro fun assert_ref_eq<$T>($t1: &$T, $t2: &$T) { + let t1 = $t1; + let t2 = $t2; + let res = t1 == t2; + if (!res) { + std::debug::print(&b"Assertion failed:".to_string()); + std::debug::print(t1); + std::debug::print(&b"!=".to_string()); + std::debug::print(t2); + assert!(false); } } diff --git a/crates/sui-framework/packages/move-stdlib/sources/uq32_32.move b/crates/sui-framework/packages/move-stdlib/sources/uq32_32.move new file mode 100644 index 0000000000000..cad6381324090 --- /dev/null +++ b/crates/sui-framework/packages/move-stdlib/sources/uq32_32.move @@ -0,0 +1,160 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// Defines an unsigned, fixed-point numeric type with a 32-bit integer part and a 32-bit fractional +/// part. The notation `uq32_32` and `UQ32_32` is based on +/// [Q notation](https://en.wikipedia.org/wiki/Q_(number_format)). `q` indicates it a fixed-point +/// number. The `u` prefix indicates it is unsigned. The `32_32` suffix indicates the number of +/// bits, where the first number indicates the number of bits in the integer part, and the second +/// the number of bits in the fractional part--in this case 32 bits for each. +module std::uq32_32; + +#[error] +const EDenominator: vector = b"Quotient specified with a zero denominator"; + +#[error] +const EQuotientTooSmall: vector = + b"Quotient specified is too small, and is outside of the supported range"; + +#[error] +const EQuotientTooLarge: vector = + b"Quotient specified is too large, and is outside of the supported range"; + +#[error] +const EOverflow: vector = b"Overflow from an arithmetic operation"; + +#[error] +const EDivisionByZero: vector = b"Division by zero"; + +/// A fixed-point numeric type with 32 integer bits and 32 fractional bits, represented by an +/// underlying 64 bit value. This is a binary representation, so decimal values may not be exactly +/// representable, but it provides more than 9 decimal digits of precision both before and after the +/// decimal point (18 digits total). +public struct UQ32_32(u64) has copy, drop, store; + +/// Create a fixed-point value from a quotient specified by its numerator and denominator. +/// `from_quotient` and `from_int` should be preferred over using `from_raw`. +/// Unless the denominator is a power of two, fractions can not be represented accurately, +/// so be careful about rounding errors. +/// Aborts if the denominator is zero. +/// Aborts if the input is non-zero but so small that it will be represented as zero, e.g. smaller +/// than 2^{-32}. +/// Aborts if the input is too large, e.g. larger than or equal to 2^32. +public fun from_quotient(numerator: u64, denominator: u64): UQ32_32 { + assert!(denominator != 0, EDenominator); + + // Scale the numerator to have 64 fractional bits and the denominator to have 32 fractional + // bits, so that the quotient will have 32 fractional bits. + let scaled_numerator = numerator as u128 << 64; + let scaled_denominator = denominator as u128 << 32; + let quotient = scaled_numerator / scaled_denominator; + + // The quotient can only be zero if the numerator is also zero. + assert!(quotient != 0 || numerator == 0, EQuotientTooSmall); + + // Return the quotient as a fixed-point number. We first need to check whether the cast + // can succeed. + assert!(quotient <= std::u64::max_value!() as u128, EQuotientTooLarge); + UQ32_32(quotient as u64) +} + +/// Create a fixed-point value from an integer. +/// `from_int` and `from_quotient` should be preferred over using `from_raw`. +public fun from_int(integer: u32): UQ32_32 { + UQ32_32((integer as u64) << 32) +} + +/// Add two fixed-point numbers, `a + b`. +/// Aborts if the sum overflows. +public fun add(a: UQ32_32, b: UQ32_32): UQ32_32 { + let sum = a.0 as u128 + (b.0 as u128); + assert!(sum <= std::u64::max_value!() as u128, EOverflow); + UQ32_32(sum as u64) +} + +/// Subtract two fixed-point numbers, `a - b`. +/// Aborts if `a < b`. +public fun sub(a: UQ32_32, b: UQ32_32): UQ32_32 { + assert!(a.0 >= b.0, EOverflow); + UQ32_32(a.0 - b.0) +} + +/// Multiply two fixed-point numbers, truncating any fractional part of the product. +/// Aborts if the product overflows. +public fun mul(a: UQ32_32, b: UQ32_32): UQ32_32 { + UQ32_32(int_mul(a.0, b)) +} + +/// Divide two fixed-point numbers, truncating any fractional part of the quotient. +/// Aborts if the divisor is zero. +/// Aborts if the quotient overflows. +public fun div(a: UQ32_32, b: UQ32_32): UQ32_32 { + UQ32_32(int_div(a.0, b)) +} + +/// Convert a fixed-point number to an integer, truncating any fractional part. +public fun to_int(a: UQ32_32): u32 { + (a.0 >> 32) as u32 +} + +/// Multiply a `u64` integer by a fixed-point number, truncating any fractional part of the product. +/// Aborts if the product overflows. +public fun int_mul(val: u64, multiplier: UQ32_32): u64 { + // The product of two 64 bit values has 128 bits, so perform the + // multiplication with u128 types and keep the full 128 bit product + // to avoid losing accuracy. + let unscaled_product = val as u128 * (multiplier.0 as u128); + // The unscaled product has 32 fractional bits (from the multiplier) + // so rescale it by shifting away the low bits. + let product = unscaled_product >> 32; + // Check whether the value is too large. + assert!(product <= std::u64::max_value!() as u128, EOverflow); + product as u64 +} + +/// Divide a `u64` integer by a fixed-point number, truncating any fractional part of the quotient. +/// Aborts if the divisor is zero. +/// Aborts if the quotient overflows. +public fun int_div(val: u64, divisor: UQ32_32): u64 { + // Check for division by zero. + assert!(divisor.0 != 0, EDivisionByZero); + // First convert to 128 bits and then shift left to + // add 32 fractional zero bits to the dividend. + let scaled_value = val as u128 << 32; + let quotient = scaled_value / (divisor.0 as u128); + // Check whether the value is too large. + assert!(quotient <= std::u64::max_value!() as u128, EOverflow); + quotient as u64 +} + +/// Less than or equal to. Returns `true` if and only if `a <= a`. +public fun le(a: UQ32_32, b: UQ32_32): bool { + a.0 <= b.0 +} + +/// Less than. Returns `true` if and only if `a < b`. +public fun lt(a: UQ32_32, b: UQ32_32): bool { + a.0 < b.0 +} + +/// Greater than or equal to. Returns `true` if and only if `a >= b`. +public fun ge(a: UQ32_32, b: UQ32_32): bool { + a.0 >= b.0 +} + +/// Greater than. Returns `true` if and only if `a > b`. +public fun gt(a: UQ32_32, b: UQ32_32): bool { + a.0 > b.0 +} + +/// Accessor for the raw u64 value. Can be paired with `from_raw` to perform less common operations +/// on the raw values directly. +public fun to_raw(a: UQ32_32): u64 { + a.0 +} + +/// Accessor for the raw u64 value. Can be paired with `to_raw` to perform less common operations +/// on the raw values directly. +public fun from_raw(raw_value: u64): UQ32_32 { + UQ32_32(raw_value) +} diff --git a/crates/sui-framework/packages/move-stdlib/sources/vector.move b/crates/sui-framework/packages/move-stdlib/sources/vector.move index 6d7d0e37fe458..96a2567855501 100644 --- a/crates/sui-framework/packages/move-stdlib/sources/vector.move +++ b/crates/sui-framework/packages/move-stdlib/sources/vector.move @@ -4,368 +4,371 @@ #[defines_primitive(vector)] /// A variable-sized container that can hold any type. Indexing is 0-based, and /// vectors are growable. This module has many native functions. -module std::vector { - /// Allows calling `.to_string()` on a vector of `u8` to get a utf8 `String`. - public use fun std::string::utf8 as vector.to_string; - - /// Allows calling `.try_to_string()` on a vector of `u8` to get a utf8 `String`. - /// This will return `None` if the vector is not valid utf8. - public use fun std::string::try_utf8 as vector.try_to_string; - - /// Allows calling `.to_ascii_string()` on a vector of `u8` to get an `ascii::String`. - public use fun std::ascii::string as vector.to_ascii_string; - - /// Allows calling `.try_to_ascii_string()` on a vector of `u8` to get an - /// `ascii::String`. This will return `None` if the vector is not valid ascii. - public use fun std::ascii::try_string as vector.try_to_ascii_string; - - /// The index into the vector is out of bounds - const EINDEX_OUT_OF_BOUNDS: u64 = 0x20000; - - #[bytecode_instruction] - /// Create an empty vector. - public native fun empty(): vector; - - #[bytecode_instruction] - /// Return the length of the vector. - public native fun length(v: &vector): u64; - - #[syntax(index)] - #[bytecode_instruction] - /// Acquire an immutable reference to the `i`th element of the vector `v`. - /// Aborts if `i` is out of bounds. - public native fun borrow(v: &vector, i: u64): ∈ - - #[bytecode_instruction] - /// Add element `e` to the end of the vector `v`. - public native fun push_back(v: &mut vector, e: Element); - - #[syntax(index)] - #[bytecode_instruction] - /// Return a mutable reference to the `i`th element in the vector `v`. - /// Aborts if `i` is out of bounds. - public native fun borrow_mut(v: &mut vector, i: u64): &mut Element; - - #[bytecode_instruction] - /// Pop an element from the end of vector `v`. - /// Aborts if `v` is empty. - public native fun pop_back(v: &mut vector): Element; - - #[bytecode_instruction] - /// Destroy the vector `v`. - /// Aborts if `v` is not empty. - public native fun destroy_empty(v: vector); - - #[bytecode_instruction] - /// Swaps the elements at the `i`th and `j`th indices in the vector `v`. - /// Aborts if `i` or `j` is out of bounds. - public native fun swap(v: &mut vector, i: u64, j: u64); - - /// Return an vector of size one containing element `e`. - public fun singleton(e: Element): vector { - let mut v = empty(); - v.push_back(e); - v - } +module std::vector; + +/// Allows calling `.to_string()` on a vector of `u8` to get a utf8 `String`. +public use fun std::string::utf8 as vector.to_string; + +/// Allows calling `.try_to_string()` on a vector of `u8` to get a utf8 `String`. +/// This will return `None` if the vector is not valid utf8. +public use fun std::string::try_utf8 as vector.try_to_string; + +/// Allows calling `.to_ascii_string()` on a vector of `u8` to get an `ascii::String`. +public use fun std::ascii::string as vector.to_ascii_string; + +/// Allows calling `.try_to_ascii_string()` on a vector of `u8` to get an +/// `ascii::String`. This will return `None` if the vector is not valid ascii. +public use fun std::ascii::try_string as vector.try_to_ascii_string; + +/// The index into the vector is out of bounds +const EINDEX_OUT_OF_BOUNDS: u64 = 0x20000; + +#[bytecode_instruction] +/// Create an empty vector. +public native fun empty(): vector; + +#[bytecode_instruction] +/// Return the length of the vector. +public native fun length(v: &vector): u64; + +#[syntax(index)] +#[bytecode_instruction] +/// Acquire an immutable reference to the `i`th element of the vector `v`. +/// Aborts if `i` is out of bounds. +public native fun borrow(v: &vector, i: u64): ∈ + +#[bytecode_instruction] +/// Add element `e` to the end of the vector `v`. +public native fun push_back(v: &mut vector, e: Element); + +#[syntax(index)] +#[bytecode_instruction] +/// Return a mutable reference to the `i`th element in the vector `v`. +/// Aborts if `i` is out of bounds. +public native fun borrow_mut(v: &mut vector, i: u64): &mut Element; + +#[bytecode_instruction] +/// Pop an element from the end of vector `v`. +/// Aborts if `v` is empty. +public native fun pop_back(v: &mut vector): Element; + +#[bytecode_instruction] +/// Destroy the vector `v`. +/// Aborts if `v` is not empty. +public native fun destroy_empty(v: vector); + +#[bytecode_instruction] +/// Swaps the elements at the `i`th and `j`th indices in the vector `v`. +/// Aborts if `i` or `j` is out of bounds. +public native fun swap(v: &mut vector, i: u64, j: u64); + +/// Return an vector of size one containing element `e`. +public fun singleton(e: Element): vector { + let mut v = empty(); + v.push_back(e); + v +} - /// Reverses the order of the elements in the vector `v` in place. - public fun reverse(v: &mut vector) { - let len = v.length(); - if (len == 0) return (); - - let mut front_index = 0; - let mut back_index = len - 1; - while (front_index < back_index) { - v.swap(front_index, back_index); - front_index = front_index + 1; - back_index = back_index - 1; - } +/// Reverses the order of the elements in the vector `v` in place. +public fun reverse(v: &mut vector) { + let len = v.length(); + if (len == 0) return (); + + let mut front_index = 0; + let mut back_index = len - 1; + while (front_index < back_index) { + v.swap(front_index, back_index); + front_index = front_index + 1; + back_index = back_index - 1; } +} - /// Pushes all of the elements of the `other` vector into the `lhs` vector. - public fun append(lhs: &mut vector, mut other: vector) { - other.reverse(); - while (!other.is_empty()) lhs.push_back(other.pop_back()); - other.destroy_empty(); - } +/// Pushes all of the elements of the `other` vector into the `lhs` vector. +public fun append(lhs: &mut vector, mut other: vector) { + other.reverse(); + while (!other.is_empty()) lhs.push_back(other.pop_back()); + other.destroy_empty(); +} - /// Return `true` if the vector `v` has no elements and `false` otherwise. - public fun is_empty(v: &vector): bool { - v.length() == 0 - } +/// Return `true` if the vector `v` has no elements and `false` otherwise. +public fun is_empty(v: &vector): bool { + v.length() == 0 +} - /// Return true if `e` is in the vector `v`. - /// Otherwise, returns false. - public fun contains(v: &vector, e: &Element): bool { - let mut i = 0; - let len = v.length(); - while (i < len) { - if (&v[i] == e) return true; - i = i + 1; - }; - false - } +/// Return true if `e` is in the vector `v`. +/// Otherwise, returns false. +public fun contains(v: &vector, e: &Element): bool { + let mut i = 0; + let len = v.length(); + while (i < len) { + if (&v[i] == e) return true; + i = i + 1; + }; + false +} - /// Return `(true, i)` if `e` is in the vector `v` at index `i`. - /// Otherwise, returns `(false, 0)`. - public fun index_of(v: &vector, e: &Element): (bool, u64) { - let mut i = 0; - let len = v.length(); - while (i < len) { - if (&v[i] == e) return (true, i); - i = i + 1; - }; - (false, 0) - } +/// Return `(true, i)` if `e` is in the vector `v` at index `i`. +/// Otherwise, returns `(false, 0)`. +public fun index_of(v: &vector, e: &Element): (bool, u64) { + let mut i = 0; + let len = v.length(); + while (i < len) { + if (&v[i] == e) return (true, i); + i = i + 1; + }; + (false, 0) +} - /// Remove the `i`th element of the vector `v`, shifting all subsequent elements. - /// This is O(n) and preserves ordering of elements in the vector. - /// Aborts if `i` is out of bounds. - public fun remove(v: &mut vector, mut i: u64): Element { - let mut len = v.length(); - // i out of bounds; abort - if (i >= len) abort EINDEX_OUT_OF_BOUNDS; - - len = len - 1; - while (i < len) v.swap(i, { i = i + 1; i }); - v.pop_back() - } +/// Remove the `i`th element of the vector `v`, shifting all subsequent elements. +/// This is O(n) and preserves ordering of elements in the vector. +/// Aborts if `i` is out of bounds. +public fun remove(v: &mut vector, mut i: u64): Element { + let mut len = v.length(); + // i out of bounds; abort + if (i >= len) abort EINDEX_OUT_OF_BOUNDS; + + len = len - 1; + while (i < len) v.swap(i, { + i = i + 1; + i + }); + v.pop_back() +} - /// Insert `e` at position `i` in the vector `v`. - /// If `i` is in bounds, this shifts the old `v[i]` and all subsequent elements to the right. - /// If `i == v.length()`, this adds `e` to the end of the vector. - /// This is O(n) and preserves ordering of elements in the vector. - /// Aborts if `i > v.length()` - public fun insert(v: &mut vector, e: Element, mut i: u64) { - let len = v.length(); - // i too big abort - if (i > len) abort EINDEX_OUT_OF_BOUNDS; - - v.push_back(e); - while (i < len) { - v.swap(i, len); - i = i + 1 - } +/// Insert `e` at position `i` in the vector `v`. +/// If `i` is in bounds, this shifts the old `v[i]` and all subsequent elements to the right. +/// If `i == v.length()`, this adds `e` to the end of the vector. +/// This is O(n) and preserves ordering of elements in the vector. +/// Aborts if `i > v.length()` +public fun insert(v: &mut vector, e: Element, mut i: u64) { + let len = v.length(); + // i too big abort + if (i > len) abort EINDEX_OUT_OF_BOUNDS; + + v.push_back(e); + while (i < len) { + v.swap(i, len); + i = i + 1 } +} - /// Swap the `i`th element of the vector `v` with the last element and then pop the vector. - /// This is O(1), but does not preserve ordering of elements in the vector. - /// Aborts if `i` is out of bounds. - public fun swap_remove(v: &mut vector, i: u64): Element { - assert!(!v.is_empty(), EINDEX_OUT_OF_BOUNDS); - let last_idx = v.length() - 1; - v.swap(i, last_idx); - v.pop_back() - } +/// Swap the `i`th element of the vector `v` with the last element and then pop the vector. +/// This is O(1), but does not preserve ordering of elements in the vector. +/// Aborts if `i` is out of bounds. +public fun swap_remove(v: &mut vector, i: u64): Element { + assert!(!v.is_empty(), EINDEX_OUT_OF_BOUNDS); + let last_idx = v.length() - 1; + v.swap(i, last_idx); + v.pop_back() +} - // === Macros === +// === Macros === - /// Create a vector of length `n` by calling the function `f` on each index. - public macro fun tabulate<$T>($n: u64, $f: |u64| -> $T): vector<$T> { - let mut v = vector[]; - let n = $n; - n.do!(|i| v.push_back($f(i))); - v - } +/// Create a vector of length `n` by calling the function `f` on each index. +public macro fun tabulate<$T>($n: u64, $f: |u64| -> $T): vector<$T> { + let mut v = vector[]; + let n = $n; + n.do!(|i| v.push_back($f(i))); + v +} - /// Destroy the vector `v` by calling `f` on each element and then destroying the vector. - /// Does not preserve the order of elements in the vector (starts from the end of the vector). - public macro fun destroy<$T>($v: vector<$T>, $f: |$T|) { - let mut v = $v; - while (!v.is_empty()) $f(v.pop_back()); - v.destroy_empty(); - } +/// Destroy the vector `v` by calling `f` on each element and then destroying the vector. +/// Does not preserve the order of elements in the vector (starts from the end of the vector). +public macro fun destroy<$T>($v: vector<$T>, $f: |$T|) { + let mut v = $v; + while (!v.is_empty()) $f(v.pop_back()); + v.destroy_empty(); +} - /// Destroy the vector `v` by calling `f` on each element and then destroying the vector. - /// Preserves the order of elements in the vector. - public macro fun do<$T>($v: vector<$T>, $f: |$T|) { - let mut v = $v; - v.reverse(); - while (!v.is_empty()) $f(v.pop_back()); - v.destroy_empty(); - } +/// Destroy the vector `v` by calling `f` on each element and then destroying the vector. +/// Preserves the order of elements in the vector. +public macro fun do<$T>($v: vector<$T>, $f: |$T|) { + let mut v = $v; + v.reverse(); + while (!v.is_empty()) $f(v.pop_back()); + v.destroy_empty(); +} - /// Perform an action `f` on each element of the vector `v`. The vector is not modified. - public macro fun do_ref<$T>($v: &vector<$T>, $f: |&$T|) { - let v = $v; - v.length().do!(|i| $f(&v[i])) - } +/// Perform an action `f` on each element of the vector `v`. The vector is not modified. +public macro fun do_ref<$T>($v: &vector<$T>, $f: |&$T|) { + let v = $v; + v.length().do!(|i| $f(&v[i])) +} - /// Perform an action `f` on each element of the vector `v`. - /// The function `f` takes a mutable reference to the element. - public macro fun do_mut<$T>($v: &mut vector<$T>, $f: |&mut $T|) { - let v = $v; - v.length().do!(|i| $f(&mut v[i])) - } +/// Perform an action `f` on each element of the vector `v`. +/// The function `f` takes a mutable reference to the element. +public macro fun do_mut<$T>($v: &mut vector<$T>, $f: |&mut $T|) { + let v = $v; + v.length().do!(|i| $f(&mut v[i])) +} - /// Map the vector `v` to a new vector by applying the function `f` to each element. - /// Preserves the order of elements in the vector, first is called first. - public macro fun map<$T, $U>($v: vector<$T>, $f: |$T| -> $U): vector<$U> { - let v = $v; - let mut r = vector[]; - v.do!(|e| r.push_back($f(e))); - r - } +/// Map the vector `v` to a new vector by applying the function `f` to each element. +/// Preserves the order of elements in the vector, first is called first. +public macro fun map<$T, $U>($v: vector<$T>, $f: |$T| -> $U): vector<$U> { + let v = $v; + let mut r = vector[]; + v.do!(|e| r.push_back($f(e))); + r +} - /// Map the vector `v` to a new vector by applying the function `f` to each element. - /// Preserves the order of elements in the vector, first is called first. - public macro fun map_ref<$T, $U>($v: &vector<$T>, $f: |&$T| -> $U): vector<$U> { - let v = $v; - let mut r = vector[]; - v.do_ref!(|e| r.push_back($f(e))); - r - } +/// Map the vector `v` to a new vector by applying the function `f` to each element. +/// Preserves the order of elements in the vector, first is called first. +public macro fun map_ref<$T, $U>($v: &vector<$T>, $f: |&$T| -> $U): vector<$U> { + let v = $v; + let mut r = vector[]; + v.do_ref!(|e| r.push_back($f(e))); + r +} - /// Filter the vector `v` by applying the function `f` to each element. - /// Return a new vector containing only the elements for which `f` returns `true`. - public macro fun filter<$T: drop>($v: vector<$T>, $f: |&$T| -> bool): vector<$T> { - let v = $v; - let mut r = vector[]; - v.do!(|e| if ($f(&e)) r.push_back(e)); - r - } +/// Filter the vector `v` by applying the function `f` to each element. +/// Return a new vector containing only the elements for which `f` returns `true`. +public macro fun filter<$T: drop>($v: vector<$T>, $f: |&$T| -> bool): vector<$T> { + let v = $v; + let mut r = vector[]; + v.do!(|e| if ($f(&e)) r.push_back(e)); + r +} - /// Split the vector `v` into two vectors by applying the function `f` to each element. - /// Return a tuple containing two vectors: the first containing the elements for which `f` returns `true`, - /// and the second containing the elements for which `f` returns `false`. - public macro fun partition<$T>($v: vector<$T>, $f: |&$T| -> bool): (vector<$T>, vector<$T>) { - let v = $v; - let mut r1 = vector[]; - let mut r2 = vector[]; - v.do!(|e| if ($f(&e)) r1.push_back(e) else r2.push_back(e)); - (r1, r2) - } +/// Split the vector `v` into two vectors by applying the function `f` to each element. +/// Return a tuple containing two vectors: the first containing the elements for which `f` returns `true`, +/// and the second containing the elements for which `f` returns `false`. +public macro fun partition<$T>($v: vector<$T>, $f: |&$T| -> bool): (vector<$T>, vector<$T>) { + let v = $v; + let mut r1 = vector[]; + let mut r2 = vector[]; + v.do!(|e| if ($f(&e)) r1.push_back(e) else r2.push_back(e)); + (r1, r2) +} - /// Finds the index of first element in the vector `v` that satisfies the predicate `f`. - /// Returns `some(index)` if such an element is found, otherwise `none()`. - public macro fun find_index<$T>($v: &vector<$T>, $f: |&$T| -> bool): Option { - let v = $v; - 'find_index: { - v.length().do!(|i| if ($f(&v[i])) return 'find_index option::some(i)); - option::none() - } +/// Finds the index of first element in the vector `v` that satisfies the predicate `f`. +/// Returns `some(index)` if such an element is found, otherwise `none()`. +public macro fun find_index<$T>($v: &vector<$T>, $f: |&$T| -> bool): Option { + let v = $v; + 'find_index: { + v.length().do!(|i| if ($f(&v[i])) return 'find_index option::some(i)); + option::none() } +} - /// Count how many elements in the vector `v` satisfy the predicate `f`. - public macro fun count<$T>($v: &vector<$T>, $f: |&$T| -> bool): u64 { - let v = $v; - let mut count = 0; - v.do_ref!(|e| if ($f(e)) count = count + 1); - count - } +/// Count how many elements in the vector `v` satisfy the predicate `f`. +public macro fun count<$T>($v: &vector<$T>, $f: |&$T| -> bool): u64 { + let v = $v; + let mut count = 0; + v.do_ref!(|e| if ($f(e)) count = count + 1); + count +} - /// Reduce the vector `v` to a single value by applying the function `f` to each element. - /// Similar to `fold_left` in Rust and `reduce` in Python and JavaScript. - public macro fun fold<$T, $Acc>($v: vector<$T>, $init: $Acc, $f: |$Acc, $T| -> $Acc): $Acc { - let v = $v; - let mut acc = $init; - v.do!(|e| acc = $f(acc, e)); - acc - } +/// Reduce the vector `v` to a single value by applying the function `f` to each element. +/// Similar to `fold_left` in Rust and `reduce` in Python and JavaScript. +public macro fun fold<$T, $Acc>($v: vector<$T>, $init: $Acc, $f: |$Acc, $T| -> $Acc): $Acc { + let v = $v; + let mut acc = $init; + v.do!(|e| acc = $f(acc, e)); + acc +} - /// Concatenate the vectors of `v` into a single vector, keeping the order of the elements. - public fun flatten(v: vector>): vector { - let mut r = vector[]; - v.do!(|u| r.append(u)); - r - } +/// Concatenate the vectors of `v` into a single vector, keeping the order of the elements. +public fun flatten(v: vector>): vector { + let mut r = vector[]; + v.do!(|u| r.append(u)); + r +} - /// Whether any element in the vector `v` satisfies the predicate `f`. - /// If the vector is empty, returns `false`. - public macro fun any<$T>($v: &vector<$T>, $f: |&$T| -> bool): bool { - let v = $v; - 'any: { - v.do_ref!(|e| if ($f(e)) return 'any true); - false - } +/// Whether any element in the vector `v` satisfies the predicate `f`. +/// If the vector is empty, returns `false`. +public macro fun any<$T>($v: &vector<$T>, $f: |&$T| -> bool): bool { + let v = $v; + 'any: { + v.do_ref!(|e| if ($f(e)) return 'any true); + false } +} - /// Whether all elements in the vector `v` satisfy the predicate `f`. - /// If the vector is empty, returns `true`. - public macro fun all<$T>($v: &vector<$T>, $f: |&$T| -> bool): bool { - let v = $v; - 'all: { - v.do_ref!(|e| if (!$f(e)) return 'all false); - true - } +/// Whether all elements in the vector `v` satisfy the predicate `f`. +/// If the vector is empty, returns `true`. +public macro fun all<$T>($v: &vector<$T>, $f: |&$T| -> bool): bool { + let v = $v; + 'all: { + v.do_ref!(|e| if (!$f(e)) return 'all false); + true } +} - /// Destroys two vectors `v1` and `v2` by calling `f` to each pair of elements. - /// Aborts if the vectors are not of the same length. - /// The order of elements in the vectors is preserved. - public macro fun zip_do<$T1, $T2>($v1: vector<$T1>, $v2: vector<$T2>, $f: |$T1, $T2|) { - let v1 = $v1; - let mut v2 = $v2; - v2.reverse(); - let len = v1.length(); - assert!(len == v2.length()); - v1.do!(|el1| $f(el1, v2.pop_back())); - } +/// Destroys two vectors `v1` and `v2` by calling `f` to each pair of elements. +/// Aborts if the vectors are not of the same length. +/// The order of elements in the vectors is preserved. +public macro fun zip_do<$T1, $T2>($v1: vector<$T1>, $v2: vector<$T2>, $f: |$T1, $T2|) { + let v1 = $v1; + let mut v2 = $v2; + v2.reverse(); + let len = v1.length(); + assert!(len == v2.length()); + v1.do!(|el1| $f(el1, v2.pop_back())); +} - /// Destroys two vectors `v1` and `v2` by calling `f` to each pair of elements. - /// Aborts if the vectors are not of the same length. - /// Starts from the end of the vectors. - public macro fun zip_do_reverse<$T1, $T2>($v1: vector<$T1>, $v2: vector<$T2>, $f: |$T1, $T2|) { - let v1 = $v1; - let mut v2 = $v2; - let len = v1.length(); - assert!(len == v2.length()); - v1.destroy!(|el1| $f(el1, v2.pop_back())); - } +/// Destroys two vectors `v1` and `v2` by calling `f` to each pair of elements. +/// Aborts if the vectors are not of the same length. +/// Starts from the end of the vectors. +public macro fun zip_do_reverse<$T1, $T2>($v1: vector<$T1>, $v2: vector<$T2>, $f: |$T1, $T2|) { + let v1 = $v1; + let mut v2 = $v2; + let len = v1.length(); + assert!(len == v2.length()); + v1.destroy!(|el1| $f(el1, v2.pop_back())); +} - /// Iterate through `v1` and `v2` and apply the function `f` to references of each pair of - /// elements. The vectors are not modified. - /// Aborts if the vectors are not of the same length. - /// The order of elements in the vectors is preserved. - public macro fun zip_do_ref<$T1, $T2>($v1: &vector<$T1>, $v2: &vector<$T2>, $f: |&$T1, &$T2|) { - let v1 = $v1; - let v2 = $v2; - let len = v1.length(); - assert!(len == v2.length()); - len.do!(|i| $f(&v1[i], &v2[i])); - } +/// Iterate through `v1` and `v2` and apply the function `f` to references of each pair of +/// elements. The vectors are not modified. +/// Aborts if the vectors are not of the same length. +/// The order of elements in the vectors is preserved. +public macro fun zip_do_ref<$T1, $T2>($v1: &vector<$T1>, $v2: &vector<$T2>, $f: |&$T1, &$T2|) { + let v1 = $v1; + let v2 = $v2; + let len = v1.length(); + assert!(len == v2.length()); + len.do!(|i| $f(&v1[i], &v2[i])); +} - /// Iterate through `v1` and `v2` and apply the function `f` to mutable references of each pair - /// of elements. The vectors may be modified. - /// Aborts if the vectors are not of the same length. - /// The order of elements in the vectors is preserved. - public macro fun zip_do_mut<$T1, $T2>( - $v1: &mut vector<$T1>, - $v2: &mut vector<$T2>, - $f: |&mut $T1, &mut $T2|, - ) { - let v1 = $v1; - let v2 = $v2; - let len = v1.length(); - assert!(len == v2.length()); - len.do!(|i| $f(&mut v1[i], &mut v2[i])); - } +/// Iterate through `v1` and `v2` and apply the function `f` to mutable references of each pair +/// of elements. The vectors may be modified. +/// Aborts if the vectors are not of the same length. +/// The order of elements in the vectors is preserved. +public macro fun zip_do_mut<$T1, $T2>( + $v1: &mut vector<$T1>, + $v2: &mut vector<$T2>, + $f: |&mut $T1, &mut $T2|, +) { + let v1 = $v1; + let v2 = $v2; + let len = v1.length(); + assert!(len == v2.length()); + len.do!(|i| $f(&mut v1[i], &mut v2[i])); +} - /// Destroys two vectors `v1` and `v2` by applying the function `f` to each pair of elements. - /// The returned values are collected into a new vector. - /// Aborts if the vectors are not of the same length. - /// The order of elements in the vectors is preserved. - public macro fun zip_map<$T1, $T2, $U>( - $v1: vector<$T1>, - $v2: vector<$T2>, - $f: |$T1, $T2| -> $U, - ): vector<$U> { - let mut r = vector[]; - zip_do!($v1, $v2, |el1, el2| r.push_back($f(el1, el2))); - r - } +/// Destroys two vectors `v1` and `v2` by applying the function `f` to each pair of elements. +/// The returned values are collected into a new vector. +/// Aborts if the vectors are not of the same length. +/// The order of elements in the vectors is preserved. +public macro fun zip_map<$T1, $T2, $U>( + $v1: vector<$T1>, + $v2: vector<$T2>, + $f: |$T1, $T2| -> $U, +): vector<$U> { + let mut r = vector[]; + zip_do!($v1, $v2, |el1, el2| r.push_back($f(el1, el2))); + r +} - /// Iterate through `v1` and `v2` and apply the function `f` to references of each pair of - /// elements. The returned values are collected into a new vector. - /// Aborts if the vectors are not of the same length. - /// The order of elements in the vectors is preserved. - public macro fun zip_map_ref<$T1, $T2, $U>( - $v1: &vector<$T1>, - $v2: &vector<$T2>, - $f: |&$T1, &$T2| -> $U, - ): vector<$U> { - let mut r = vector[]; - zip_do_ref!($v1, $v2, |el1, el2| r.push_back($f(el1, el2))); - r - } +/// Iterate through `v1` and `v2` and apply the function `f` to references of each pair of +/// elements. The returned values are collected into a new vector. +/// Aborts if the vectors are not of the same length. +/// The order of elements in the vectors is preserved. +public macro fun zip_map_ref<$T1, $T2, $U>( + $v1: &vector<$T1>, + $v2: &vector<$T2>, + $f: |&$T1, &$T2| -> $U, +): vector<$U> { + let mut r = vector[]; + zip_do_ref!($v1, $v2, |el1, el2| r.push_back($f(el1, el2))); + r } diff --git a/crates/sui-framework/packages/move-stdlib/tests/ascii_tests.move b/crates/sui-framework/packages/move-stdlib/tests/ascii_tests.move index ec6d4c7fdb817..13aae73d22c40 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/ascii_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/ascii_tests.move @@ -4,219 +4,219 @@ // SPDX-License-Identifier: Apache-2.0 #[test_only] -module std::ascii_tests { - use std::ascii; - - #[test] - fun test_ascii_chars() { - let mut i = 0; - let end = 128; - let mut vec = vector[]; - - while (i < end) { - assert!(ascii::is_valid_char(i)); - vec.push_back(i); - i = i + 1; - }; - - let str = vec.to_ascii_string(); - assert!(str.as_bytes().length() == 128); - assert!(!str.all_characters_printable()); - assert!(str.into_bytes().length() == 128); - } - - #[test] - fun test_ascii_push_chars() { - let mut i = 0; - let end = 128; - let mut str = vector[].to_ascii_string(); - - while (i < end) { - str.push_char(ascii::char(i)); - i = i + 1; - }; - - assert!(str.as_bytes().length() == 128); - assert!(str.length() == 128); - assert!(!str.all_characters_printable()); - } - - #[test] - fun test_ascii_push_char_pop_char() { - let mut i = 0; - let end = 128; - let mut str = vector[].to_ascii_string(); - - while (i < end) { - str.push_char(ascii::char(i)); - i = i + 1; - }; - - while (i > 0) { - let char = str.pop_char(); - assert!(ascii::byte(char) == i - 1); - i = i - 1; - }; - - assert!(str.as_bytes().length() == 0); - assert!(str.length() == 0); - assert!(str.all_characters_printable()); - } - - #[test] - fun test_printable_chars() { - let mut i = 0x20; - let end = 0x7E; - let mut vec = vector[]; - - while (i <= end) { - assert!(ascii::is_printable_char(i)); - vec.push_back(i); - i = i + 1; - }; - - let str = vec.to_ascii_string(); - assert!(str.all_characters_printable()); - } - - #[test] - fun printable_chars_dont_allow_tab() { - let str = vector[0x09].to_ascii_string(); +module std::ascii_tests; + +use std::ascii; + +#[test] +fun test_ascii_chars() { + let mut i = 0; + let end = 128; + let mut vec = vector[]; + + while (i < end) { + assert!(ascii::is_valid_char(i)); + vec.push_back(i); + i = i + 1; + }; + + let str = vec.to_ascii_string(); + assert!(str.as_bytes().length() == 128); + assert!(!str.all_characters_printable()); + assert!(str.into_bytes().length() == 128); +} + +#[test] +fun test_ascii_push_chars() { + let mut i = 0; + let end = 128; + let mut str = vector[].to_ascii_string(); + + while (i < end) { + str.push_char(ascii::char(i)); + i = i + 1; + }; + + assert!(str.as_bytes().length() == 128); + assert!(str.length() == 128); + assert!(!str.all_characters_printable()); +} + +#[test] +fun test_ascii_push_char_pop_char() { + let mut i = 0; + let end = 128; + let mut str = vector[].to_ascii_string(); + + while (i < end) { + str.push_char(ascii::char(i)); + i = i + 1; + }; + + while (i > 0) { + let char = str.pop_char(); + assert!(ascii::byte(char) == i - 1); + i = i - 1; + }; + + assert!(str.as_bytes().length() == 0); + assert!(str.length() == 0); + assert!(str.all_characters_printable()); +} + +#[test] +fun test_printable_chars() { + let mut i = 0x20; + let end = 0x7E; + let mut vec = vector[]; + + while (i <= end) { + assert!(ascii::is_printable_char(i)); + vec.push_back(i); + i = i + 1; + }; + + let str = vec.to_ascii_string(); + assert!(str.all_characters_printable()); +} + +#[test] +fun printable_chars_dont_allow_tab() { + let str = vector[0x09].to_ascii_string(); + assert!(!str.all_characters_printable()); +} + +#[test] +fun printable_chars_dont_allow_newline() { + let str = vector[0x0A].to_ascii_string(); + assert!(!str.all_characters_printable()); +} + +#[test] +fun test_invalid_ascii_characters() { + let mut i = 128u8; + let end = 255u8; + while (i < end) { + let try_str = vector[i].try_to_ascii_string(); + assert!(try_str.is_none()); + i = i + 1; + }; +} + +#[test] +fun test_nonvisible_chars() { + let mut i = 0; + let end = 0x09; + while (i < end) { + let str = vector[i].to_ascii_string(); assert!(!str.all_characters_printable()); - } + i = i + 1; + }; - #[test] - fun printable_chars_dont_allow_newline() { - let str = vector[0x0A].to_ascii_string(); + let mut i = 0x0B; + let end = 0x0F; + while (i <= end) { + let str = vector[i].to_ascii_string(); assert!(!str.all_characters_printable()); - } - - #[test] - fun test_invalid_ascii_characters() { - let mut i = 128u8; - let end = 255u8; - while (i < end) { - let try_str = vector[i].try_to_ascii_string(); - assert!(try_str.is_none()); - i = i + 1; - }; - } - - #[test] - fun test_nonvisible_chars() { - let mut i = 0; - let end = 0x09; - while (i < end) { - let str = vector[i].to_ascii_string(); - assert!(!str.all_characters_printable()); - i = i + 1; - }; - - let mut i = 0x0B; - let end = 0x0F; - while (i <= end) { - let str = vector[i].to_ascii_string(); - assert!(!str.all_characters_printable()); - i = i + 1; - }; - } - - #[test] - fun test_append() { - let mut str = b"hello".to_ascii_string(); - str.append(b" world".to_ascii_string()); - - assert!(str == b"hello world".to_ascii_string()); - } - - #[test] - fun test_to_uppercase() { - let str = b"azhello_world_!".to_ascii_string(); - assert!(str.to_uppercase() == b"AZHELLO_WORLD_!".to_ascii_string()); - } - - #[test] - fun test_to_lowercase() { - let str = b"AZHELLO_WORLD_!".to_ascii_string(); - assert!(str.to_lowercase() == b"azhello_world_!".to_ascii_string()); - } - - #[test] - fun test_substring() { - let str = b"hello world".to_ascii_string(); - assert!(str.substring(0, 5) == b"hello".to_ascii_string()); - assert!(str.substring(6, 11) == b"world".to_ascii_string()); - } - - #[test] - fun test_substring_len_one() { - let str = b"hello world".to_ascii_string(); - assert!(str.substring(0, 1) == b"h".to_ascii_string()); - assert!(str.substring(6, 7) == b"w".to_ascii_string()); - } - - #[test] - fun test_substring_len_zero() { - let str = b"hello world".to_ascii_string(); - assert!(str.substring(0, 0).is_empty()); - } - - #[test] - fun test_index_of() { - let str = b"hello world orwell".to_ascii_string(); - assert!(str.index_of(&b"hello".to_ascii_string()) == 0); - assert!(str.index_of(&b"world".to_ascii_string()) == 6); - assert!(str.index_of(&b"o".to_ascii_string()) == 4); - assert!(str.index_of(&b"z".to_ascii_string()) == str.length()); - assert!(str.index_of(&b"o ".to_ascii_string()) == 4); - assert!(str.index_of(&b"or".to_ascii_string()) == 7); - assert!(str.index_of(&b"".to_ascii_string()) == 0); - assert!(str.index_of(&b"orwell".to_ascii_string()) == 12); - assert!( - b"ororwell" - .to_ascii_string() - .index_of(&b"orwell".to_ascii_string()) == 2, - ); - } - - #[test, expected_failure(abort_code = ascii::EInvalidIndex)] - fun test_substring_i_out_of_bounds_fail() { - let str = b"hello world".to_ascii_string(); - str.substring(12, 13); - } - - #[test, expected_failure(abort_code = ascii::EInvalidIndex)] - fun test_substring_j_lt_i_fail() { - let str = b"hello world".to_ascii_string(); - str.substring(9, 8); - } - - #[test, expected_failure(abort_code = ascii::EInvalidIndex)] - fun test_substring_j_out_of_bounds_fail() { - let str = b"hello world".to_ascii_string(); - str.substring(9, 13); - } - - #[test] - fun test_insert() { - let mut str = b"hello".to_ascii_string(); - str.insert(5, b" world".to_ascii_string()); - assert!(str == b"hello world".to_ascii_string()); - - str.insert(5, b" cruel".to_ascii_string()); - assert!(str == b"hello cruel world".to_ascii_string()); - } - - #[test] - fun test_insert_empty() { - let mut str = b"hello".to_ascii_string(); - str.insert(5, b"".to_ascii_string()); - assert!(str == b"hello".to_ascii_string()); - } - - #[test, expected_failure(abort_code = ascii::EInvalidIndex)] - fun test_insert_out_of_bounds_fail() { - let mut str = b"hello".to_ascii_string(); - str.insert(6, b" world".to_ascii_string()); - } + i = i + 1; + }; +} + +#[test] +fun test_append() { + let mut str = b"hello".to_ascii_string(); + str.append(b" world".to_ascii_string()); + + assert!(str == b"hello world".to_ascii_string()); +} + +#[test] +fun test_to_uppercase() { + let str = b"azhello_world_!".to_ascii_string(); + assert!(str.to_uppercase() == b"AZHELLO_WORLD_!".to_ascii_string()); +} + +#[test] +fun test_to_lowercase() { + let str = b"AZHELLO_WORLD_!".to_ascii_string(); + assert!(str.to_lowercase() == b"azhello_world_!".to_ascii_string()); +} + +#[test] +fun test_substring() { + let str = b"hello world".to_ascii_string(); + assert!(str.substring(0, 5) == b"hello".to_ascii_string()); + assert!(str.substring(6, 11) == b"world".to_ascii_string()); +} + +#[test] +fun test_substring_len_one() { + let str = b"hello world".to_ascii_string(); + assert!(str.substring(0, 1) == b"h".to_ascii_string()); + assert!(str.substring(6, 7) == b"w".to_ascii_string()); +} + +#[test] +fun test_substring_len_zero() { + let str = b"hello world".to_ascii_string(); + assert!(str.substring(0, 0).is_empty()); +} + +#[test] +fun test_index_of() { + let str = b"hello world orwell".to_ascii_string(); + assert!(str.index_of(&b"hello".to_ascii_string()) == 0); + assert!(str.index_of(&b"world".to_ascii_string()) == 6); + assert!(str.index_of(&b"o".to_ascii_string()) == 4); + assert!(str.index_of(&b"z".to_ascii_string()) == str.length()); + assert!(str.index_of(&b"o ".to_ascii_string()) == 4); + assert!(str.index_of(&b"or".to_ascii_string()) == 7); + assert!(str.index_of(&b"".to_ascii_string()) == 0); + assert!(str.index_of(&b"orwell".to_ascii_string()) == 12); + assert!( + b"ororwell" + .to_ascii_string() + .index_of(&b"orwell".to_ascii_string()) == 2, + ); +} + +#[test, expected_failure(abort_code = ascii::EInvalidIndex)] +fun test_substring_i_out_of_bounds_fail() { + let str = b"hello world".to_ascii_string(); + str.substring(12, 13); +} + +#[test, expected_failure(abort_code = ascii::EInvalidIndex)] +fun test_substring_j_lt_i_fail() { + let str = b"hello world".to_ascii_string(); + str.substring(9, 8); +} + +#[test, expected_failure(abort_code = ascii::EInvalidIndex)] +fun test_substring_j_out_of_bounds_fail() { + let str = b"hello world".to_ascii_string(); + str.substring(9, 13); +} + +#[test] +fun test_insert() { + let mut str = b"hello".to_ascii_string(); + str.insert(5, b" world".to_ascii_string()); + assert!(str == b"hello world".to_ascii_string()); + + str.insert(5, b" cruel".to_ascii_string()); + assert!(str == b"hello cruel world".to_ascii_string()); +} + +#[test] +fun test_insert_empty() { + let mut str = b"hello".to_ascii_string(); + str.insert(5, b"".to_ascii_string()); + assert!(str == b"hello".to_ascii_string()); +} + +#[test, expected_failure(abort_code = ascii::EInvalidIndex)] +fun test_insert_out_of_bounds_fail() { + let mut str = b"hello".to_ascii_string(); + str.insert(6, b" world".to_ascii_string()); } diff --git a/crates/sui-framework/packages/move-stdlib/tests/bcs_tests.move b/crates/sui-framework/packages/move-stdlib/tests/bcs_tests.move index 308dd99920289..10e1d265a4a90 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/bcs_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/bcs_tests.move @@ -4,106 +4,106 @@ // SPDX-License-Identifier: Apache-2.0 #[test_only] -module std::bcs_tests { - use std::bcs; - - public struct Box has copy, drop, store { x: T } - public struct Box3 has copy, drop, store { x: Box> } - public struct Box7 has copy, drop, store { x: Box3> } - public struct Box15 has copy, drop, store { x: Box7> } - public struct Box31 has copy, drop, store { x: Box15> } - public struct Box63 has copy, drop, store { x: Box31> } - public struct Box127 has copy, drop, store { x: Box63> } - - #[test] - fun bcs_address() { - let addr = @0x0000000000000000000000000000000089b9f9d1fadc027cf9532d6f99041522; - let expected_output = x"0000000000000000000000000000000089b9f9d1fadc027cf9532d6f99041522"; - assert!(bcs::to_bytes(&addr) == expected_output); - } - - #[test] - fun bcs_bool() { - let expected_output = x"01"; - assert!(bcs::to_bytes(&true) == expected_output); - } - - #[test] - fun bcs_u8() { - let expected_output = x"01"; - assert!(bcs::to_bytes(&1u8) == expected_output); - } - - #[test] - fun bcs_u16() { - let expected_output = x"0100"; - assert!(bcs::to_bytes(&1u16) == expected_output); - } - - #[test] - fun bcs_u32() { - let expected_output = x"01000000"; - assert!(bcs::to_bytes(&1u32) == expected_output); - } - - #[test] - fun bcs_u64() { - let expected_output = x"0100000000000000"; - assert!(bcs::to_bytes(&1) == expected_output); - } - - #[test] - fun bcs_u128() { - let expected_output = x"01000000000000000000000000000000"; - assert!(bcs::to_bytes(&1u128) == expected_output); - } - - #[test] - fun bcs_u256() { - let expected_output = x"0100000000000000000000000000000000000000000000000000000000000000"; - assert!(bcs::to_bytes(&1u256) == expected_output); - } - - #[test] - fun bcs_vec_u8() { - let v = x"0f"; - let expected_output = x"010f"; - assert!(bcs::to_bytes(&v) == expected_output); - } - - fun box3(x: T): Box3 { - Box3 { x: Box { x: Box { x } } } - } - - fun box7(x: T): Box7 { - Box7 { x: box3(box3(x)) } - } - - fun box15(x: T): Box15 { - Box15 { x: box7(box7(x)) } - } - - fun box31(x: T): Box31 { - Box31 { x: box15(box15(x)) } - } - - fun box63(x: T): Box63 { - Box63 { x: box31(box31(x)) } - } - - fun box127(x: T): Box127 { - Box127 { x: box63(box63(x)) } - } - - #[test] - fun encode_128() { - bcs::to_bytes(&box127(true)); - } - - #[test] - #[expected_failure] - // failes due to VM max value depth - fun encode_129() { - bcs::to_bytes(&Box { x: box127(true) }); - } +module std::bcs_tests; + +use std::bcs; + +public struct Box has copy, drop, store { x: T } +public struct Box3 has copy, drop, store { x: Box> } +public struct Box7 has copy, drop, store { x: Box3> } +public struct Box15 has copy, drop, store { x: Box7> } +public struct Box31 has copy, drop, store { x: Box15> } +public struct Box63 has copy, drop, store { x: Box31> } +public struct Box127 has copy, drop, store { x: Box63> } + +#[test] +fun bcs_address() { + let addr = @0x0000000000000000000000000000000089b9f9d1fadc027cf9532d6f99041522; + let expected_output = x"0000000000000000000000000000000089b9f9d1fadc027cf9532d6f99041522"; + assert!(bcs::to_bytes(&addr) == expected_output); +} + +#[test] +fun bcs_bool() { + let expected_output = x"01"; + assert!(bcs::to_bytes(&true) == expected_output); +} + +#[test] +fun bcs_u8() { + let expected_output = x"01"; + assert!(bcs::to_bytes(&1u8) == expected_output); +} + +#[test] +fun bcs_u16() { + let expected_output = x"0100"; + assert!(bcs::to_bytes(&1u16) == expected_output); +} + +#[test] +fun bcs_u32() { + let expected_output = x"01000000"; + assert!(bcs::to_bytes(&1u32) == expected_output); +} + +#[test] +fun bcs_u64() { + let expected_output = x"0100000000000000"; + assert!(bcs::to_bytes(&1) == expected_output); +} + +#[test] +fun bcs_u128() { + let expected_output = x"01000000000000000000000000000000"; + assert!(bcs::to_bytes(&1u128) == expected_output); +} + +#[test] +fun bcs_u256() { + let expected_output = x"0100000000000000000000000000000000000000000000000000000000000000"; + assert!(bcs::to_bytes(&1u256) == expected_output); +} + +#[test] +fun bcs_vec_u8() { + let v = x"0f"; + let expected_output = x"010f"; + assert!(bcs::to_bytes(&v) == expected_output); +} + +fun box3(x: T): Box3 { + Box3 { x: Box { x: Box { x } } } +} + +fun box7(x: T): Box7 { + Box7 { x: box3(box3(x)) } +} + +fun box15(x: T): Box15 { + Box15 { x: box7(box7(x)) } +} + +fun box31(x: T): Box31 { + Box31 { x: box15(box15(x)) } +} + +fun box63(x: T): Box63 { + Box63 { x: box31(box31(x)) } +} + +fun box127(x: T): Box127 { + Box127 { x: box63(box63(x)) } +} + +#[test] +fun encode_128() { + bcs::to_bytes(&box127(true)); +} + +#[test] +#[expected_failure] +// failes due to VM max value depth +fun encode_129() { + bcs::to_bytes(&Box { x: box127(true) }); } diff --git a/crates/sui-framework/packages/move-stdlib/tests/bit_vector_tests.move b/crates/sui-framework/packages/move-stdlib/tests/bit_vector_tests.move index 10a35c0cfa84c..28ce9a9e9d830 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/bit_vector_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/bit_vector_tests.move @@ -4,225 +4,225 @@ // SPDX-License-Identifier: Apache-2.0 #[test_only] -module std::bit_vector_tests { - use std::bit_vector; - - #[test_only] - fun test_bitvector_set_unset_of_size(k: u64) { - let mut bitvector = bit_vector::new(k); - let mut index = 0; - while (index < k) { - bitvector.set(index); - assert!(bitvector.is_index_set(index)); - index = index + 1; - let mut index_to_right = index; - while (index_to_right < k) { - assert!(!bitvector.is_index_set(index_to_right)); - index_to_right = index_to_right + 1; - }; +module std::bit_vector_tests; + +use std::bit_vector; + +#[test_only] +fun test_bitvector_set_unset_of_size(k: u64) { + let mut bitvector = bit_vector::new(k); + let mut index = 0; + while (index < k) { + bitvector.set(index); + assert!(bitvector.is_index_set(index)); + index = index + 1; + let mut index_to_right = index; + while (index_to_right < k) { + assert!(!bitvector.is_index_set(index_to_right)); + index_to_right = index_to_right + 1; }; - // now go back down unsetting - index = 0; - - while (index < k) { - bitvector.unset(index); - assert!(!bitvector.is_index_set(index)); - index = index + 1; - let mut index_to_right = index; - while (index_to_right < k) { - assert!(bitvector.is_index_set(index_to_right)); - index_to_right = index_to_right + 1; - }; + }; + // now go back down unsetting + index = 0; + + while (index < k) { + bitvector.unset(index); + assert!(!bitvector.is_index_set(index)); + index = index + 1; + let mut index_to_right = index; + while (index_to_right < k) { + assert!(bitvector.is_index_set(index_to_right)); + index_to_right = index_to_right + 1; }; - } + }; +} - #[test] - #[expected_failure(abort_code = bit_vector::EINDEX)] - fun set_bit_out_of_bounds() { - let mut bitvector = bit_vector::new(bit_vector::word_size()); - bitvector.set(bit_vector::word_size()); - } +#[test] +#[expected_failure(abort_code = bit_vector::EINDEX)] +fun set_bit_out_of_bounds() { + let mut bitvector = bit_vector::new(bit_vector::word_size()); + bitvector.set(bit_vector::word_size()); +} - #[test] - #[expected_failure(abort_code = bit_vector::EINDEX)] - fun unset_bit_out_of_bounds() { - let mut bitvector = bit_vector::new(bit_vector::word_size()); - bitvector.unset(bit_vector::word_size()); - } +#[test] +#[expected_failure(abort_code = bit_vector::EINDEX)] +fun unset_bit_out_of_bounds() { + let mut bitvector = bit_vector::new(bit_vector::word_size()); + bitvector.unset(bit_vector::word_size()); +} - #[test] - #[expected_failure(abort_code = bit_vector::EINDEX)] - fun index_bit_out_of_bounds() { - let bitvector = bit_vector::new(bit_vector::word_size()); - bitvector.is_index_set(bit_vector::word_size()); - } +#[test] +#[expected_failure(abort_code = bit_vector::EINDEX)] +fun index_bit_out_of_bounds() { + let bitvector = bit_vector::new(bit_vector::word_size()); + bitvector.is_index_set(bit_vector::word_size()); +} - #[test] - fun test_set_bit_and_index_basic() { - test_bitvector_set_unset_of_size(8) - } +#[test] +fun test_set_bit_and_index_basic() { + test_bitvector_set_unset_of_size(8) +} - #[test] - fun test_set_bit_and_index_odd_size() { - test_bitvector_set_unset_of_size(140) - } +#[test] +fun test_set_bit_and_index_odd_size() { + test_bitvector_set_unset_of_size(140) +} - #[test] - fun longest_sequence_no_set_zero_index() { - let bitvector = bit_vector::new(100); - assert!(bitvector.longest_set_sequence_starting_at(0) == 0); - } +#[test] +fun longest_sequence_no_set_zero_index() { + let bitvector = bit_vector::new(100); + assert!(bitvector.longest_set_sequence_starting_at(0) == 0); +} - #[test] - fun longest_sequence_one_set_zero_index() { - let mut bitvector = bit_vector::new(100); - bitvector.set(1); - assert!(bitvector.longest_set_sequence_starting_at(0) == 0); - } +#[test] +fun longest_sequence_one_set_zero_index() { + let mut bitvector = bit_vector::new(100); + bitvector.set(1); + assert!(bitvector.longest_set_sequence_starting_at(0) == 0); +} - #[test] - fun longest_sequence_no_set_nonzero_index() { - let bitvector = bit_vector::new(100); - assert!(bitvector.longest_set_sequence_starting_at(51) == 0); - } +#[test] +fun longest_sequence_no_set_nonzero_index() { + let bitvector = bit_vector::new(100); + assert!(bitvector.longest_set_sequence_starting_at(51) == 0); +} - #[test] - fun longest_sequence_two_set_nonzero_index() { - let mut bitvector = bit_vector::new(100); - bitvector.set(50); - bitvector.set(52); - assert!(bitvector.longest_set_sequence_starting_at(51) == 0); - } +#[test] +fun longest_sequence_two_set_nonzero_index() { + let mut bitvector = bit_vector::new(100); + bitvector.set(50); + bitvector.set(52); + assert!(bitvector.longest_set_sequence_starting_at(51) == 0); +} - #[test] - fun longest_sequence_with_break() { - let mut bitvector = bit_vector::new(100); - let mut i = 0; - while (i < 20) { - bitvector.set(i); - i = i + 1; - }; - // create a break in the run +#[test] +fun longest_sequence_with_break() { + let mut bitvector = bit_vector::new(100); + let mut i = 0; + while (i < 20) { + bitvector.set(i); i = i + 1; - while (i < 100) { - bitvector.set(i); - i = i + 1; - }; - assert!(bitvector.longest_set_sequence_starting_at(0) == 20); - assert!(bitvector.longest_set_sequence_starting_at(20) == 0); - assert!(bitvector.longest_set_sequence_starting_at(21) == 100 - 21); - } - - #[test] - fun test_shift_left() { - let bitlen = 97; - let mut bitvector = bit_vector::new(bitlen); + }; + // create a break in the run + i = i + 1; + while (i < 100) { + bitvector.set(i); + i = i + 1; + }; + assert!(bitvector.longest_set_sequence_starting_at(0) == 20); + assert!(bitvector.longest_set_sequence_starting_at(20) == 0); + assert!(bitvector.longest_set_sequence_starting_at(21) == 100 - 21); +} - let mut i = 0; - while (i < bitlen) { - bitvector.set(i); - i = i + 1; - }; +#[test] +fun test_shift_left() { + let bitlen = 97; + let mut bitvector = bit_vector::new(bitlen); - i = bitlen - 1; - while (i > 0) { - assert!(bitvector.is_index_set(i)); - bitvector.shift_left(1); - assert!(!bitvector.is_index_set( i)); - i = i - 1; - }; - } + let mut i = 0; + while (i < bitlen) { + bitvector.set(i); + i = i + 1; + }; + + i = bitlen - 1; + while (i > 0) { + assert!(bitvector.is_index_set(i)); + bitvector.shift_left(1); + assert!(!bitvector.is_index_set(i)); + i = i - 1; + }; +} - #[test] - fun test_shift_left_specific_amount() { - let bitlen = 300; - let shift_amount = 133; - let mut bitvector = bit_vector::new(bitlen); +#[test] +fun test_shift_left_specific_amount() { + let bitlen = 300; + let shift_amount = 133; + let mut bitvector = bit_vector::new(bitlen); - bitvector.set(201); - assert!(bitvector.is_index_set(201)); + bitvector.set(201); + assert!(bitvector.is_index_set(201)); - bitvector.shift_left(shift_amount); - assert!(bitvector.is_index_set(201 - shift_amount)); - assert!(!bitvector.is_index_set(201)); + bitvector.shift_left(shift_amount); + assert!(bitvector.is_index_set(201 - shift_amount)); + assert!(!bitvector.is_index_set(201)); - // Make sure this shift clears all the bits - bitvector.shift_left(bitlen - 1); + // Make sure this shift clears all the bits + bitvector.shift_left(bitlen - 1); - let mut i = 0; - while (i < bitlen) { - assert!(!bitvector.is_index_set(i)); - i = i + 1; - } + let mut i = 0; + while (i < bitlen) { + assert!(!bitvector.is_index_set(i)); + i = i + 1; } +} - #[test] - fun test_shift_left_specific_amount_to_unset_bit() { - let bitlen = 50; - let chosen_index = 24; - let shift_amount = 3; - let mut bitvector = bit_vector::new(bitlen); +#[test] +fun test_shift_left_specific_amount_to_unset_bit() { + let bitlen = 50; + let chosen_index = 24; + let shift_amount = 3; + let mut bitvector = bit_vector::new(bitlen); - let mut i = 0; + let mut i = 0; - while (i < bitlen) { - bitvector.set(i); - i = i + 1; - }; + while (i < bitlen) { + bitvector.set(i); + i = i + 1; + }; - bitvector.unset(chosen_index); - assert!(!bitvector.is_index_set(chosen_index)); + bitvector.unset(chosen_index); + assert!(!bitvector.is_index_set(chosen_index)); - bitvector.shift_left(shift_amount); + bitvector.shift_left(shift_amount); - i = 0; + i = 0; - while (i < bitlen) { - // only chosen_index - shift_amount and the remaining bits should be BitVector::unset - if ((i == chosen_index - shift_amount) || (i >= bitlen - shift_amount)) { - assert!(!bitvector.is_index_set(i)); - } else { - assert!(bitvector.is_index_set(i)); - }; - i = i + 1; - } + while (i < bitlen) { + // only chosen_index - shift_amount and the remaining bits should be BitVector::unset + if ((i == chosen_index - shift_amount) || (i >= bitlen - shift_amount)) { + assert!(!bitvector.is_index_set(i)); + } else { + assert!(bitvector.is_index_set(i)); + }; + i = i + 1; } +} - #[test] - fun shift_left_at_size() { - let bitlen = 133; - let mut bitvector = bit_vector::new(bitlen); +#[test] +fun shift_left_at_size() { + let bitlen = 133; + let mut bitvector = bit_vector::new(bitlen); - let mut i = 0; - while (i < bitlen) { - bitvector.set(i); - i = i + 1; - }; - - bitvector.shift_left(bitlen - 1); - i = bitlen - 1; - while (i > 0) { - assert!(!bitvector.is_index_set( i)); - i = i - 1; - }; - } + let mut i = 0; + while (i < bitlen) { + bitvector.set(i); + i = i + 1; + }; + + bitvector.shift_left(bitlen - 1); + i = bitlen - 1; + while (i > 0) { + assert!(!bitvector.is_index_set(i)); + i = i - 1; + }; +} - #[test] - fun shift_left_more_than_size() { - let bitlen = 133; - let mut bitvector = bit_vector::new(bitlen); - bitvector.shift_left(bitlen); - } +#[test] +fun shift_left_more_than_size() { + let bitlen = 133; + let mut bitvector = bit_vector::new(bitlen); + bitvector.shift_left(bitlen); +} - #[test] - #[expected_failure(abort_code = bit_vector::ELENGTH)] - fun empty_bitvector() { - bit_vector::new(0); - } +#[test] +#[expected_failure(abort_code = bit_vector::ELENGTH)] +fun empty_bitvector() { + bit_vector::new(0); +} - #[test] - fun single_bit_bitvector() { - let bitvector = bit_vector::new(1); - assert!(bitvector.length() == 1); - } +#[test] +fun single_bit_bitvector() { + let bitvector = bit_vector::new(1); + assert!(bitvector.length() == 1); } diff --git a/crates/sui-framework/packages/move-stdlib/tests/fixedpoint32_tests.move b/crates/sui-framework/packages/move-stdlib/tests/fixedpoint32_tests.move index 4fd5f847b4ab9..f3a42e898ed11 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/fixedpoint32_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/fixedpoint32_tests.move @@ -3,112 +3,112 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -#[test_only] -module std::fixed_point32_tests { - use std::fixed_point32; - - #[test] - #[expected_failure(abort_code = fixed_point32::EDENOMINATOR)] - fun create_div_zero() { - // A denominator of zero should cause an arithmetic error. - fixed_point32::create_from_rational(2, 0); - } - - #[test] - #[expected_failure(abort_code = fixed_point32::ERATIO_OUT_OF_RANGE)] - fun create_overflow() { - // The maximum value is 2^32 - 1. Check that anything larger aborts - // with an overflow. - fixed_point32::create_from_rational(4294967296, 1); // 2^32 - } - - #[test] - #[expected_failure(abort_code = fixed_point32::ERATIO_OUT_OF_RANGE)] - fun create_underflow() { - // The minimum non-zero value is 2^-32. Check that anything smaller - // aborts. - fixed_point32::create_from_rational(1, 8589934592); // 2^-33 - } - - #[test] - fun create_zero() { - let x = fixed_point32::create_from_rational(0, 1); - assert!(x.is_zero()); - } - - #[test] - #[expected_failure(abort_code = fixed_point32::EDIVISION_BY_ZERO)] - fun divide_by_zero() { - // Dividing by zero should cause an arithmetic error. - let f = fixed_point32::create_from_raw_value(0); - fixed_point32::divide_u64(1, f); - } - - #[test] - #[expected_failure(abort_code = fixed_point32::EDIVISION)] - fun divide_overflow_small_divisore() { - let f = fixed_point32::create_from_raw_value(1); // 0x0.00000001 - // Divide 2^32 by the minimum fractional value. This should overflow. - fixed_point32::divide_u64(4294967296, f); - } - - #[test] - #[expected_failure(abort_code = fixed_point32::EDIVISION)] - fun divide_overflow_large_numerator() { - let f = fixed_point32::create_from_rational(1, 2); // 0.5 - // Divide the maximum u64 value by 0.5. This should overflow. - fixed_point32::divide_u64(18446744073709551615, f); - } - - #[test] - #[expected_failure(abort_code = fixed_point32::EMULTIPLICATION)] - fun multiply_overflow_small_multiplier() { - let f = fixed_point32::create_from_rational(3, 2); // 1.5 - // Multiply the maximum u64 value by 1.5. This should overflow. - fixed_point32::multiply_u64(18446744073709551615, f); - } - - #[test] - #[expected_failure(abort_code = fixed_point32::EMULTIPLICATION)] - fun multiply_overflow_large_multiplier() { - let f = fixed_point32::create_from_raw_value(18446744073709551615); - // Multiply 2^33 by the maximum fixed-point value. This should overflow. - fixed_point32::multiply_u64(8589934592, f); - } - - #[test] - fun exact_multiply() { - let f = fixed_point32::create_from_rational(3, 4); // 0.75 - let nine = fixed_point32::multiply_u64(12, f); // 12 * 0.75 - assert!(nine == 9); - } - - #[test] - fun exact_divide() { - let f = fixed_point32::create_from_rational(3, 4); // 0.75 - let twelve = fixed_point32::divide_u64(9, f); // 9 / 0.75 - assert!(twelve == 12); - } - - #[test] - fun multiply_truncates() { - let f = fixed_point32::create_from_rational(1, 3); // 0.333... - let not_three = fixed_point32::multiply_u64(9, copy f); // 9 * 0.333... - // multiply_u64 does NOT round -- it truncates -- so values that - // are not perfectly representable in binary may be off by one. - assert!(not_three == 2); - - // Try again with a fraction slightly larger than 1/3. - let f = fixed_point32::create_from_raw_value(f.get_raw_value() + 1); - let three = fixed_point32::multiply_u64(9, f); - assert!(three == 3); - } - - #[test] - fun create_from_rational_max_numerator_denominator() { - // Test creating a 1.0 fraction from the maximum u64 value. - let f = fixed_point32::create_from_rational(18446744073709551615, 18446744073709551615); - let one = f.get_raw_value(); - assert!(one == 4294967296); // 0x1.00000000 - } +#[test_only, allow(deprecated_usage)] +module std::fixed_point32_tests; + +use std::fixed_point32; + +#[test] +#[expected_failure(abort_code = fixed_point32::EDENOMINATOR)] +fun create_div_zero() { + // A denominator of zero should cause an arithmetic error. + fixed_point32::create_from_rational(2, 0); +} + +#[test] +#[expected_failure(abort_code = fixed_point32::ERATIO_OUT_OF_RANGE)] +fun create_overflow() { + // The maximum value is 2^32 - 1. Check that anything larger aborts + // with an overflow. + fixed_point32::create_from_rational(4294967296, 1); // 2^32 +} + +#[test] +#[expected_failure(abort_code = fixed_point32::ERATIO_OUT_OF_RANGE)] +fun create_underflow() { + // The minimum non-zero value is 2^-32. Check that anything smaller + // aborts. + fixed_point32::create_from_rational(1, 8589934592); // 2^-33 +} + +#[test] +fun create_zero() { + let x = fixed_point32::create_from_rational(0, 1); + assert!(x.is_zero()); +} + +#[test] +#[expected_failure(abort_code = fixed_point32::EDIVISION_BY_ZERO)] +fun divide_by_zero() { + // Dividing by zero should cause an arithmetic error. + let f = fixed_point32::create_from_raw_value(0); + fixed_point32::divide_u64(1, f); +} + +#[test] +#[expected_failure(abort_code = fixed_point32::EDIVISION)] +fun divide_overflow_small_divisore() { + let f = fixed_point32::create_from_raw_value(1); // 0x0.00000001 + // Divide 2^32 by the minimum fractional value. This should overflow. + fixed_point32::divide_u64(4294967296, f); +} + +#[test] +#[expected_failure(abort_code = fixed_point32::EDIVISION)] +fun divide_overflow_large_numerator() { + let f = fixed_point32::create_from_rational(1, 2); // 0.5 + // Divide the maximum u64 value by 0.5. This should overflow. + fixed_point32::divide_u64(18446744073709551615, f); +} + +#[test] +#[expected_failure(abort_code = fixed_point32::EMULTIPLICATION)] +fun multiply_overflow_small_multiplier() { + let f = fixed_point32::create_from_rational(3, 2); // 1.5 + // Multiply the maximum u64 value by 1.5. This should overflow. + fixed_point32::multiply_u64(18446744073709551615, f); +} + +#[test] +#[expected_failure(abort_code = fixed_point32::EMULTIPLICATION)] +fun multiply_overflow_large_multiplier() { + let f = fixed_point32::create_from_raw_value(18446744073709551615); + // Multiply 2^33 by the maximum fixed-point value. This should overflow. + fixed_point32::multiply_u64(8589934592, f); +} + +#[test] +fun exact_multiply() { + let f = fixed_point32::create_from_rational(3, 4); // 0.75 + let nine = fixed_point32::multiply_u64(12, f); // 12 * 0.75 + assert!(nine == 9); +} + +#[test] +fun exact_divide() { + let f = fixed_point32::create_from_rational(3, 4); // 0.75 + let twelve = fixed_point32::divide_u64(9, f); // 9 / 0.75 + assert!(twelve == 12); +} + +#[test] +fun multiply_truncates() { + let f = fixed_point32::create_from_rational(1, 3); // 0.333... + let not_three = fixed_point32::multiply_u64(9, copy f); // 9 * 0.333... + // multiply_u64 does NOT round -- it truncates -- so values that + // are not perfectly representable in binary may be off by one. + assert!(not_three == 2); + + // Try again with a fraction slightly larger than 1/3. + let f = fixed_point32::create_from_raw_value(f.get_raw_value() + 1); + let three = fixed_point32::multiply_u64(9, f); + assert!(three == 3); +} + +#[test] +fun create_from_rational_max_numerator_denominator() { + // Test creating a 1.0 fraction from the maximum u64 value. + let f = fixed_point32::create_from_rational(18446744073709551615, 18446744073709551615); + let one = f.get_raw_value(); + assert!(one == 4294967296); // 0x1.00000000 } diff --git a/crates/sui-framework/packages/move-stdlib/tests/hash_tests.move b/crates/sui-framework/packages/move-stdlib/tests/hash_tests.move index 8b309c30e1f75..b5e493eb8efcb 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/hash_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/hash_tests.move @@ -4,20 +4,20 @@ // SPDX-License-Identifier: Apache-2.0 #[test_only] -module std::hash_tests { - use std::hash; +module std::hash_tests; - #[test] - fun sha2_256_expected_hash() { - let input = x"616263"; - let expected_output = x"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"; - assert!(hash::sha2_256(input) == expected_output); - } +use std::hash; - #[test] - fun sha3_256_expected_hash() { - let input = x"616263"; - let expected_output = x"3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"; - assert!(hash::sha3_256(input) == expected_output); - } +#[test] +fun sha2_256_expected_hash() { + let input = x"616263"; + let expected_output = x"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"; + assert!(hash::sha2_256(input) == expected_output); +} + +#[test] +fun sha3_256_expected_hash() { + let input = x"616263"; + let expected_output = x"3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"; + assert!(hash::sha3_256(input) == expected_output); } diff --git a/crates/sui-framework/packages/move-stdlib/tests/integer_tests.move b/crates/sui-framework/packages/move-stdlib/tests/integer_tests.move index e4a8da1f0bf34..9456cd095d035 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/integer_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/integer_tests.move @@ -3,300 +3,295 @@ // helpers for integer tests #[test_only] -module std::integer_tests { - use std::unit_test::assert_eq; - - public(package) macro fun cases($max: _, $cases: vector<_>, $f: |_, _, _|) { - let mut cases = $cases; - let max_pred = $max - 1; - while (!cases.is_empty()) { - let case = cases.pop_back(); - let case_pred = case.max(1) - 1; - let case_succ = case.min(max_pred) + 1; - $f(case_pred, case, case_succ); - } +module std::integer_tests; + +use std::unit_test::assert_eq; + +public(package) macro fun cases($max: _, $cases: vector<_>, $f: |_, _, _|) { + let mut cases = $cases; + let max_pred = $max - 1; + while (!cases.is_empty()) { + let case = cases.pop_back(); + let case_pred = case.max(1) - 1; + let case_succ = case.min(max_pred) + 1; + $f(case_pred, case, case_succ); } +} - public(package) macro fun test_bitwise_not($max: _, $cases: vector<_>) { - let max = $max; - let cases = $cases; - assert_eq!(max.bitwise_not(), 0); - cases!(max, cases, |case_pred, case, case_succ| { - assert_eq!(case_pred.bitwise_not().bitwise_not(), case_pred); - assert_eq!(case_pred.bitwise_not() | case_pred, max); - assert_eq!(case_pred.bitwise_not() ^ case_pred, max); - assert_eq!(case_pred.bitwise_not() & case_pred, 0); - - assert_eq!(case.bitwise_not().bitwise_not(), case); - assert_eq!(case.bitwise_not() | case, max); - assert_eq!(case.bitwise_not() ^ case, max); - assert_eq!(case.bitwise_not() & case, 0); - - assert_eq!(case_succ.bitwise_not().bitwise_not(), case_succ); - assert_eq!(case_succ.bitwise_not() | case_succ, max); - assert_eq!(case_succ.bitwise_not() ^ case_succ, max); - assert_eq!(case_succ.bitwise_not() & case_succ, 0); - }) - } +public(package) macro fun test_bitwise_not($max: _, $cases: vector<_>) { + let max = $max; + let cases = $cases; + assert_eq!(max.bitwise_not(), 0); + cases!(max, cases, |case_pred, case, case_succ| { + assert_eq!(case_pred.bitwise_not().bitwise_not(), case_pred); + assert_eq!(case_pred.bitwise_not() | case_pred, max); + assert_eq!(case_pred.bitwise_not() ^ case_pred, max); + assert_eq!(case_pred.bitwise_not() & case_pred, 0); + + assert_eq!(case.bitwise_not().bitwise_not(), case); + assert_eq!(case.bitwise_not() | case, max); + assert_eq!(case.bitwise_not() ^ case, max); + assert_eq!(case.bitwise_not() & case, 0); + + assert_eq!(case_succ.bitwise_not().bitwise_not(), case_succ); + assert_eq!(case_succ.bitwise_not() | case_succ, max); + assert_eq!(case_succ.bitwise_not() ^ case_succ, max); + assert_eq!(case_succ.bitwise_not() & case_succ, 0); + }) +} - public(package) macro fun test_max($max: _, $cases: vector<_>) { - let max = $max; - let cases = $cases; - assert_eq!(max.max(max), max); - cases!(max, cases, |case_pred, case, case_succ| { - assert_eq!(max.max(case), max); - assert_eq!(case.max(max), max); - assert_eq!(case.max(case), case); - assert_eq!(case_pred.max(case), case); - assert_eq!(case_succ.max(case), case_succ); - }) - } +public(package) macro fun test_max($max: _, $cases: vector<_>) { + let max = $max; + let cases = $cases; + assert_eq!(max.max(max), max); + cases!(max, cases, |case_pred, case, case_succ| { + assert_eq!(max.max(case), max); + assert_eq!(case.max(max), max); + assert_eq!(case.max(case), case); + assert_eq!(case_pred.max(case), case); + assert_eq!(case_succ.max(case), case_succ); + }) +} - public(package) macro fun test_min($max: _, $cases: vector<_>) { - let max = $max; - let cases = $cases; - assert_eq!(max.min(max), max); - cases!(max, cases, |case_pred, case, case_succ| { - assert_eq!(max.min(case), case); - assert_eq!(case.min(max), case); - assert_eq!(case.min(case), case); - assert_eq!(case_pred.min(case), case_pred); - assert_eq!(case_succ.min(case), case); - }) - } +public(package) macro fun test_min($max: _, $cases: vector<_>) { + let max = $max; + let cases = $cases; + assert_eq!(max.min(max), max); + cases!(max, cases, |case_pred, case, case_succ| { + assert_eq!(max.min(case), case); + assert_eq!(case.min(max), case); + assert_eq!(case.min(case), case); + assert_eq!(case_pred.min(case), case_pred); + assert_eq!(case_succ.min(case), case); + }) +} - public(package) macro fun test_diff($max: _, $cases: vector<_>) { - let max = $max; - let cases = $cases; - assert_eq!(max.diff(max), 0); - cases!(max, cases, |case_pred, case, case_succ| { - assert_eq!(max.diff(case), max - case); - assert_eq!(case.diff(max), max - case); - assert_eq!(case.diff(case), 0); - assert_eq!(case_pred.diff(case), case - case_pred); - assert_eq!(case.diff(case_pred), case - case_pred); - assert_eq!(case_succ.diff(case), case_succ - case); - assert_eq!(case.diff(case_succ), case_succ - case); - }) - } +public(package) macro fun test_diff($max: _, $cases: vector<_>) { + let max = $max; + let cases = $cases; + assert_eq!(max.diff(max), 0); + cases!(max, cases, |case_pred, case, case_succ| { + assert_eq!(max.diff(case), max - case); + assert_eq!(case.diff(max), max - case); + assert_eq!(case.diff(case), 0); + assert_eq!(case_pred.diff(case), case - case_pred); + assert_eq!(case.diff(case_pred), case - case_pred); + assert_eq!(case_succ.diff(case), case_succ - case); + assert_eq!(case.diff(case_succ), case_succ - case); + }) +} - public(package) macro fun check_div_round($x: _, $y: _) { - let x = $x; - let y = $y; - if (y == 0) return; - assert_eq!(x.divide_and_round_up(y), (x / y) + (x % y).min(1)); - } +public(package) macro fun check_div_round($x: _, $y: _) { + let x = $x; + let y = $y; + if (y == 0) return; + assert_eq!(x.divide_and_round_up(y), (x / y) + (x % y).min(1)); +} - public(package) macro fun test_divide_and_round_up($max: _, $cases: vector<_>) { - let max = $max; - let cases = $cases; - assert_eq!(max.divide_and_round_up(max), 1); - check_div_round!(max, max); - cases!(max, cases, |case_pred, case, case_succ| { - check_div_round!(max, case); - check_div_round!(case, max); - check_div_round!(case, case); - check_div_round!(case_pred, case); - check_div_round!(case, case_pred); - check_div_round!(case_succ, case); - check_div_round!(case, case_succ); - }) - } +public(package) macro fun test_divide_and_round_up($max: _, $cases: vector<_>) { + let max = $max; + let cases = $cases; + assert_eq!(max.divide_and_round_up(max), 1); + check_div_round!(max, max); + cases!(max, cases, |case_pred, case, case_succ| { + check_div_round!(max, case); + check_div_round!(case, max); + check_div_round!(case, case); + check_div_round!(case_pred, case); + check_div_round!(case, case_pred); + check_div_round!(case_succ, case); + check_div_round!(case, case_succ); + }) +} - public(package) macro fun slow_pow($base: _, $exp: u8): _ { - let base = $base; - let mut exp = $exp; - let mut result = 1; - while (exp > 0) { - result = result * base; - exp = exp - 1; - }; - result - } +public(package) macro fun slow_pow($base: _, $exp: u8): _ { + let base = $base; + let mut exp = $exp; + let mut result = 1; + while (exp > 0) { + result = result * base; + exp = exp - 1; + }; + result +} - public(package) macro fun test_pow<$T>($max: $T, $cases: vector<$T>) { - let max = $max; - let cases = $cases; - cases!(max, cases, |case_pred, case, case_succ| { - assert_eq!(case_pred.pow(0), 1); - assert_eq!(case_pred.pow(1), case_pred); - assert_eq!(case.pow(0), 1); - assert_eq!(case.pow(1), case); - assert_eq!(case_succ.pow(0), 1); - assert_eq!(case_succ.pow(1), case_succ); - }); - assert_eq!((0: $T).pow(2), 0); - assert_eq!((1: $T).pow(255), 1); - assert_eq!((2: $T).pow(7), slow_pow!((2: $T), 7)); - assert_eq!((3: $T).pow(5), slow_pow!((3: $T), 5)); - } +public(package) macro fun test_pow<$T>($max: $T, $cases: vector<$T>) { + let max = $max; + let cases = $cases; + cases!(max, cases, |case_pred, case, case_succ| { + assert_eq!(case_pred.pow(0), 1); + assert_eq!(case_pred.pow(1), case_pred); + assert_eq!(case.pow(0), 1); + assert_eq!(case.pow(1), case); + assert_eq!(case_succ.pow(0), 1); + assert_eq!(case_succ.pow(1), case_succ); + }); + assert_eq!((0: $T).pow(2), 0); + assert_eq!((1: $T).pow(255), 1); + assert_eq!((2: $T).pow(7), slow_pow!((2: $T), 7)); + assert_eq!((3: $T).pow(5), slow_pow!((3: $T), 5)); +} - public(package) macro fun test_sqrt<$T>( - $max: $T, - $bound_cases: vector<$T>, - $reflexive_cases: vector<$T>, - ) { - let max = $max; - let cases = $bound_cases; - // logical bounds cases - let max_sqrt = max.sqrt(); - cases!(max, cases, |case_pred, case, case_succ| { - let sqrt_pred = case_pred.sqrt(); - assert!(sqrt_pred * sqrt_pred <= case_pred); - let sqrt = case.sqrt(); - assert!(sqrt * sqrt <= case); - let sqrt_succ = case_succ.sqrt(); - assert!(sqrt_succ * sqrt_succ <= case_succ); - - if (sqrt_pred >= max_sqrt) return; - assert!((sqrt_pred + 1) * (sqrt_pred + 1) > case_pred); - - if (sqrt >= max_sqrt) return; - assert!((sqrt + 1) * (sqrt + 1) > case); - - if (sqrt_succ >= max_sqrt) return; - assert!((sqrt_succ + 1) * (sqrt_succ + 1) > case_succ); - }); - - // simple reflexive cases - let cases: vector<$T> = $reflexive_cases; - cases!(max, cases, |case_pred, case, case_succ| { - assert_eq!((case_pred * case_pred).sqrt(), case_pred); - assert_eq!((case * case).sqrt(), case); - assert_eq!((case_succ * case_succ).sqrt(), case_succ); - }); - - // test that the square of a non perfect square is the most recent square root perfect - // square, rounding down - let mut cases: vector<$T> = vector[2, 3, 4, 5, 6]; - while (!cases.is_empty()) { - let case = cases.pop_back(); - let prev = case - 1; - let square = case * case; - let prev_suare = prev * prev; - let mut i = prev_suare; - while (i < square) { - assert_eq!(i.sqrt(), prev); - i = i + 1; - } +public(package) macro fun test_sqrt<$T>( + $max: $T, + $bound_cases: vector<$T>, + $reflexive_cases: vector<$T>, +) { + let max = $max; + let cases = $bound_cases; + // logical bounds cases + let max_sqrt = max.sqrt(); + cases!(max, cases, |case_pred, case, case_succ| { + let sqrt_pred = case_pred.sqrt(); + assert!(sqrt_pred * sqrt_pred <= case_pred); + let sqrt = case.sqrt(); + assert!(sqrt * sqrt <= case); + let sqrt_succ = case_succ.sqrt(); + assert!(sqrt_succ * sqrt_succ <= case_succ); + + if (sqrt_pred >= max_sqrt) return; + assert!((sqrt_pred + 1) * (sqrt_pred + 1) > case_pred); + + if (sqrt >= max_sqrt) return; + assert!((sqrt + 1) * (sqrt + 1) > case); + + if (sqrt_succ >= max_sqrt) return; + assert!((sqrt_succ + 1) * (sqrt_succ + 1) > case_succ); + }); + + // simple reflexive cases + let cases: vector<$T> = $reflexive_cases; + cases!(max, cases, |case_pred, case, case_succ| { + assert_eq!((case_pred * case_pred).sqrt(), case_pred); + assert_eq!((case * case).sqrt(), case); + assert_eq!((case_succ * case_succ).sqrt(), case_succ); + }); + + // test that the square of a non perfect square is the most recent square root perfect + // square, rounding down + let mut cases: vector<$T> = vector[2, 3, 4, 5, 6]; + while (!cases.is_empty()) { + let case = cases.pop_back(); + let prev = case - 1; + let square = case * case; + let prev_suare = prev * prev; + let mut i = prev_suare; + while (i < square) { + assert_eq!(i.sqrt(), prev); + i = i + 1; } } +} - public(package) macro fun test_try_as_u8<$T>($max: $T) { - assert_eq!((0: $T).try_as_u8(), option::some(0)); - assert_eq!((1: $T).try_as_u8(), option::some(1)); - assert_eq!((0xFF: $T).try_as_u8(), option::some(0xFF)); - assert_eq!((0xFF + 1: $T).try_as_u8(), option::none()); - let max = $max; - assert_eq!(max.try_as_u8(), option::none()); - } - - public(package) macro fun test_try_as_u16<$T>($max: $T) { - assert_eq!((0: $T).try_as_u16(), option::some(0)); - assert_eq!((1: $T).try_as_u16(), option::some(1)); - assert_eq!((0xFFFF: $T).try_as_u16(), option::some(0xFFFF)); - assert_eq!((0xFFFF + 1: $T).try_as_u16(), option::none()); - let max = $max; - assert_eq!(max.try_as_u16(), option::none()); - } - - public(package) macro fun test_try_as_u32<$T>($max: $T) { - assert_eq!((0: $T).try_as_u32(), option::some(0)); - assert_eq!((1: $T).try_as_u32(), option::some(1)); - assert_eq!((0xFFFF_FFFF: $T).try_as_u32(), option::some(0xFFFF_FFFF)); - assert_eq!((0xFFFF_FFFF + 1: $T).try_as_u32(), option::none()); - let max = $max; - assert_eq!(max.try_as_u32(), option::none()); - } +public(package) macro fun test_try_as_u8<$T>($max: $T) { + assert_eq!((0: $T).try_as_u8(), option::some(0)); + assert_eq!((1: $T).try_as_u8(), option::some(1)); + assert_eq!((0xFF: $T).try_as_u8(), option::some(0xFF)); + assert_eq!((0xFF + 1: $T).try_as_u8(), option::none()); + let max = $max; + assert_eq!(max.try_as_u8(), option::none()); +} - public(package) macro fun test_try_as_u64<$T>($max: $T) { - assert_eq!((0: $T).try_as_u64(), option::some(0)); - assert_eq!((1: $T).try_as_u64(), option::some(1)); - assert_eq!((0xFFFF_FFFF_FFFF_FFFF: $T).try_as_u64(), option::some(0xFFFF_FFFF_FFFF_FFFF)); - assert_eq!((0xFFFF_FFFF_FFFF_FFFF + 1: $T).try_as_u64(), option::none()); - let max = $max; - assert_eq!(max.try_as_u64(), option::none()); - } +public(package) macro fun test_try_as_u16<$T>($max: $T) { + assert_eq!((0: $T).try_as_u16(), option::some(0)); + assert_eq!((1: $T).try_as_u16(), option::some(1)); + assert_eq!((0xFFFF: $T).try_as_u16(), option::some(0xFFFF)); + assert_eq!((0xFFFF + 1: $T).try_as_u16(), option::none()); + let max = $max; + assert_eq!(max.try_as_u16(), option::none()); +} - public(package) macro fun test_try_as_u128<$T>($max: $T) { - assert_eq!((0: $T).try_as_u128(), option::some(0)); - assert_eq!((1: $T).try_as_u128(), option::some(1)); - assert_eq!( - (0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $T).try_as_u128(), - option::some(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF) - ); - assert_eq!( - (0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF + 1: $T).try_as_u128(), - option::none() - ); - let max = $max; - assert_eq!(max.try_as_u128(), option::none()); - } +public(package) macro fun test_try_as_u32<$T>($max: $T) { + assert_eq!((0: $T).try_as_u32(), option::some(0)); + assert_eq!((1: $T).try_as_u32(), option::some(1)); + assert_eq!((0xFFFF_FFFF: $T).try_as_u32(), option::some(0xFFFF_FFFF)); + assert_eq!((0xFFFF_FFFF + 1: $T).try_as_u32(), option::none()); + let max = $max; + assert_eq!(max.try_as_u32(), option::none()); +} - public(package) macro fun sum_range<$T>($n: $T): $T { - let n = $n; - (n * (n + 1)) / 2 - } +public(package) macro fun test_try_as_u64<$T>($max: $T) { + assert_eq!((0: $T).try_as_u64(), option::some(0)); + assert_eq!((1: $T).try_as_u64(), option::some(1)); + assert_eq!((0xFFFF_FFFF_FFFF_FFFF: $T).try_as_u64(), option::some(0xFFFF_FFFF_FFFF_FFFF)); + assert_eq!((0xFFFF_FFFF_FFFF_FFFF + 1: $T).try_as_u64(), option::none()); + let max = $max; + assert_eq!(max.try_as_u64(), option::none()); +} - public(package) macro fun test_to_string<$T>() { - assert_eq!((0: $T).to_string(), b"0".to_string()); - assert_eq!((1: $T).to_string(), b"1".to_string()); - assert_eq!((10: $T).to_string(), b"10".to_string()); - assert_eq!((11: $T).to_string(), b"11".to_string()); - assert_eq!((100: $T).to_string(), b"100".to_string()); - assert_eq!((111: $T).to_string(), b"111".to_string()); - } +public(package) macro fun test_try_as_u128<$T>($max: $T) { + assert_eq!((0: $T).try_as_u128(), option::some(0)); + assert_eq!((1: $T).try_as_u128(), option::some(1)); + assert_eq!( + (0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF: $T).try_as_u128(), + option::some(0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF), + ); + assert_eq!((0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF + 1: $T).try_as_u128(), option::none()); + let max = $max; + assert_eq!(max.try_as_u128(), option::none()); +} - public(package) macro fun test_dos_case<$T>($case: $T) { - let case = $case; - let mut sum: $T = 0; - case.do!(|i| sum = sum + i); - assert_eq!(sum, sum_range!(case - 1)); +public(package) macro fun sum_range<$T>($n: $T): $T { + let n = $n; + (n * (n + 1)) / 2 +} - sum = 0; - case.do_eq!(|i| sum = sum + i); - assert_eq!(sum, sum_range!(case)); +public(package) macro fun test_to_string<$T>() { + assert_eq!((0: $T).to_string(), b"0".to_string()); + assert_eq!((1: $T).to_string(), b"1".to_string()); + assert_eq!((10: $T).to_string(), b"10".to_string()); + assert_eq!((11: $T).to_string(), b"11".to_string()); + assert_eq!((100: $T).to_string(), b"100".to_string()); + assert_eq!((111: $T).to_string(), b"111".to_string()); +} - let half = case / 2; +public(package) macro fun test_dos_case<$T>($case: $T) { + let case = $case; + let mut sum: $T = 0; + case.do!(|i| sum = sum + i); + assert_eq!(sum, sum_range!(case - 1)); - sum = 0; - half.range_do!(case, |i| sum = sum + i); - assert_eq!(sum, sum_range!(case - 1) - sum_range!(half - 1)); + sum = 0; + case.do_eq!(|i| sum = sum + i); + assert_eq!(sum, sum_range!(case)); - sum = 0; - half.range_do_eq!(case, |i| sum = sum + i); - assert_eq!(sum, sum_range!(case) - sum_range!(half - 1)); - } + let half = case / 2; - public(package) macro fun test_dos<$T>($max: $T, $cases: vector<$T>) { - let max = $max; - let cases = $cases; - // test bounds/invalid ranges - (0: $T).do!(|_| assert!(false)); - cases!(max, cases, |case_pred, case, case_succ| { - if (case == 0) return; - case.range_do!(0, |_| assert!(false)); - case.range_do_eq!(0, |_| assert!(false)); - - if (case == max) return; - case.range_do!(case_pred, |_| assert!(false)); - case_succ.range_do!(case, |_| assert!(false)); - case.range_do_eq!(case_pred, |_| assert!(false)); - case_succ.range_do_eq!(case, |_| assert!(false)); - }); - - // test upper bound being max - let max_pred = max - 1; - max_pred.range_do_eq!(max, |_| ()); - - // test iteration numbers - let cases: vector<$T> = vector[3, 5, 8, 11, 14]; - cases!(max, cases, |case_pred, case, case_succ| { - test_dos_case!(case_pred); - test_dos_case!(case); - test_dos_case!(case_succ); - }); - } + sum = 0; + half.range_do!(case, |i| sum = sum + i); + assert_eq!(sum, sum_range!(case - 1) - sum_range!(half - 1)); + sum = 0; + half.range_do_eq!(case, |i| sum = sum + i); + assert_eq!(sum, sum_range!(case) - sum_range!(half - 1)); +} +public(package) macro fun test_dos<$T>($max: $T, $cases: vector<$T>) { + let max = $max; + let cases = $cases; + // test bounds/invalid ranges + (0: $T).do!(|_| assert!(false)); + cases!(max, cases, |case_pred, case, case_succ| { + if (case == 0) return; + case.range_do!(0, |_| assert!(false)); + case.range_do_eq!(0, |_| assert!(false)); + + if (case == max) return; + case.range_do!(case_pred, |_| assert!(false)); + case_succ.range_do!(case, |_| assert!(false)); + case.range_do_eq!(case_pred, |_| assert!(false)); + case_succ.range_do_eq!(case, |_| assert!(false)); + }); + + // test upper bound being max + let max_pred = max - 1; + max_pred.range_do_eq!(max, |_| ()); + + // test iteration numbers + let cases: vector<$T> = vector[3, 5, 8, 11, 14]; + cases!(max, cases, |case_pred, case, case_succ| { + test_dos_case!(case_pred); + test_dos_case!(case); + test_dos_case!(case_succ); + }); } diff --git a/crates/sui-framework/packages/move-stdlib/tests/option_tests.move b/crates/sui-framework/packages/move-stdlib/tests/option_tests.move index 18c02dcee2218..0fef1546ed30e 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/option_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/option_tests.move @@ -4,277 +4,277 @@ // SPDX-License-Identifier: Apache-2.0 #[test_only] -module std::option_tests { - #[test] - fun option_none_is_none() { - let none = option::none(); - assert!(none.is_none()); - assert!(!none.is_some()); - } - - #[test] - fun option_some_is_some() { - let some = option::some(5); - assert!(!some.is_none()); - assert!(some.is_some()); - } - - #[test] - fun option_contains() { - let none = option::none(); - let some = option::some(5); - let some_other = option::some(6); - assert!(some.contains(&5)); - assert!(some_other.contains(&6)); - assert!(!none.contains(&5)); - assert!(!some_other.contains(&5)); - } - - #[test] - fun option_borrow_some() { - let some = option::some(5); - let some_other = option::some(6); - assert!(*some.borrow() == 5); - assert!(*some_other.borrow() == 6); - } - - #[test] - #[expected_failure(abort_code = option::EOPTION_NOT_SET)] - fun option_borrow_none() { - option::none().borrow(); - } - - #[test] - fun borrow_mut_some() { - let mut some = option::some(1); - let ref = some.borrow_mut(); - *ref = 10; - assert!(*some.borrow() == 10); - } - - #[test] - #[expected_failure(abort_code = option::EOPTION_NOT_SET)] - fun borrow_mut_none() { - option::none().borrow_mut(); - } - - #[test] - fun borrow_with_default() { - let none = option::none(); - let some = option::some(5); - assert!(*some.borrow_with_default(&7) == 5); - assert!(*none.borrow_with_default(&7) == 7); - } - - #[test] - fun get_with_default() { - let none = option::none(); - let some = option::some(5); - assert!(option::get_with_default(&some, 7) == 5); - assert!(option::get_with_default(&none, 7) == 7); - } - - #[test] - fun extract_some() { - let mut opt = option::some(1); - assert!(opt.extract() == 1); - assert!(opt.is_none()); - } - - #[test] - #[expected_failure(abort_code = option::EOPTION_NOT_SET)] - fun extract_none() { - option::none().extract(); - } - - #[test] - fun swap_some() { - let mut some = option::some(5); - assert!(some.swap(1) == 5); - assert!(*some.borrow() == 1); - } - - #[test] - fun swap_or_fill_some() { - let mut some = option::some(5); - assert!(some.swap_or_fill(1) == option::some(5)); - assert!(*some.borrow() == 1); - } - - #[test] - fun swap_or_fill_none() { - let mut none = option::none(); - assert!(none.swap_or_fill(1) == option::none()); - assert!(*none.borrow() == 1); - } - - #[test] - #[expected_failure(abort_code = option::EOPTION_NOT_SET)] - fun swap_none() { - option::none().swap(1); - } - - #[test] - fun fill_none() { - let mut none = option::none(); - none.fill(3); - assert!(none.is_some()); - assert!(*none.borrow() == 3); - } - - #[test] - #[expected_failure(abort_code = option::EOPTION_IS_SET)] - fun fill_some() { - option::some(3).fill(0); - } - - #[test] - fun destroy_with_default() { - assert!(option::none().destroy_with_default(4) == 4); - assert!(option::some(4).destroy_with_default(5) == 4); - } - - #[test] - fun destroy_some() { - assert!(option::some(4).destroy_some() == 4); - } - - #[test] - #[expected_failure(abort_code = option::EOPTION_NOT_SET)] - fun destroy_some_none() { - option::none().destroy_some(); - } - - #[test] - fun destroy_none() { - option::none().destroy_none(); - } - - #[test] - #[expected_failure(abort_code = option::EOPTION_IS_SET)] - fun destroy_none_some() { - option::some(0).destroy_none(); - } - - #[test] - fun into_vec_some() { - let mut v = option::some(0).to_vec(); - assert!(v.length() == 1); - let x = v.pop_back(); - assert!(x == 0); - } - - #[test] - fun into_vec_none() { - let v: vector = option::none().to_vec(); - assert!(v.is_empty()); - } - - // === Macros === - - public struct NoDrop {} - - #[test] - fun do_destroy() { - let mut counter = 0; - option::some(5).destroy!(|x| counter = x); - option::some(10).do!(|x| counter = counter + x); - - assert!(counter == 15); - - let some = option::some(NoDrop {}); - let none = option::none(); - - some.do!(|el| { let NoDrop {} = el; }); - none.do!(|el| { let NoDrop {} = el; }); - } - - #[test] - fun do_ref_mut() { - let mut counter = 0; - let mut opt = option::some(5); - opt.do_mut!(|x| *x = 100); - opt.do_ref!(|x| counter = *x); - - assert!(counter == 100); - } - - #[test] - fun map_map_ref() { - assert!(option::some(5).map!(|x| vector[x]) == option::some(vector[5])); - assert!(option::some(5).map_ref!(|x| vector[*x]) == option::some(vector[5])); - assert!(option::none().map!(|x| vector[x]) == option::none()); - assert!(option::none().map_ref!(|x| vector[*x]) == option::none()); - } - - #[test] - fun map_no_drop() { - let none = option::none().map!(|el| { - let NoDrop {} = el; - 100u64 - }); - let some = option::some(NoDrop {}).map!(|el| { - let NoDrop {} = el; - 100u64 - }); - - assert!(none == option::none()); - assert!(some == option::some(100)); - } - - #[test] - fun or_no_drop() { - let none = option::none().or!(option::some(NoDrop {})); - let some = option::some(NoDrop {}).or!(option::some(NoDrop {})); - - assert!(none.is_some()); - assert!(some.is_some()); - - let NoDrop {} = none.destroy_some(); - let NoDrop {} = some.destroy_some(); - } - - #[test] - fun and_no_drop() { - let none = option::none().and!(|e| { - let NoDrop {} = e; - option::some(100) - }); - - let some = option::some(NoDrop {}).and!(|e| { - let NoDrop {} = e; - option::some(100) - }); - - assert!(some == option::some(100)); - assert!(none == option::none()); - } - - #[test] - fun filter() { - assert!(option::some(5).filter!(|x| *x == 5) == option::some(5)); - assert!(option::some(5).filter!(|x| *x == 6) == option::none()); - } - - #[test] - fun is_some_and() { - assert!(option::some(5).is_some_and!(|x| *x == 5)); - assert!(!option::some(5).is_some_and!(|x| *x == 6)); - assert!(!option::none().is_some_and!(|x| *x == 5)); - } - - #[test] - fun destroy_or() { - assert!(option::none().destroy_or!(10) == 10); - assert!(option::some(5).destroy_or!(10) == 5); - } - - #[test] - fun destroy_or_no_drop() { - let none = option::none().destroy_or!(NoDrop {}); - let some = option::some(NoDrop {}).destroy_or!(NoDrop {}); - - let NoDrop {} = some; - let NoDrop {} = none; - } +module std::option_tests; + +#[test] +fun option_none_is_none() { + let none = option::none(); + assert!(none.is_none()); + assert!(!none.is_some()); +} + +#[test] +fun option_some_is_some() { + let some = option::some(5); + assert!(!some.is_none()); + assert!(some.is_some()); +} + +#[test] +fun option_contains() { + let none = option::none(); + let some = option::some(5); + let some_other = option::some(6); + assert!(some.contains(&5)); + assert!(some_other.contains(&6)); + assert!(!none.contains(&5)); + assert!(!some_other.contains(&5)); +} + +#[test] +fun option_borrow_some() { + let some = option::some(5); + let some_other = option::some(6); + assert!(*some.borrow() == 5); + assert!(*some_other.borrow() == 6); +} + +#[test] +#[expected_failure(abort_code = option::EOPTION_NOT_SET)] +fun option_borrow_none() { + option::none().borrow(); +} + +#[test] +fun borrow_mut_some() { + let mut some = option::some(1); + let ref = some.borrow_mut(); + *ref = 10; + assert!(*some.borrow() == 10); +} + +#[test] +#[expected_failure(abort_code = option::EOPTION_NOT_SET)] +fun borrow_mut_none() { + option::none().borrow_mut(); +} + +#[test] +fun borrow_with_default() { + let none = option::none(); + let some = option::some(5); + assert!(*some.borrow_with_default(&7) == 5); + assert!(*none.borrow_with_default(&7) == 7); +} + +#[test] +fun get_with_default() { + let none = option::none(); + let some = option::some(5); + assert!(option::get_with_default(&some, 7) == 5); + assert!(option::get_with_default(&none, 7) == 7); +} + +#[test] +fun extract_some() { + let mut opt = option::some(1); + assert!(opt.extract() == 1); + assert!(opt.is_none()); +} + +#[test] +#[expected_failure(abort_code = option::EOPTION_NOT_SET)] +fun extract_none() { + option::none().extract(); +} + +#[test] +fun swap_some() { + let mut some = option::some(5); + assert!(some.swap(1) == 5); + assert!(*some.borrow() == 1); +} + +#[test] +fun swap_or_fill_some() { + let mut some = option::some(5); + assert!(some.swap_or_fill(1) == option::some(5)); + assert!(*some.borrow() == 1); +} + +#[test] +fun swap_or_fill_none() { + let mut none = option::none(); + assert!(none.swap_or_fill(1) == option::none()); + assert!(*none.borrow() == 1); +} + +#[test] +#[expected_failure(abort_code = option::EOPTION_NOT_SET)] +fun swap_none() { + option::none().swap(1); +} + +#[test] +fun fill_none() { + let mut none = option::none(); + none.fill(3); + assert!(none.is_some()); + assert!(*none.borrow() == 3); +} + +#[test] +#[expected_failure(abort_code = option::EOPTION_IS_SET)] +fun fill_some() { + option::some(3).fill(0); +} + +#[test] +fun destroy_with_default() { + assert!(option::none().destroy_with_default(4) == 4); + assert!(option::some(4).destroy_with_default(5) == 4); +} + +#[test] +fun destroy_some() { + assert!(option::some(4).destroy_some() == 4); +} + +#[test] +#[expected_failure(abort_code = option::EOPTION_NOT_SET)] +fun destroy_some_none() { + option::none().destroy_some(); +} + +#[test] +fun destroy_none() { + option::none().destroy_none(); +} + +#[test] +#[expected_failure(abort_code = option::EOPTION_IS_SET)] +fun destroy_none_some() { + option::some(0).destroy_none(); +} + +#[test] +fun into_vec_some() { + let mut v = option::some(0).to_vec(); + assert!(v.length() == 1); + let x = v.pop_back(); + assert!(x == 0); +} + +#[test] +fun into_vec_none() { + let v: vector = option::none().to_vec(); + assert!(v.is_empty()); +} + +// === Macros === + +public struct NoDrop {} + +#[test] +fun do_destroy() { + let mut counter = 0; + option::some(5).destroy!(|x| counter = x); + option::some(10).do!(|x| counter = counter + x); + + assert!(counter == 15); + + let some = option::some(NoDrop {}); + let none = option::none(); + + some.do!(|el| { let NoDrop {} = el; }); + none.do!(|el| { let NoDrop {} = el; }); +} + +#[test] +fun do_ref_mut() { + let mut counter = 0; + let mut opt = option::some(5); + opt.do_mut!(|x| *x = 100); + opt.do_ref!(|x| counter = *x); + + assert!(counter == 100); +} + +#[test] +fun map_map_ref() { + assert!(option::some(5).map!(|x| vector[x]) == option::some(vector[5])); + assert!(option::some(5).map_ref!(|x| vector[*x]) == option::some(vector[5])); + assert!(option::none().map!(|x| vector[x]) == option::none()); + assert!(option::none().map_ref!(|x| vector[*x]) == option::none()); +} + +#[test] +fun map_no_drop() { + let none = option::none().map!(|el| { + let NoDrop {} = el; + 100u64 + }); + let some = option::some(NoDrop {}).map!(|el| { + let NoDrop {} = el; + 100u64 + }); + + assert!(none == option::none()); + assert!(some == option::some(100)); +} + +#[test] +fun or_no_drop() { + let none = option::none().or!(option::some(NoDrop {})); + let some = option::some(NoDrop {}).or!(option::some(NoDrop {})); + + assert!(none.is_some()); + assert!(some.is_some()); + + let NoDrop {} = none.destroy_some(); + let NoDrop {} = some.destroy_some(); +} + +#[test] +fun and_no_drop() { + let none = option::none().and!(|e| { + let NoDrop {} = e; + option::some(100) + }); + + let some = option::some(NoDrop {}).and!(|e| { + let NoDrop {} = e; + option::some(100) + }); + + assert!(some == option::some(100)); + assert!(none == option::none()); +} + +#[test] +fun filter() { + assert!(option::some(5).filter!(|x| *x == 5) == option::some(5)); + assert!(option::some(5).filter!(|x| *x == 6) == option::none()); +} + +#[test] +fun is_some_and() { + assert!(option::some(5).is_some_and!(|x| *x == 5)); + assert!(!option::some(5).is_some_and!(|x| *x == 6)); + assert!(!option::none().is_some_and!(|x| *x == 5)); +} + +#[test] +fun destroy_or() { + assert!(option::none().destroy_or!(10) == 10); + assert!(option::some(5).destroy_or!(10) == 5); +} + +#[test] +fun destroy_or_no_drop() { + let none = option::none().destroy_or!(NoDrop {}); + let some = option::some(NoDrop {}).destroy_or!(NoDrop {}); + + let NoDrop {} = some; + let NoDrop {} = none; } diff --git a/crates/sui-framework/packages/move-stdlib/tests/string_tests.move b/crates/sui-framework/packages/move-stdlib/tests/string_tests.move index bf99e678860d0..1c3f479f6cb16 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/string_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/string_tests.move @@ -4,85 +4,85 @@ // SPDX-License-Identifier: Apache-2.0 #[test_only] -module std::string_tests { - use std::string; +module std::string_tests; - #[test] - fun test_valid_utf8() { - let sparkle_heart = vector[240, 159, 146, 150]; - let s = sparkle_heart.to_string(); - assert!(s.length() == 4); - } +use std::string; - #[test] - #[expected_failure(abort_code = string::EInvalidUTF8)] - fun test_invalid_utf8() { - let no_sparkle_heart = vector[0, 159, 146, 150]; - let s = no_sparkle_heart.to_string(); - assert!(s.length() == 1); - } +#[test] +fun test_valid_utf8() { + let sparkle_heart = vector[240, 159, 146, 150]; + let s = sparkle_heart.to_string(); + assert!(s.length() == 4); +} - #[test] - fun test_substring() { - let s = b"abcd".to_string(); - let sub = s.substring(2, 4); - assert!(sub == b"cd".to_string()) - } +#[test] +#[expected_failure(abort_code = string::EInvalidUTF8)] +fun test_invalid_utf8() { + let no_sparkle_heart = vector[0, 159, 146, 150]; + let s = no_sparkle_heart.to_string(); + assert!(s.length() == 1); +} - #[test] - #[expected_failure(abort_code = string::EInvalidIndex)] - fun test_substring_invalid_boundary() { - let sparkle_heart = vector[240, 159, 146, 150]; - let s = sparkle_heart.to_string(); - let _sub = s.substring(1, 4); - } +#[test] +fun test_substring() { + let s = b"abcd".to_string(); + let sub = s.substring(2, 4); + assert!(sub == b"cd".to_string()) +} - #[test] - #[expected_failure(abort_code = string::EInvalidIndex)] - fun test_substring_invalid_index() { - let s = b"abcd".to_string(); - let _sub = s.substring(4, 5); - } +#[test] +#[expected_failure(abort_code = string::EInvalidIndex)] +fun test_substring_invalid_boundary() { + let sparkle_heart = vector[240, 159, 146, 150]; + let s = sparkle_heart.to_string(); + let _sub = s.substring(1, 4); +} - #[test] - fun test_substring_empty() { - let s = b"abcd".to_string(); - let sub = s.substring(4, 4); - assert!(sub.is_empty()) - } +#[test] +#[expected_failure(abort_code = string::EInvalidIndex)] +fun test_substring_invalid_index() { + let s = b"abcd".to_string(); + let _sub = s.substring(4, 5); +} - #[test] - fun test_index_of() { - let s = b"abcd".to_string(); - let r = b"bc".to_string(); - let p = s.index_of(&r); - assert!(p == 1) - } +#[test] +fun test_substring_empty() { + let s = b"abcd".to_string(); + let sub = s.substring(4, 4); + assert!(sub.is_empty()) +} - #[test] - fun test_index_of_fail() { - let s = b"abcd".to_string(); - let r = b"bce".to_string(); - let p = s.index_of(&r); - assert!(p == 4) - } +#[test] +fun test_index_of() { + let s = b"abcd".to_string(); + let r = b"bc".to_string(); + let p = s.index_of(&r); + assert!(p == 1) +} - #[test] - fun test_append() { - let mut s = b"abcd".to_string(); - s.append(b"ef".to_string()); - assert!(s == b"abcdef".to_string()) - } +#[test] +fun test_index_of_fail() { + let s = b"abcd".to_string(); + let r = b"bce".to_string(); + let p = s.index_of(&r); + assert!(p == 4) +} - #[test] - fun test_insert() { - let mut s = b"abcd".to_string(); - s.insert(1, b"xy".to_string()); - assert!(s == b"axybcd".to_string()) - } +#[test] +fun test_append() { + let mut s = b"abcd".to_string(); + s.append(b"ef".to_string()); + assert!(s == b"abcdef".to_string()) +} + +#[test] +fun test_insert() { + let mut s = b"abcd".to_string(); + s.insert(1, b"xy".to_string()); + assert!(s == b"axybcd".to_string()) +} - #[test] - fun test_into_bytes() { - assert!(b"abcd" == b"abcd".to_string().into_bytes()) - } +#[test] +fun test_into_bytes() { + assert!(b"abcd" == b"abcd".to_string().into_bytes()) } diff --git a/crates/sui-framework/packages/move-stdlib/tests/type_name_tests.move b/crates/sui-framework/packages/move-stdlib/tests/type_name_tests.move index 256c31c901fa8..6a2cf432bb78a 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/type_name_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/type_name_tests.move @@ -4,101 +4,127 @@ // SPDX-License-Identifier: Apache-2.0 // note: intentionally using 0xa here to test non-0x1 module addresses -module 0xA::type_name_tests { - #[test_only] - use std::type_name::{get, into_string, is_primitive, get_address, get_module}; - #[test_only] - use std::ascii::string; - - public struct TestStruct {} - - public struct TestGenerics { } - - public struct TestMultiGenerics { } - - #[test] - fun test_primitive_types() { - assert!(into_string(get()) == string(b"u8")); - assert!(into_string(get()) == string(b"u16")); - assert!(into_string(get()) == string(b"u32")); - assert!(into_string(get()) == string(b"u64")); - assert!(into_string(get()) == string(b"u128")); - assert!(into_string(get()) == string(b"u256")); - assert!(into_string(get
()) == string(b"address")); - assert!(into_string(get>()) == string(b"vector")); - assert!(into_string(get>>()) == string(b"vector>")); - assert!(into_string(get>>()) == string(b"vector>")); - } - - #[test] - fun test_is_primitive() { - assert!(is_primitive(&get())); - assert!(is_primitive(&get())); - assert!(is_primitive(&get())); - assert!(is_primitive(&get())); - assert!(is_primitive(&get())); - assert!(is_primitive(&get())); - assert!(is_primitive(&get
())); - assert!(is_primitive(&get>())); - assert!(is_primitive(&get>>())); - assert!(is_primitive(&get>>())); - } - - // Note: these tests assume a 32 byte address length - #[test] - fun test_structs() { - assert!(into_string(get()) == string(b"000000000000000000000000000000000000000000000000000000000000000a::type_name_tests::TestStruct")); - assert!(into_string(get()) == string(b"0000000000000000000000000000000000000000000000000000000000000001::ascii::String")); - assert!(into_string(get>()) == string(b"0000000000000000000000000000000000000000000000000000000000000001::option::Option")); - assert!(into_string(get()) == string(b"0000000000000000000000000000000000000000000000000000000000000001::string::String")); - } - - // Note: these tests assume a 32 byte address length - #[test] - fun test_generics() { - assert!(into_string(get>()) == string(b"000000000000000000000000000000000000000000000000000000000000000a::type_name_tests::TestGenerics<0000000000000000000000000000000000000000000000000000000000000001::string::String>")); - assert!(into_string(get>>()) == string(b"vector<000000000000000000000000000000000000000000000000000000000000000a::type_name_tests::TestGenerics>")); - assert!(into_string(get>>()) == string(b"0000000000000000000000000000000000000000000000000000000000000001::option::Option<000000000000000000000000000000000000000000000000000000000000000a::type_name_tests::TestGenerics>")); - } - - // Note: these tests assume a 32 byte address length - #[test] - fun test_multi_generics() { - assert!(into_string(get>()) == string(b"000000000000000000000000000000000000000000000000000000000000000a::type_name_tests::TestMultiGenerics")); - assert!(into_string(get, TestGenerics>>()) == string(b"000000000000000000000000000000000000000000000000000000000000000a::type_name_tests::TestMultiGenerics,000000000000000000000000000000000000000000000000000000000000000a::type_name_tests::TestGenerics>")); - } - - #[test] - fun test_get_address() { - assert!(get_address(&get()) == string(b"0000000000000000000000000000000000000000000000000000000000000001")); - assert!(get_address(&get()) == string(b"000000000000000000000000000000000000000000000000000000000000000a")); - assert!(get_address(&get>()) == string(b"000000000000000000000000000000000000000000000000000000000000000a")); - } - - #[test] - fun test_get_module() { - assert!(get_module(&get()) == string(b"ascii")); - assert!(get_module(&get()) == string(b"type_name_tests")); - assert!(get_module(&get>()) == string(b"type_name_tests")); - } - - #[test, expected_failure(abort_code = std::type_name::ENonModuleType)] - fun test_get_address_aborts_with_primitive() { - get_address(&get()); - } - - #[test, expected_failure(abort_code = std::type_name::ENonModuleType)] - fun test_get_module_aborts_with_primitive() { - get_module(&get()); - } - - #[test, expected_failure(abort_code = std::type_name::ENonModuleType)] - fun test_get_address_aborts_with_primitive_generic() { - get_address(&get>()); - } - - #[test, expected_failure(abort_code = std::type_name::ENonModuleType)] - fun test_get_module_aborts_with_primitive_generic() { - get_module(&get>>()); - } +module 0xA::type_name_tests; + +#[test_only] +use std::type_name::{get, into_string, is_primitive, get_address, get_module}; +#[test_only] +use std::ascii::string; + +public struct TestStruct {} + +public struct TestGenerics {} + +public struct TestMultiGenerics {} + +#[test] +fun test_primitive_types() { + assert!(into_string(get()) == string(b"u8")); + assert!(into_string(get()) == string(b"u16")); + assert!(into_string(get()) == string(b"u32")); + assert!(into_string(get()) == string(b"u64")); + assert!(into_string(get()) == string(b"u128")); + assert!(into_string(get()) == string(b"u256")); + assert!(into_string(get
()) == string(b"address")); + assert!(into_string(get>()) == string(b"vector")); + assert!(into_string(get>>()) == string(b"vector>")); + assert!( + into_string(get>>()) == string(b"vector>"), + ); +} + +#[test] +fun test_is_primitive() { + assert!(is_primitive(&get())); + assert!(is_primitive(&get())); + assert!(is_primitive(&get())); + assert!(is_primitive(&get())); + assert!(is_primitive(&get())); + assert!(is_primitive(&get())); + assert!(is_primitive(&get
())); + assert!(is_primitive(&get>())); + assert!(is_primitive(&get>>())); + assert!(is_primitive(&get>>())); +} + +// Note: these tests assume a 32 byte address length +#[test] +fun test_structs() { + assert!( + into_string(get()) == string(b"000000000000000000000000000000000000000000000000000000000000000a::type_name_tests::TestStruct"), + ); + assert!( + into_string(get()) == string(b"0000000000000000000000000000000000000000000000000000000000000001::ascii::String"), + ); + assert!( + into_string(get>()) == string(b"0000000000000000000000000000000000000000000000000000000000000001::option::Option"), + ); + assert!( + into_string(get()) == string(b"0000000000000000000000000000000000000000000000000000000000000001::string::String"), + ); +} + +// Note: these tests assume a 32 byte address length +#[test] +fun test_generics() { + assert!( + into_string(get>()) == string(b"000000000000000000000000000000000000000000000000000000000000000a::type_name_tests::TestGenerics<0000000000000000000000000000000000000000000000000000000000000001::string::String>"), + ); + assert!( + into_string(get>>()) == string(b"vector<000000000000000000000000000000000000000000000000000000000000000a::type_name_tests::TestGenerics>"), + ); + assert!( + into_string(get>>()) == string(b"0000000000000000000000000000000000000000000000000000000000000001::option::Option<000000000000000000000000000000000000000000000000000000000000000a::type_name_tests::TestGenerics>"), + ); +} + +// Note: these tests assume a 32 byte address length +#[test] +fun test_multi_generics() { + assert!( + into_string(get>()) == string(b"000000000000000000000000000000000000000000000000000000000000000a::type_name_tests::TestMultiGenerics"), + ); + assert!( + into_string(get, TestGenerics>>()) == string(b"000000000000000000000000000000000000000000000000000000000000000a::type_name_tests::TestMultiGenerics,000000000000000000000000000000000000000000000000000000000000000a::type_name_tests::TestGenerics>"), + ); +} + +#[test] +fun test_get_address() { + assert!( + get_address(&get()) == string(b"0000000000000000000000000000000000000000000000000000000000000001"), + ); + assert!( + get_address(&get()) == string(b"000000000000000000000000000000000000000000000000000000000000000a"), + ); + assert!( + get_address(&get>()) == string(b"000000000000000000000000000000000000000000000000000000000000000a"), + ); +} + +#[test] +fun test_get_module() { + assert!(get_module(&get()) == string(b"ascii")); + assert!(get_module(&get()) == string(b"type_name_tests")); + assert!(get_module(&get>()) == string(b"type_name_tests")); +} + +#[test, expected_failure(abort_code = std::type_name::ENonModuleType)] +fun test_get_address_aborts_with_primitive() { + get_address(&get()); +} + +#[test, expected_failure(abort_code = std::type_name::ENonModuleType)] +fun test_get_module_aborts_with_primitive() { + get_module(&get()); +} + +#[test, expected_failure(abort_code = std::type_name::ENonModuleType)] +fun test_get_address_aborts_with_primitive_generic() { + get_address(&get>()); +} + +#[test, expected_failure(abort_code = std::type_name::ENonModuleType)] +fun test_get_module_aborts_with_primitive_generic() { + get_module(&get>>()); } diff --git a/crates/sui-framework/packages/move-stdlib/tests/u128_tests.move b/crates/sui-framework/packages/move-stdlib/tests/u128_tests.move index 70f6750cc8693..8a45b903596a1 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/u128_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/u128_tests.move @@ -2,111 +2,111 @@ // SPDX-License-Identifier: Apache-2.0 #[test_only] -module std::u128_tests { - use std::integer_tests; - use std::unit_test::assert_eq; - - const BIT_SIZE: u8 = 128; - const MAX: u128 = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF; - const MAX_PRED: u128 = MAX - 1; - - const CASES: vector = vector[ - 0, - 1, - 10, - 11, - 100, - 111, - 1 << (BIT_SIZE / 2 - 1), - (1 << (BIT_SIZE / 2 - 1)) + 1, - 1 << (BIT_SIZE - 1), - (1 << (BIT_SIZE - 1)) + 1, - MAX / 2, - (MAX / 2) + 1, - MAX_PRED, - MAX, - ]; - - #[test] - fun test_bitwise_not() { - integer_tests::test_bitwise_not!(MAX, CASES); - } - - #[test] - fun test_max() { - integer_tests::test_max!(MAX, CASES); - } - - #[test] - fun test_min() { - integer_tests::test_min!(MAX, CASES); - } - - #[test] - fun test_diff() { - integer_tests::test_diff!(MAX, CASES); - } - - #[test] - fun test_divide_and_round_up() { - integer_tests::test_divide_and_round_up!(MAX, CASES); - } - - #[test, expected_failure(arithmetic_error, location = std::u8)] - fun test_divide_and_round_up_error() { - 1u8.divide_and_round_up(0); - } - - #[test] - fun test_pow() { - integer_tests::test_pow!(MAX, CASES); - assert_eq!(2u128.pow(12), integer_tests::slow_pow!(2u128, 12)); - assert_eq!(3u128.pow(27), integer_tests::slow_pow!(3u128, 27)); - } - - #[test, expected_failure(arithmetic_error, location = std::u16)] - fun test_pow_overflow() { - 255u16.pow(255); - } - - #[test] - fun test_sqrt() { - let reflexive_cases = - vector[0, 2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 56, 59]; - integer_tests::test_sqrt!(MAX, CASES, reflexive_cases) - } - - #[test] - fun test_try_as_u8() { - integer_tests::test_try_as_u8!(MAX); - } - - #[test] - fun test_try_as_u16() { - integer_tests::test_try_as_u16!(MAX); - } - - #[test] - fun test_try_as_u32() { - integer_tests::test_try_as_u32!(MAX); - } - - #[test] - fun test_try_as_u64() { - integer_tests::test_try_as_u64!(MAX); - } - - #[test] - fun test_to_string() { - integer_tests::test_to_string!(); - assert_eq!((MAX / 2).to_string(), b"170141183460469231731687303715884105727".to_string()); - assert_eq!((MAX / 2 + 1).to_string(), b"170141183460469231731687303715884105728".to_string()); - assert_eq!(MAX_PRED.to_string(), b"340282366920938463463374607431768211454".to_string()); - assert_eq!(MAX.to_string(), b"340282366920938463463374607431768211455".to_string()); - } - - #[test] - fun test_dos() { - integer_tests::test_dos!(MAX, CASES); - } +module std::u128_tests; + +use std::{integer_tests, unit_test::assert_eq}; + +const BIT_SIZE: u8 = 128; +const MAX: u128 = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF; +const MAX_PRED: u128 = MAX - 1; + +const CASES: vector = vector[ + 0, + 1, + 10, + 11, + 100, + 111, + 1 << (BIT_SIZE / 2 - 1), + (1 << (BIT_SIZE / 2 - 1)) + 1, + 1 << (BIT_SIZE - 1), + (1 << (BIT_SIZE - 1)) + 1, + MAX / 2, + (MAX / 2) + 1, + MAX_PRED, + MAX, +]; + +#[test] +fun test_bitwise_not() { + integer_tests::test_bitwise_not!(MAX, CASES); +} + +#[test] +fun test_max() { + integer_tests::test_max!(MAX, CASES); +} + +#[test] +fun test_min() { + integer_tests::test_min!(MAX, CASES); +} + +#[test] +fun test_diff() { + integer_tests::test_diff!(MAX, CASES); +} + +#[test] +fun test_divide_and_round_up() { + integer_tests::test_divide_and_round_up!(MAX, CASES); +} + +#[test, expected_failure(arithmetic_error, location = std::u8)] +fun test_divide_and_round_up_error() { + 1u8.divide_and_round_up(0); +} + +#[test] +fun test_pow() { + integer_tests::test_pow!(MAX, CASES); + assert_eq!(2u128.pow(12), integer_tests::slow_pow!(2u128, 12)); + assert_eq!(3u128.pow(27), integer_tests::slow_pow!(3u128, 27)); +} + +#[test, expected_failure(arithmetic_error, location = std::u16)] +fun test_pow_overflow() { + 255u16.pow(255); +} + +#[test] +fun test_sqrt() { + // prettier-ignore + let reflexive_cases = + vector[0, 2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 56, 59]; + integer_tests::test_sqrt!(MAX, CASES, reflexive_cases) +} + +#[test] +fun test_try_as_u8() { + integer_tests::test_try_as_u8!(MAX); +} + +#[test] +fun test_try_as_u16() { + integer_tests::test_try_as_u16!(MAX); +} + +#[test] +fun test_try_as_u32() { + integer_tests::test_try_as_u32!(MAX); +} + +#[test] +fun test_try_as_u64() { + integer_tests::test_try_as_u64!(MAX); +} + +#[test] +fun test_to_string() { + integer_tests::test_to_string!(); + assert_eq!((MAX / 2).to_string(), b"170141183460469231731687303715884105727".to_string()); + assert_eq!((MAX / 2 + 1).to_string(), b"170141183460469231731687303715884105728".to_string()); + assert_eq!(MAX_PRED.to_string(), b"340282366920938463463374607431768211454".to_string()); + assert_eq!(MAX.to_string(), b"340282366920938463463374607431768211455".to_string()); +} + +#[test] +fun test_dos() { + integer_tests::test_dos!(MAX, CASES); } diff --git a/crates/sui-framework/packages/move-stdlib/tests/u16_tests.move b/crates/sui-framework/packages/move-stdlib/tests/u16_tests.move index 90419371e85e5..f6933a31fedc7 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/u16_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/u16_tests.move @@ -2,96 +2,96 @@ // SPDX-License-Identifier: Apache-2.0 #[test_only] -module std::u16_tests { - use std::integer_tests; - use std::unit_test::assert_eq; - - const BIT_SIZE: u8 = 16; - const MAX: u16 = 0xFFFF; - const MAX_PRED: u16 = MAX - 1; - - const CASES: vector = vector[ - 0, - 1, - 10, - 11, - 100, - 111, - 1 << (BIT_SIZE / 2 - 1), - (1 << (BIT_SIZE / 2 - 1)) + 1, - 1 << (BIT_SIZE - 1), - (1 << (BIT_SIZE - 1)) + 1, - MAX / 2, - (MAX / 2) + 1, - MAX_PRED, - MAX, - ]; - - #[test] - fun test_bitwise_not() { - integer_tests::test_bitwise_not!(MAX, CASES); - } - - #[test] - fun test_max() { - integer_tests::test_max!(MAX, CASES); - } - - #[test] - fun test_min() { - integer_tests::test_min!(MAX, CASES); - } - - #[test] - fun test_diff() { - integer_tests::test_diff!(MAX, CASES); - } - - #[test] - fun test_divide_and_round_up() { - integer_tests::test_divide_and_round_up!(MAX, CASES); - } - - #[test, expected_failure(arithmetic_error, location = std::u8)] - fun test_divide_and_round_up_error() { - 1u8.divide_and_round_up(0); - } - - #[test] - fun test_pow() { - integer_tests::test_pow!(MAX, CASES); - assert_eq!(2u16.pow(12), integer_tests::slow_pow!(2u16, 12)); - assert_eq!(3u16.pow(10), integer_tests::slow_pow!(3u16, 10)); - } - - #[test, expected_failure(arithmetic_error, location = std::u16)] - fun test_pow_overflow() { - 255u16.pow(255); - } - - #[test] - fun test_sqrt() { - let reflexive_cases = - vector[0, 2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 56, 59]; - integer_tests::test_sqrt!(MAX, CASES, reflexive_cases) - } - - #[test] - fun test_try_as_u8() { - integer_tests::test_try_as_u8!(MAX); - } - - #[test] - fun test_to_string() { - integer_tests::test_to_string!(); - assert_eq!((MAX / 2).to_string(), b"32767".to_string()); - assert_eq!((MAX / 2 + 1).to_string(), b"32768".to_string()); - assert_eq!(MAX_PRED.to_string(), b"65534".to_string()); - assert_eq!(MAX.to_string(), b"65535".to_string()); - } - - #[test] - fun test_dos() { - integer_tests::test_dos!(MAX, CASES); - } +module std::u16_tests; + +use std::{integer_tests, unit_test::assert_eq}; + +const BIT_SIZE: u8 = 16; +const MAX: u16 = 0xFFFF; +const MAX_PRED: u16 = MAX - 1; + +const CASES: vector = vector[ + 0, + 1, + 10, + 11, + 100, + 111, + 1 << (BIT_SIZE / 2 - 1), + (1 << (BIT_SIZE / 2 - 1)) + 1, + 1 << (BIT_SIZE - 1), + (1 << (BIT_SIZE - 1)) + 1, + MAX / 2, + (MAX / 2) + 1, + MAX_PRED, + MAX, +]; + +#[test] +fun test_bitwise_not() { + integer_tests::test_bitwise_not!(MAX, CASES); +} + +#[test] +fun test_max() { + integer_tests::test_max!(MAX, CASES); +} + +#[test] +fun test_min() { + integer_tests::test_min!(MAX, CASES); +} + +#[test] +fun test_diff() { + integer_tests::test_diff!(MAX, CASES); +} + +#[test] +fun test_divide_and_round_up() { + integer_tests::test_divide_and_round_up!(MAX, CASES); +} + +#[test, expected_failure(arithmetic_error, location = std::u8)] +fun test_divide_and_round_up_error() { + 1u8.divide_and_round_up(0); +} + +#[test] +fun test_pow() { + integer_tests::test_pow!(MAX, CASES); + assert_eq!(2u16.pow(12), integer_tests::slow_pow!(2u16, 12)); + assert_eq!(3u16.pow(10), integer_tests::slow_pow!(3u16, 10)); +} + +#[test, expected_failure(arithmetic_error, location = std::u16)] +fun test_pow_overflow() { + 255u16.pow(255); +} + +#[test] +fun test_sqrt() { + // prettier-ignore + let reflexive_cases = + vector[0, 2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 56, 59]; + integer_tests::test_sqrt!(MAX, CASES, reflexive_cases) +} + +#[test] +fun test_try_as_u8() { + integer_tests::test_try_as_u8!(MAX); +} + +#[test] +fun test_to_string() { + integer_tests::test_to_string!(); + assert_eq!((MAX / 2).to_string(), b"32767".to_string()); + assert_eq!((MAX / 2 + 1).to_string(), b"32768".to_string()); + assert_eq!(MAX_PRED.to_string(), b"65534".to_string()); + assert_eq!(MAX.to_string(), b"65535".to_string()); +} + +#[test] +fun test_dos() { + integer_tests::test_dos!(MAX, CASES); } diff --git a/crates/sui-framework/packages/move-stdlib/tests/u256_tests.move b/crates/sui-framework/packages/move-stdlib/tests/u256_tests.move index 505b532b8c0dd..548cb2c2f7efc 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/u256_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/u256_tests.move @@ -2,105 +2,115 @@ // SPDX-License-Identifier: Apache-2.0 #[test_only] -module std::u256_tests { - use std::integer_tests; - use std::unit_test::assert_eq; - - const BIT_SIZE: u8 = 255; - const MAX: u256 = - 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF; - const MAX_PRED: u256 = MAX - 1; - - const CASES: vector = vector[ - 0, - 1, - 10, - 11, - 100, - 111, - 1 << (BIT_SIZE / 2), - (1 << (BIT_SIZE / 2)) + 1, - 1 << BIT_SIZE, - (1 << BIT_SIZE) + 1, - MAX / 2, - (MAX / 2) + 1, - MAX_PRED, - MAX, - ]; - - #[test] - fun test_bitwise_not() { - integer_tests::test_bitwise_not!(MAX, CASES); - } - - #[test] - fun test_max() { - integer_tests::test_max!(MAX, CASES); - } - - #[test] - fun test_min() { - integer_tests::test_min!(MAX, CASES); - } - - #[test] - fun test_diff() { - integer_tests::test_diff!(MAX, CASES); - } - - #[test] - fun test_divide_and_round_up() { - integer_tests::test_divide_and_round_up!(MAX, CASES); - } - - #[test, expected_failure(arithmetic_error, location = std::u8)] - fun test_divide_and_round_up_error() { - 1u8.divide_and_round_up(0); - } - - #[test] - fun test_pow() { - integer_tests::test_pow!(MAX, CASES); - assert_eq!(2u256.pow(12), integer_tests::slow_pow!(2u256, 12)); - assert_eq!(3u256.pow(27), integer_tests::slow_pow!(3u256, 27)); - } - - #[test, expected_failure(arithmetic_error, location = std::u256)] - fun test_pow_overflow() { - 255u256.pow(255); - } - - #[test] - fun test_try_as_u8() { - integer_tests::test_try_as_u8!(MAX); - } - - #[test] - fun test_try_as_u16() { - integer_tests::test_try_as_u16!(MAX); - } - - #[test] - fun test_try_as_u32() { - integer_tests::test_try_as_u32!(MAX); - } - - #[test] - fun test_try_as_u64() { - integer_tests::test_try_as_u64!(MAX); - } - - #[test] - fun test_to_string() { - integer_tests::test_to_string!(); - assert_eq!((MAX / 2).to_string(), b"57896044618658097711785492504343953926634992332820282019728792003956564819967".to_string()); - assert_eq!((MAX / 2 + 1).to_string(), b"57896044618658097711785492504343953926634992332820282019728792003956564819968".to_string()); - assert_eq!(MAX_PRED.to_string(), b"115792089237316195423570985008687907853269984665640564039457584007913129639934".to_string()); - assert_eq!(MAX.to_string(), b"115792089237316195423570985008687907853269984665640564039457584007913129639935".to_string()); - } - - #[test] - fun test_dos() { - integer_tests::test_dos!(MAX, CASES); - } +module std::u256_tests; + +use std::{integer_tests, unit_test::assert_eq}; + +const BIT_SIZE: u8 = 255; +const MAX: u256 = 0xFFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF_FFFF; +const MAX_PRED: u256 = MAX - 1; + +const CASES: vector = vector[ + 0, + 1, + 10, + 11, + 100, + 111, + 1 << (BIT_SIZE / 2), + (1 << (BIT_SIZE / 2)) + 1, + 1 << BIT_SIZE, + (1 << BIT_SIZE) + 1, + MAX / 2, + (MAX / 2) + 1, + MAX_PRED, + MAX, +]; + +#[test] +fun test_bitwise_not() { + integer_tests::test_bitwise_not!(MAX, CASES); +} + +#[test] +fun test_max() { + integer_tests::test_max!(MAX, CASES); +} + +#[test] +fun test_min() { + integer_tests::test_min!(MAX, CASES); +} + +#[test] +fun test_diff() { + integer_tests::test_diff!(MAX, CASES); +} + +#[test] +fun test_divide_and_round_up() { + integer_tests::test_divide_and_round_up!(MAX, CASES); +} + +#[test, expected_failure(arithmetic_error, location = std::u8)] +fun test_divide_and_round_up_error() { + 1u8.divide_and_round_up(0); +} + +#[test] +fun test_pow() { + integer_tests::test_pow!(MAX, CASES); + assert_eq!(2u256.pow(12), integer_tests::slow_pow!(2u256, 12)); + assert_eq!(3u256.pow(27), integer_tests::slow_pow!(3u256, 27)); +} + +#[test, expected_failure(arithmetic_error, location = std::u256)] +fun test_pow_overflow() { + 255u256.pow(255); +} + +#[test] +fun test_try_as_u8() { + integer_tests::test_try_as_u8!(MAX); +} + +#[test] +fun test_try_as_u16() { + integer_tests::test_try_as_u16!(MAX); +} + +#[test] +fun test_try_as_u32() { + integer_tests::test_try_as_u32!(MAX); +} + +#[test] +fun test_try_as_u64() { + integer_tests::test_try_as_u64!(MAX); +} + +#[test] +fun test_to_string() { + integer_tests::test_to_string!(); + assert_eq!( + (MAX / 2).to_string(), + b"57896044618658097711785492504343953926634992332820282019728792003956564819967".to_string(), + ); + assert_eq!( + (MAX / 2 + 1).to_string(), + b"57896044618658097711785492504343953926634992332820282019728792003956564819968".to_string(), + ); + assert_eq!( + MAX_PRED.to_string(), + b"115792089237316195423570985008687907853269984665640564039457584007913129639934".to_string(), + ); + assert_eq!( + MAX.to_string(), + b"115792089237316195423570985008687907853269984665640564039457584007913129639935".to_string(), + ); +} + +#[test] +fun test_dos() { + integer_tests::test_dos!(MAX, CASES); } diff --git a/crates/sui-framework/packages/move-stdlib/tests/u32_tests.move b/crates/sui-framework/packages/move-stdlib/tests/u32_tests.move index 49b83cfd6e0fe..11704e46cb016 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/u32_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/u32_tests.move @@ -2,101 +2,101 @@ // SPDX-License-Identifier: Apache-2.0 #[test_only] -module std::u32_tests { - use std::integer_tests; - use std::unit_test::assert_eq; - - const BIT_SIZE: u8 = 32; - const MAX: u32 = 0xFFFF_FFFF; - const MAX_PRED: u32 = MAX - 1; - - const CASES: vector = vector[ - 0, - 1, - 10, - 11, - 100, - 111, - 1 << (BIT_SIZE / 2 - 1), - (1 << (BIT_SIZE / 2 - 1)) + 1, - 1 << (BIT_SIZE - 1), - (1 << (BIT_SIZE - 1)) + 1, - MAX / 2, - (MAX / 2) + 1, - MAX_PRED, - MAX, - ]; - - #[test] - fun test_bitwise_not() { - integer_tests::test_bitwise_not!(MAX, CASES); - } - - #[test] - fun test_max() { - integer_tests::test_max!(MAX, CASES); - } - - #[test] - fun test_min() { - integer_tests::test_min!(MAX, CASES); - } - - #[test] - fun test_diff() { - integer_tests::test_diff!(MAX, CASES); - } - - #[test] - fun test_divide_and_round_up() { - integer_tests::test_divide_and_round_up!(MAX, CASES); - } - - #[test, expected_failure(arithmetic_error, location = std::u8)] - fun test_divide_and_round_up_error() { - 1u8.divide_and_round_up(0); - } - - #[test] - fun test_pow() { - integer_tests::test_pow!(MAX, CASES); - assert_eq!(2u32.pow(12), integer_tests::slow_pow!(2u32, 12)); - assert_eq!(3u32.pow(20), integer_tests::slow_pow!(3u32, 20)); - } - - #[test, expected_failure(arithmetic_error, location = std::u32)] - fun test_pow_overflow() { - 255u32.pow(255); - } - - #[test] - fun test_sqrt() { - let reflexive_cases = - vector[0, 2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 56, 59]; - integer_tests::test_sqrt!(MAX, CASES, reflexive_cases) - } - - #[test] - fun test_try_as_u8() { - integer_tests::test_try_as_u8!(MAX); - } - - #[test] - fun test_try_as_u16() { - integer_tests::test_try_as_u16!(MAX); - } - - #[test] - fun test_to_string() { - integer_tests::test_to_string!(); - assert_eq!((MAX / 2).to_string(), b"2147483647".to_string()); - assert_eq!((MAX / 2 + 1).to_string(), b"2147483648".to_string()); - assert_eq!(MAX_PRED.to_string(), b"4294967294".to_string()); - assert_eq!(MAX.to_string(), b"4294967295".to_string()); - } - - #[test] - fun test_dos() { - integer_tests::test_dos!(MAX, CASES); - } +module std::u32_tests; + +use std::{integer_tests, unit_test::assert_eq}; + +const BIT_SIZE: u8 = 32; +const MAX: u32 = 0xFFFF_FFFF; +const MAX_PRED: u32 = MAX - 1; + +const CASES: vector = vector[ + 0, + 1, + 10, + 11, + 100, + 111, + 1 << (BIT_SIZE / 2 - 1), + (1 << (BIT_SIZE / 2 - 1)) + 1, + 1 << (BIT_SIZE - 1), + (1 << (BIT_SIZE - 1)) + 1, + MAX / 2, + (MAX / 2) + 1, + MAX_PRED, + MAX, +]; + +#[test] +fun test_bitwise_not() { + integer_tests::test_bitwise_not!(MAX, CASES); +} + +#[test] +fun test_max() { + integer_tests::test_max!(MAX, CASES); +} + +#[test] +fun test_min() { + integer_tests::test_min!(MAX, CASES); +} + +#[test] +fun test_diff() { + integer_tests::test_diff!(MAX, CASES); +} + +#[test] +fun test_divide_and_round_up() { + integer_tests::test_divide_and_round_up!(MAX, CASES); +} + +#[test, expected_failure(arithmetic_error, location = std::u8)] +fun test_divide_and_round_up_error() { + 1u8.divide_and_round_up(0); +} + +#[test] +fun test_pow() { + integer_tests::test_pow!(MAX, CASES); + assert_eq!(2u32.pow(12), integer_tests::slow_pow!(2u32, 12)); + assert_eq!(3u32.pow(20), integer_tests::slow_pow!(3u32, 20)); +} + +#[test, expected_failure(arithmetic_error, location = std::u32)] +fun test_pow_overflow() { + 255u32.pow(255); +} + +#[test] +fun test_sqrt() { + // prettier-ignore + let reflexive_cases = + vector[0, 2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 56, 59]; + integer_tests::test_sqrt!(MAX, CASES, reflexive_cases) +} + +#[test] +fun test_try_as_u8() { + integer_tests::test_try_as_u8!(MAX); +} + +#[test] +fun test_try_as_u16() { + integer_tests::test_try_as_u16!(MAX); +} + +#[test] +fun test_to_string() { + integer_tests::test_to_string!(); + assert_eq!((MAX / 2).to_string(), b"2147483647".to_string()); + assert_eq!((MAX / 2 + 1).to_string(), b"2147483648".to_string()); + assert_eq!(MAX_PRED.to_string(), b"4294967294".to_string()); + assert_eq!(MAX.to_string(), b"4294967295".to_string()); +} + +#[test] +fun test_dos() { + integer_tests::test_dos!(MAX, CASES); } diff --git a/crates/sui-framework/packages/move-stdlib/tests/u64_tests.move b/crates/sui-framework/packages/move-stdlib/tests/u64_tests.move index 383488b6bbefb..90b8dce7befe3 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/u64_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/u64_tests.move @@ -2,107 +2,106 @@ // SPDX-License-Identifier: Apache-2.0 #[test_only] -module std::u64_tests { - use std::integer_tests; - use std::unit_test::assert_eq; - - const BIT_SIZE: u8 = 64; - const MAX: u64 = 0xFFFF_FFFF_FFFF_FFFF; - const MAX_PRED: u64 = MAX - 1; - - const CASES: vector = vector[ - 0, - 1, - 10, - 11, - 100, - 111, - 1 << (BIT_SIZE / 2 - 1), - (1 << (BIT_SIZE / 2 - 1)) + 1, - 1 << (BIT_SIZE - 1), - (1 << (BIT_SIZE - 1)) + 1, - MAX / 2, - (MAX / 2) + 1, - MAX_PRED, - MAX, - ]; - - #[test] - fun test_bitwise_not() { - integer_tests::test_bitwise_not!(MAX, CASES); - } - - #[test] - fun test_max() { - integer_tests::test_max!(MAX, CASES); - } - - #[test] - fun test_min() { - integer_tests::test_min!(MAX, CASES); - } - - #[test] - fun test_diff() { - integer_tests::test_diff!(MAX, CASES); - } - - #[test] - fun test_divide_and_round_up() { - integer_tests::test_divide_and_round_up!(MAX, CASES); - } - - #[test, expected_failure(arithmetic_error, location = std::u8)] - fun test_divide_and_round_up_error() { - 1u8.divide_and_round_up(0); - } - - #[test] - fun test_pow() { - integer_tests::test_pow!(MAX, CASES); - assert_eq!(2u64.pow(12), integer_tests::slow_pow!(2u64, 12)); - assert_eq!(3u64.pow(27), integer_tests::slow_pow!(3u64, 27)); - } - - #[test, expected_failure(arithmetic_error, location = std::u64)] - fun test_pow_overflow() { - 255u64.pow(255); - } - - #[test] - fun test_sqrt() { - let reflexive_cases = - vector[0, 2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 56, 59]; - integer_tests::test_sqrt!(MAX, CASES, reflexive_cases) - } - - #[test] - fun test_try_as_u8() { - integer_tests::test_try_as_u8!(MAX); - } - - #[test] - fun test_try_as_u16() { - integer_tests::test_try_as_u16!(MAX); - } - - #[test] - fun test_try_as_u32() { - integer_tests::test_try_as_u32!(MAX); - } - - #[test] - fun test_to_string() { - integer_tests::test_to_string!(); - assert_eq!((MAX / 2).to_string(), b"9223372036854775807".to_string()); - assert_eq!((MAX / 2 + 1).to_string(), b"9223372036854775808".to_string()); - assert_eq!(MAX_PRED.to_string(), b"18446744073709551614".to_string()); - assert_eq!(MAX.to_string(), b"18446744073709551615".to_string()); - } - - #[test] - fun test_dos() { - integer_tests::test_dos!(MAX, CASES); - } +module std::u64_tests; + +use std::{integer_tests, unit_test::assert_eq}; + +const BIT_SIZE: u8 = 64; +const MAX: u64 = 0xFFFF_FFFF_FFFF_FFFF; +const MAX_PRED: u64 = MAX - 1; + +const CASES: vector = vector[ + 0, + 1, + 10, + 11, + 100, + 111, + 1 << (BIT_SIZE / 2 - 1), + (1 << (BIT_SIZE / 2 - 1)) + 1, + 1 << (BIT_SIZE - 1), + (1 << (BIT_SIZE - 1)) + 1, + MAX / 2, + (MAX / 2) + 1, + MAX_PRED, + MAX, +]; + +#[test] +fun test_bitwise_not() { + integer_tests::test_bitwise_not!(MAX, CASES); +} + +#[test] +fun test_max() { + integer_tests::test_max!(MAX, CASES); +} + +#[test] +fun test_min() { + integer_tests::test_min!(MAX, CASES); +} + +#[test] +fun test_diff() { + integer_tests::test_diff!(MAX, CASES); +} + +#[test] +fun test_divide_and_round_up() { + integer_tests::test_divide_and_round_up!(MAX, CASES); +} + +#[test, expected_failure(arithmetic_error, location = std::u8)] +fun test_divide_and_round_up_error() { + 1u8.divide_and_round_up(0); +} + +#[test] +fun test_pow() { + integer_tests::test_pow!(MAX, CASES); + assert_eq!(2u64.pow(12), integer_tests::slow_pow!(2u64, 12)); + assert_eq!(3u64.pow(27), integer_tests::slow_pow!(3u64, 27)); +} + +#[test, expected_failure(arithmetic_error, location = std::u64)] +fun test_pow_overflow() { + 255u64.pow(255); +} + +#[test] +fun test_sqrt() { + // prettier-ignore + let reflexive_cases = + vector[0, 2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38, 41, 44, 47, 50, 53, 56, 59]; + integer_tests::test_sqrt!(MAX, CASES, reflexive_cases) +} + +#[test] +fun test_try_as_u8() { + integer_tests::test_try_as_u8!(MAX); +} + +#[test] +fun test_try_as_u16() { + integer_tests::test_try_as_u16!(MAX); +} + +#[test] +fun test_try_as_u32() { + integer_tests::test_try_as_u32!(MAX); +} + +#[test] +fun test_to_string() { + integer_tests::test_to_string!(); + assert_eq!((MAX / 2).to_string(), b"9223372036854775807".to_string()); + assert_eq!((MAX / 2 + 1).to_string(), b"9223372036854775808".to_string()); + assert_eq!(MAX_PRED.to_string(), b"18446744073709551614".to_string()); + assert_eq!(MAX.to_string(), b"18446744073709551615".to_string()); +} +#[test] +fun test_dos() { + integer_tests::test_dos!(MAX, CASES); } diff --git a/crates/sui-framework/packages/move-stdlib/tests/u8_tests.move b/crates/sui-framework/packages/move-stdlib/tests/u8_tests.move index ffe49a03ab3e8..2e7a878d5d689 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/u8_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/u8_tests.move @@ -2,90 +2,89 @@ // SPDX-License-Identifier: Apache-2.0 #[test_only] -module std::u8_tests { - use std::integer_tests; - use std::unit_test::assert_eq; +module std::u8_tests; - const BIT_SIZE: u8 = 8; - const MAX: u8 = 0xFF; - const MAX_PRED: u8 = MAX - 1; +use std::{integer_tests, unit_test::assert_eq}; - const CASES: vector = vector[ - 0, - 1, - 10, - 11, - 100, - 111, - 1 << (BIT_SIZE / 2 - 1), - (1 << (BIT_SIZE / 2 - 1)) + 1, - 1 << (BIT_SIZE - 1), - (1 << (BIT_SIZE - 1)) + 1, - MAX / 2, - (MAX / 2) + 1, - MAX_PRED, - MAX, - ]; +const BIT_SIZE: u8 = 8; +const MAX: u8 = 0xFF; +const MAX_PRED: u8 = MAX - 1; - #[test] - fun test_bitwise_not() { - integer_tests::test_bitwise_not!(MAX, CASES); - } +const CASES: vector = vector[ + 0, + 1, + 10, + 11, + 100, + 111, + 1 << (BIT_SIZE / 2 - 1), + (1 << (BIT_SIZE / 2 - 1)) + 1, + 1 << (BIT_SIZE - 1), + (1 << (BIT_SIZE - 1)) + 1, + MAX / 2, + (MAX / 2) + 1, + MAX_PRED, + MAX, +]; - #[test] - fun test_max() { - integer_tests::test_max!(MAX, CASES); - } +#[test] +fun test_bitwise_not() { + integer_tests::test_bitwise_not!(MAX, CASES); +} - #[test] - fun test_min() { - integer_tests::test_min!(MAX, CASES); - } +#[test] +fun test_max() { + integer_tests::test_max!(MAX, CASES); +} - #[test] - fun test_diff() { - integer_tests::test_diff!(MAX, CASES); - } +#[test] +fun test_min() { + integer_tests::test_min!(MAX, CASES); +} - #[test] - fun test_divide_and_round_up() { - integer_tests::test_divide_and_round_up!(MAX, CASES); - } +#[test] +fun test_diff() { + integer_tests::test_diff!(MAX, CASES); +} - #[test, expected_failure(arithmetic_error, location = std::u8)] - fun test_divide_and_round_up_error() { - 1u8.divide_and_round_up(0); - } +#[test] +fun test_divide_and_round_up() { + integer_tests::test_divide_and_round_up!(MAX, CASES); +} - #[test] - fun test_pow() { - integer_tests::test_pow!(MAX, CASES); - } +#[test, expected_failure(arithmetic_error, location = std::u8)] +fun test_divide_and_round_up_error() { + 1u8.divide_and_round_up(0); +} - #[test, expected_failure(arithmetic_error, location = std::u8)] - fun test_pow_overflow() { - 255u8.pow(255); - } +#[test] +fun test_pow() { + integer_tests::test_pow!(MAX, CASES); +} - #[test] - fun test_sqrt() { - integer_tests::test_sqrt!(MAX, CASES, vector[0, 2, 5, 8, 11, 14]); - } +#[test, expected_failure(arithmetic_error, location = std::u8)] +fun test_pow_overflow() { + 255u8.pow(255); +} - #[test] - fun test_to_string() { - integer_tests::test_to_string!(); - assert_eq!((MAX / 2).to_string(), b"127".to_string()); - assert_eq!((MAX / 2 + 1).to_string(), b"128".to_string()); - assert_eq!(MAX_PRED.to_string(), b"254".to_string()); - assert_eq!(MAX.to_string(), b"255".to_string()); - } +#[test] +fun test_sqrt() { + integer_tests::test_sqrt!(MAX, CASES, vector[0, 2, 5, 8, 11, 14]); +} + +#[test] +fun test_to_string() { + integer_tests::test_to_string!(); + assert_eq!((MAX / 2).to_string(), b"127".to_string()); + assert_eq!((MAX / 2 + 1).to_string(), b"128".to_string()); + assert_eq!(MAX_PRED.to_string(), b"254".to_string()); + assert_eq!(MAX.to_string(), b"255".to_string()); +} - #[test] - fun test_dos() { - let mut sum = 0u16; - 255u8.do_eq!(|i| sum = sum + (i as u16)); - assert_eq!(sum, 32640); - integer_tests::test_dos!(MAX, CASES); - } +#[test] +fun test_dos() { + let mut sum = 0u16; + 255u8.do_eq!(|i| sum = sum + (i as u16)); + assert_eq!(sum, 32640); + integer_tests::test_dos!(MAX, CASES); } diff --git a/crates/sui-framework/packages/move-stdlib/tests/uq32_32_tests.move b/crates/sui-framework/packages/move-stdlib/tests/uq32_32_tests.move new file mode 100644 index 0000000000000..78c1d9f543c33 --- /dev/null +++ b/crates/sui-framework/packages/move-stdlib/tests/uq32_32_tests.move @@ -0,0 +1,257 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module std::uq32_32_tests; + +use std::unit_test::assert_eq; +use std::uq32_32::{ + Self, + add, + sub, + mul, + div, + int_div, + int_mul, + from_int, + from_quotient, + from_raw, + to_raw, +}; + +#[test] +fun from_quotient_zero() { + let x = from_quotient(0, 1); + assert_eq!(x.to_raw(), 0); +} + +#[test] +fun from_quotient_max_numerator_denominator() { + // Test creating a 1.0 fraction from the maximum u64 value. + let f = from_quotient(std::u64::max_value!(), std::u64::max_value!()); + let one = f.to_raw(); + assert_eq!(one, 1 << 32); // 0x1.00000000 +} + +#[test] +#[expected_failure(abort_code = uq32_32::EDenominator)] +fun from_quotient_div_zero() { + // A denominator of zero should cause an arithmetic error. + from_quotient(2, 0); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EQuotientTooLarge)] +fun from_quotient_ratio_too_large() { + // The maximum value is 2^32 - 1. Check that anything larger aborts + // with an overflow. + from_quotient(1 << 32, 1); // 2^32 +} + +#[test] +#[expected_failure(abort_code = uq32_32::EQuotientTooSmall)] +fun from_quotient_ratio_too_small() { + // The minimum non-zero value is 2^-32. Check that anything smaller + // aborts. + from_quotient(1, (1 << 32) + 1); // 1/(2^32 + 1) +} + +#[test] +fun test_from_int() { + assert_eq!(from_int(0).to_raw(), 0); + assert_eq!(from_int(1).to_raw(), 0x1_0000_0000); + assert_eq!(from_int(std::u32::max_value!()).to_raw(), std::u32::max_value!() as u64 << 32); +} + +#[test] +fun test_add() { + let a = from_quotient(3, 4); + assert!(a.add(from_int(0)) == a); + + let c = a.add(from_int(1)); + assert!(from_quotient(7, 4) == c); + + let b = from_quotient(1, 4); + let c = a.add(b); + assert!(from_int(1) == c); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EOverflow)] +fun test_add_overflow() { + let a = from_int(1 << 31); + let b = from_int(1 << 31); + let _ = a.add(b); +} + +#[test] +fun test_sub() { + let a = from_int(5); + assert_eq!(a.sub(from_int(0)), a); + + let b = from_int(4); + let c = a.sub(b); + assert_eq!(from_int(1), c); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EOverflow)] +fun test_sub_underflow() { + let a = from_int(3); + let b = from_int(5); + a.sub(b); +} + +#[test] +fun test_mul() { + let a = from_quotient(3, 4); + assert!(a.mul(from_int(0)) == from_int(0)); + assert!(a.mul(from_int(1)) == a); + + let b = from_quotient(3, 2); + let c = a.mul(b); + let expected = from_quotient(9, 8); + assert_eq!(c, expected); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EOverflow)] +fun test_mul_overflow() { + let a = from_int(1 << 16); + let b = from_int(1 << 16); + let _ = a.mul(b); +} + +#[test] +fun test_div() { + let a = from_quotient(3, 4); + assert!(a.div(from_int(1)) == a); + + let b = from_int(8); + let c = a.div(b); + let expected = from_quotient(3, 32); + assert_eq!(c, expected); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EDivisionByZero)] +fun test_div_by_zero() { + let a = from_int(7); + let b = from_int(0); + let _ = a.div(b); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EOverflow)] +fun test_div_overflow() { + let a = from_int(1 << 31); + let b = from_quotient(1, 2); + let _ = a.div(b); +} + +#[test] +fun exact_int_div() { + let f = from_quotient(3, 4); // 0.75 + let twelve = int_div(9, f); // 9 / 0.75 + assert_eq!(twelve, 12); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EDivisionByZero)] +fun int_div_by_zero() { + let f = from_raw(0); // 0 + // Dividing by zero should cause an arithmetic error. + int_div(1, f); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EOverflow)] +fun int_div_overflow_small_divisor() { + let f = from_raw(1); // 0x0.00000001 + // Divide 2^32 by the minimum fractional value. This should overflow. + int_div(1 << 32, f); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EOverflow)] +fun int_div_overflow_large_numerator() { + let f = from_quotient(1, 2); // 0.5 + // Divide the maximum u64 value by 0.5. This should overflow. + int_div(std::u64::max_value!(), f); +} + +#[test] +fun exact_int_mul() { + let f = from_quotient(3, 4); // 0.75 + let nine = int_mul(12, f); // 12 * 0.75 + assert_eq!(nine, 9); +} + +#[test] +fun int_mul_truncates() { + let f = from_quotient(1, 3); // 0.333... + let not_three = int_mul(9, copy f); // 9 * 0.333... + // multiply_u64 does NOT round -- it truncates -- so values that + // are not perfectly representable in binary may be off by one. + assert_eq!(not_three, 2); + + // Try again with a fraction slightly larger than 1/3. + let f = from_raw(f.to_raw() + 1); + let three = int_mul(9, f); + assert_eq!(three, 3); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EOverflow)] +fun int_mul_overflow_small_multiplier() { + let f = from_quotient(3, 2); // 1.5 + // Multiply the maximum u64 value by 1.5. This should overflow. + int_mul(std::u64::max_value!(), f); +} + +#[test] +#[expected_failure(abort_code = uq32_32::EOverflow)] +fun int_mul_overflow_large_multiplier() { + let f = from_raw(std::u64::max_value!()); + // Multiply 2^32 + 1 by the maximum fixed-point value. This should overflow. + int_mul((1 << 32) + 1, f); +} + +#[test] +fun test_comparison() { + let a = from_quotient(5, 2); + let b = from_quotient(5, 3); + let c = from_quotient(5, 2); + + assert!(b.le(a)); + assert!(b.lt(a)); + assert!(c.le(a)); + assert_eq!(c, a); + assert!(a.ge(b)); + assert!(a.gt(b)); + assert!(from_int(0).le(a)); +} + +#[random_test] +fun test_raw(raw: u64) { + assert_eq!(from_raw(raw).to_raw(), raw); +} + +#[random_test] +fun test_int_roundtrip(c: u32) { + assert_eq!(from_int(c).to_int(), c); +} + +#[random_test] +fun test_mul_rand(n: u16, d: u16, c: u16) { + if (d == 0) return; + let q = from_quotient(n as u64, d as u64); + assert_eq!(int_mul(c as u64, q), q.mul(from_int(c as u32)).to_int() as u64); +} + +#[random_test] +fun test_div_rand(n: u16, d: u16, c: u16) { + if (d == 0) return; + let q = from_quotient(n as u64, d as u64); + assert_eq!(int_div(c as u64, q), from_int(c as u32).div(q).to_int() as u64); +} diff --git a/crates/sui-framework/packages/move-stdlib/tests/vector_tests.move b/crates/sui-framework/packages/move-stdlib/tests/vector_tests.move index 7064ee307c357..eac18f10653a2 100644 --- a/crates/sui-framework/packages/move-stdlib/tests/vector_tests.move +++ b/crates/sui-framework/packages/move-stdlib/tests/vector_tests.move @@ -4,801 +4,801 @@ // SPDX-License-Identifier: Apache-2.0 #[test_only] -module std::vector_tests { - public struct R has store { } - public struct Droppable has drop {} - public struct NotDroppable {} - - #[test] - fun test_singleton_contains() { - assert!(vector[0][0] == 0); - assert!(vector[true][0] == true); - assert!(vector[@0x1][0] == @0x1); - } +module std::vector_tests; - #[test] - fun test_singleton_len() { - assert!(&vector[0].length() == 1); - assert!(&vector[true].length() == 1); - assert!(&vector[@0x1].length() == 1); - } +public struct R has store {} +public struct Droppable has drop {} +public struct NotDroppable {} - #[test] - fun test_empty_is_empty() { - assert!(vector[].is_empty()); - } - - #[test] - fun append_empties_is_empty() { - let mut v1 = vector[]; - let v2 = vector[]; - v1.append(v2); - assert!(v1.is_empty()); - } - - #[test] - fun append_respects_order_empty_lhs() { - let mut v1 = vector[]; - let mut v2 = vector[]; - v2.push_back(0); - v2.push_back(1); - v2.push_back(2); - v2.push_back(3); - v1.append(v2); - assert!(!v1.is_empty()); - assert!(v1.length() == 4); - assert!(v1[0] == 0); - assert!(v1[1] == 1); - assert!(v1[2] == 2); - assert!(v1[3] == 3); - } +#[test] +fun test_singleton_contains() { + assert!(vector[0][0] == 0); + assert!(vector[true][0] == true); + assert!(vector[@0x1][0] == @0x1); +} - #[test] - fun append_respects_order_empty_rhs() { - let mut v1 = vector[]; - let v2 = vector[]; - v1.push_back(0); - v1.push_back(1); - v1.push_back(2); - v1.push_back(3); - v1.append(v2); - assert!(!v1.is_empty()); - assert!(v1.length() == 4); - assert!(v1[0] == 0); - assert!(v1[1] == 1); - assert!(v1[2] == 2); - assert!(v1[3] == 3); - } +#[test] +fun test_singleton_len() { + assert!(&vector[0].length() == 1); + assert!(&vector[true].length() == 1); + assert!(&vector[@0x1].length() == 1); +} - #[test] - fun append_respects_order_nonempty_rhs_lhs() { - let mut v1 = vector[]; - let mut v2 = vector[]; - v1.push_back(0); - v1.push_back(1); - v1.push_back(2); - v1.push_back(3); - v2.push_back(4); - v2.push_back(5); - v2.push_back(6); - v2.push_back(7); - v1.append(v2); - assert!(!v1.is_empty()); - assert!(v1.length() == 8); - let mut i = 0; - while (i < 8) { - assert!(v1[i] == i, i); - i = i + 1; - } - } +#[test] +fun test_empty_is_empty() { + assert!(vector[].is_empty()); +} - #[test] - #[expected_failure(vector_error, minor_status = 1, location = Self)] - fun borrow_out_of_range() { - let mut v = vector[]; - v.push_back(7); - &v[1]; - } +#[test] +fun append_empties_is_empty() { + let mut v1 = vector[]; + let v2 = vector[]; + v1.append(v2); + assert!(v1.is_empty()); +} - #[test] - fun vector_contains() { - let mut vec = vector[]; - assert!(!vec.contains(&0)); - - vec.push_back(0); - assert!(vec.contains(&0)); - assert!(!vec.contains(&1)); - - vec.push_back(1); - assert!(vec.contains(&0)); - assert!(vec.contains(&1)); - assert!(!vec.contains(&2)); - - vec.push_back(2); - assert!(vec.contains(&0)); - assert!(vec.contains(&1)); - assert!(vec.contains(&2)); - assert!(!vec.contains(&3)); - } +#[test] +fun append_respects_order_empty_lhs() { + let mut v1 = vector[]; + let mut v2 = vector[]; + v2.push_back(0); + v2.push_back(1); + v2.push_back(2); + v2.push_back(3); + v1.append(v2); + assert!(!v1.is_empty()); + assert!(v1.length() == 4); + assert!(v1[0] == 0); + assert!(v1[1] == 1); + assert!(v1[2] == 2); + assert!(v1[3] == 3); +} - #[test] - fun destroy_empty() { - vector[].destroy_empty(); - vector[].destroy_empty(); - vector::empty().destroy_empty(); - vector::empty().destroy_empty(); - } +#[test] +fun append_respects_order_empty_rhs() { + let mut v1 = vector[]; + let v2 = vector[]; + v1.push_back(0); + v1.push_back(1); + v1.push_back(2); + v1.push_back(3); + v1.append(v2); + assert!(!v1.is_empty()); + assert!(v1.length() == 4); + assert!(v1[0] == 0); + assert!(v1[1] == 1); + assert!(v1[2] == 2); + assert!(v1[3] == 3); +} - #[test] - fun destroy_empty_with_pops() { - let mut v = vector[]; - v.push_back(42); - v.pop_back(); - v.destroy_empty(); +#[test] +fun append_respects_order_nonempty_rhs_lhs() { + let mut v1 = vector[]; + let mut v2 = vector[]; + v1.push_back(0); + v1.push_back(1); + v1.push_back(2); + v1.push_back(3); + v2.push_back(4); + v2.push_back(5); + v2.push_back(6); + v2.push_back(7); + v1.append(v2); + assert!(!v1.is_empty()); + assert!(v1.length() == 8); + let mut i = 0; + while (i < 8) { + assert!(v1[i] == i, i); + i = i + 1; } +} - #[test] - #[expected_failure(vector_error, minor_status = 3, location = Self)] - fun destroy_non_empty() { - let mut v = vector[]; - v.push_back(42); - v.destroy_empty(); - } +#[test] +#[expected_failure(vector_error, minor_status = 1, location = Self)] +fun borrow_out_of_range() { + let mut v = vector[]; + v.push_back(7); + &v[1]; +} - #[test] - fun get_set_work() { - let mut vec = vector[]; - vec.push_back(0); - vec.push_back(1); - assert!(vec[1] == 1); - assert!(vec[0] == 0); - - *&mut vec[0] = 17; - assert!(vec[1] == 1); - assert!(vec[0] == 17); - } +#[test] +fun vector_contains() { + let mut vec = vector[]; + assert!(!vec.contains(&0)); + + vec.push_back(0); + assert!(vec.contains(&0)); + assert!(!vec.contains(&1)); + + vec.push_back(1); + assert!(vec.contains(&0)); + assert!(vec.contains(&1)); + assert!(!vec.contains(&2)); + + vec.push_back(2); + assert!(vec.contains(&0)); + assert!(vec.contains(&1)); + assert!(vec.contains(&2)); + assert!(!vec.contains(&3)); +} - #[test] - #[expected_failure(vector_error, minor_status = 2, location = Self)] - fun pop_out_of_range() { - let mut v = vector[]; - v.pop_back(); - } +#[test] +fun destroy_empty() { + vector[].destroy_empty(); + vector[].destroy_empty(); + vector::empty().destroy_empty(); + vector::empty().destroy_empty(); +} - #[test] - fun swap_different_indices() { - let mut vec = vector[]; - vec.push_back(0); - vec.push_back(1); - vec.push_back(2); - vec.push_back(3); - vec.swap(0, 3); - vec.swap(1, 2); - assert!(vec[0] == 3); - assert!(vec[1] == 2); - assert!(vec[2] == 1); - assert!(vec[3] == 0); - } +#[test] +fun destroy_empty_with_pops() { + let mut v = vector[]; + v.push_back(42); + v.pop_back(); + v.destroy_empty(); +} - #[test] - fun swap_same_index() { - let mut vec = vector[]; - vec.push_back(0); - vec.push_back(1); - vec.push_back(2); - vec.push_back(3); - vec.swap(1, 1); - assert!(vec[0] == 0); - assert!(vec[1] == 1); - assert!(vec[2] == 2); - assert!(vec[3] == 3); - } +#[test] +#[expected_failure(vector_error, minor_status = 3, location = Self)] +fun destroy_non_empty() { + let mut v = vector[]; + v.push_back(42); + v.destroy_empty(); +} - #[test] - fun remove_singleton_vector() { - let mut v = vector[]; - v.push_back(0); - assert!(v.remove(0) == 0); - assert!(v.length() == 0); - } +#[test] +fun get_set_work() { + let mut vec = vector[]; + vec.push_back(0); + vec.push_back(1); + assert!(vec[1] == 1); + assert!(vec[0] == 0); + + *&mut vec[0] = 17; + assert!(vec[1] == 1); + assert!(vec[0] == 17); +} - #[test] - fun remove_nonsingleton_vector() { - let mut v = vector[]; - v.push_back(0); - v.push_back(1); - v.push_back(2); - v.push_back(3); - - assert!(v.remove(1) == 1); - assert!(v.length() == 3); - assert!(v[0] == 0); - assert!(v[1] == 2); - assert!(v[2] == 3); - } +#[test] +#[expected_failure(vector_error, minor_status = 2, location = Self)] +fun pop_out_of_range() { + let mut v = vector[]; + v.pop_back(); +} - #[test] - fun remove_nonsingleton_vector_last_elem() { - let mut v = vector[]; - v.push_back(0); - v.push_back(1); - v.push_back(2); - v.push_back(3); - - assert!(v.remove(3) == 3); - assert!(v.length() == 3); - assert!(v[0] == 0); - assert!(v[1] == 1); - assert!(v[2] == 2); - } +#[test] +fun swap_different_indices() { + let mut vec = vector[]; + vec.push_back(0); + vec.push_back(1); + vec.push_back(2); + vec.push_back(3); + vec.swap(0, 3); + vec.swap(1, 2); + assert!(vec[0] == 3); + assert!(vec[1] == 2); + assert!(vec[2] == 1); + assert!(vec[3] == 0); +} - #[test] - #[expected_failure(abort_code = vector::EINDEX_OUT_OF_BOUNDS)] - fun remove_empty_vector() { - let mut v = vector[]; - v.remove(0); - } +#[test] +fun swap_same_index() { + let mut vec = vector[]; + vec.push_back(0); + vec.push_back(1); + vec.push_back(2); + vec.push_back(3); + vec.swap(1, 1); + assert!(vec[0] == 0); + assert!(vec[1] == 1); + assert!(vec[2] == 2); + assert!(vec[3] == 3); +} - #[test] - #[expected_failure(abort_code = vector::EINDEX_OUT_OF_BOUNDS)] - fun remove_out_of_bound_index() { - let mut v = vector[]; - v.push_back(0); - v.remove(1); - } +#[test] +fun remove_singleton_vector() { + let mut v = vector[]; + v.push_back(0); + assert!(v.remove(0) == 0); + assert!(v.length() == 0); +} - #[test] - fun reverse_vector_empty() { - let mut v = vector[]; - let is_empty = v.is_empty(); - v.reverse(); - assert!(is_empty == v.is_empty()); - } +#[test] +fun remove_nonsingleton_vector() { + let mut v = vector[]; + v.push_back(0); + v.push_back(1); + v.push_back(2); + v.push_back(3); + + assert!(v.remove(1) == 1); + assert!(v.length() == 3); + assert!(v[0] == 0); + assert!(v[1] == 2); + assert!(v[2] == 3); +} - #[test] - fun reverse_singleton_vector() { - let mut v = vector[]; - v.push_back(0); - assert!(v[0] == 0); - v.reverse(); - assert!(v[0] == 0); - } +#[test] +fun remove_nonsingleton_vector_last_elem() { + let mut v = vector[]; + v.push_back(0); + v.push_back(1); + v.push_back(2); + v.push_back(3); + + assert!(v.remove(3) == 3); + assert!(v.length() == 3); + assert!(v[0] == 0); + assert!(v[1] == 1); + assert!(v[2] == 2); +} - #[test] - fun reverse_vector_nonempty_even_length() { - let mut v = vector[]; - v.push_back(0); - v.push_back(1); - v.push_back(2); - v.push_back(3); - - assert!(v[0] == 0); - assert!(v[1] == 1); - assert!(v[2] == 2); - assert!(v[3] == 3); - - v.reverse(); - - assert!(v[3] == 0); - assert!(v[2] == 1); - assert!(v[1] == 2); - assert!(v[0] == 3); - } +#[test] +#[expected_failure(abort_code = vector::EINDEX_OUT_OF_BOUNDS)] +fun remove_empty_vector() { + let mut v = vector[]; + v.remove(0); +} - #[test] - fun reverse_vector_nonempty_odd_length_non_singleton() { - let mut v = vector[]; - v.push_back(0); - v.push_back(1); - v.push_back(2); +#[test] +#[expected_failure(abort_code = vector::EINDEX_OUT_OF_BOUNDS)] +fun remove_out_of_bound_index() { + let mut v = vector[]; + v.push_back(0); + v.remove(1); +} - assert!(v[0] == 0); - assert!(v[1] == 1); - assert!(v[2] == 2); +#[test] +fun reverse_vector_empty() { + let mut v = vector[]; + let is_empty = v.is_empty(); + v.reverse(); + assert!(is_empty == v.is_empty()); +} - v.reverse(); +#[test] +fun reverse_singleton_vector() { + let mut v = vector[]; + v.push_back(0); + assert!(v[0] == 0); + v.reverse(); + assert!(v[0] == 0); +} - assert!(v[2] == 0); - assert!(v[1] == 1); - assert!(v[0] == 2); - } +#[test] +fun reverse_vector_nonempty_even_length() { + let mut v = vector[]; + v.push_back(0); + v.push_back(1); + v.push_back(2); + v.push_back(3); + + assert!(v[0] == 0); + assert!(v[1] == 1); + assert!(v[2] == 2); + assert!(v[3] == 3); + + v.reverse(); + + assert!(v[3] == 0); + assert!(v[2] == 1); + assert!(v[1] == 2); + assert!(v[0] == 3); +} - #[test] - #[expected_failure(vector_error, minor_status = 1, location = Self)] - fun swap_empty() { - let mut v = vector[]; - v.swap(0, 0); - } +#[test] +fun reverse_vector_nonempty_odd_length_non_singleton() { + let mut v = vector[]; + v.push_back(0); + v.push_back(1); + v.push_back(2); - #[test] - #[expected_failure(vector_error, minor_status = 1, location = Self)] - fun swap_out_of_range() { - let mut v = vector[]; + assert!(v[0] == 0); + assert!(v[1] == 1); + assert!(v[2] == 2); - v.push_back(0); - v.push_back(1); - v.push_back(2); - v.push_back(3); + v.reverse(); - v.swap(1, 10); - } + assert!(v[2] == 0); + assert!(v[1] == 1); + assert!(v[0] == 2); +} - #[test] - #[expected_failure(abort_code = std::vector::EINDEX_OUT_OF_BOUNDS)] - fun swap_remove_empty() { - let mut v = vector[]; - v.swap_remove(0); - } +#[test] +#[expected_failure(vector_error, minor_status = 1, location = Self)] +fun swap_empty() { + let mut v = vector[]; + v.swap(0, 0); +} - #[test] - fun swap_remove_singleton() { - let mut v = vector[]; - v.push_back(0); - assert!(v.swap_remove(0) == 0); - assert!(v.is_empty()); - } +#[test] +#[expected_failure(vector_error, minor_status = 1, location = Self)] +fun swap_out_of_range() { + let mut v = vector[]; - #[test] - fun swap_remove_inside_vector() { - let mut v = vector[]; - v.push_back(0); - v.push_back(1); - v.push_back(2); - v.push_back(3); + v.push_back(0); + v.push_back(1); + v.push_back(2); + v.push_back(3); - assert!(v[0] == 0); - assert!(v[1] == 1); - assert!(v[2] == 2); - assert!(v[3] == 3); + v.swap(1, 10); +} - assert!(v.swap_remove(1) == 1); - assert!(v.length() == 3); +#[test] +#[expected_failure(abort_code = std::vector::EINDEX_OUT_OF_BOUNDS)] +fun swap_remove_empty() { + let mut v = vector[]; + v.swap_remove(0); +} - assert!(v[0] == 0); - assert!(v[1] == 3); - assert!(v[2] == 2); +#[test] +fun swap_remove_singleton() { + let mut v = vector[]; + v.push_back(0); + assert!(v.swap_remove(0) == 0); + assert!(v.is_empty()); +} - } +#[test] +fun swap_remove_inside_vector() { + let mut v = vector[]; + v.push_back(0); + v.push_back(1); + v.push_back(2); + v.push_back(3); + + assert!(v[0] == 0); + assert!(v[1] == 1); + assert!(v[2] == 2); + assert!(v[3] == 3); + + assert!(v.swap_remove(1) == 1); + assert!(v.length() == 3); + + assert!(v[0] == 0); + assert!(v[1] == 3); + assert!(v[2] == 2); +} - #[test] - fun swap_remove_end_of_vector() { - let mut v = vector[]; - v.push_back(0); - v.push_back(1); - v.push_back(2); - v.push_back(3); - - assert!(v[0] == 0); - assert!(v[1] == 1); - assert!(v[2] == 2); - assert!(v[3] == 3); - - assert!(v.swap_remove(3) == 3); - assert!(v.length() == 3); - - assert!(v[0] == 0); - assert!(v[1] == 1); - assert!(v[2] == 2); - } +#[test] +fun swap_remove_end_of_vector() { + let mut v = vector[]; + v.push_back(0); + v.push_back(1); + v.push_back(2); + v.push_back(3); + + assert!(v[0] == 0); + assert!(v[1] == 1); + assert!(v[2] == 2); + assert!(v[3] == 3); + + assert!(v.swap_remove(3) == 3); + assert!(v.length() == 3); + + assert!(v[0] == 0); + assert!(v[1] == 1); + assert!(v[2] == 2); +} - #[test] - #[expected_failure(vector_error, minor_status = 1, location = std::vector)] - fun swap_remove_out_of_range() { - let mut v = vector[]; - v.push_back(0); - v.swap_remove(1); - } +#[test] +#[expected_failure(vector_error, minor_status = 1, location = std::vector)] +fun swap_remove_out_of_range() { + let mut v = vector[]; + v.push_back(0); + v.swap_remove(1); +} - #[test] - fun push_back_and_borrow() { - let mut v = vector[]; - v.push_back(7); - assert!(!v.is_empty()); - assert!(v.length() == 1); - assert!(v[0] == 7); - - v.push_back(8); - assert!(v.length() == 2); - assert!(v[0] == 7); - assert!(v[1] == 8); - } +#[test] +fun push_back_and_borrow() { + let mut v = vector[]; + v.push_back(7); + assert!(!v.is_empty()); + assert!(v.length() == 1); + assert!(v[0] == 7); + + v.push_back(8); + assert!(v.length() == 2); + assert!(v[0] == 7); + assert!(v[1] == 8); +} - #[test] - fun index_of_empty_not_has() { - let v = vector[]; - let (has, index) = v.index_of(&true); - assert!(!has); - assert!(index == 0); - } +#[test] +fun index_of_empty_not_has() { + let v = vector[]; + let (has, index) = v.index_of(&true); + assert!(!has); + assert!(index == 0); +} - #[test] - fun index_of_nonempty_not_has() { - let mut v = vector[]; - v.push_back(false); - let (has, index) = v.index_of(&true); - assert!(!has); - assert!(index == 0); - } +#[test] +fun index_of_nonempty_not_has() { + let mut v = vector[]; + v.push_back(false); + let (has, index) = v.index_of(&true); + assert!(!has); + assert!(index == 0); +} - #[test] - fun index_of_nonempty_has() { - let mut v = vector[]; - v.push_back(false); - v.push_back(true); - let (has, index) = v.index_of(&true); - assert!(has); - assert!(index == 1); - } +#[test] +fun index_of_nonempty_has() { + let mut v = vector[]; + v.push_back(false); + v.push_back(true); + let (has, index) = v.index_of(&true); + assert!(has); + assert!(index == 1); +} - // index_of will return the index first occurence that is equal - #[test] - fun index_of_nonempty_has_multiple_occurences() { - let mut v = vector[]; - v.push_back(false); - v.push_back(true); - v.push_back(true); - let (has, index) = v.index_of(&true); - assert!(has); - assert!(index == 1); - } +// index_of will return the index first occurence that is equal +#[test] +fun index_of_nonempty_has_multiple_occurences() { + let mut v = vector[]; + v.push_back(false); + v.push_back(true); + v.push_back(true); + let (has, index) = v.index_of(&true); + assert!(has); + assert!(index == 1); +} - #[test] - fun length() { - let mut empty = vector[]; - assert!(empty.length() == 0); - let mut i = 0; - let max_len = 42; - while (i < max_len) { - empty.push_back(i); - assert!(empty.length() == i + 1, i); - i = i + 1; - } +#[test] +fun length() { + let mut empty = vector[]; + assert!(empty.length() == 0); + let mut i = 0; + let max_len = 42; + while (i < max_len) { + empty.push_back(i); + assert!(empty.length() == i + 1, i); + i = i + 1; } +} - #[test] - fun pop_push_back() { - let mut v = vector[]; - let mut i = 0; - let max_len = 42; - - while (i < max_len) { - v.push_back(i); - i = i + 1; - }; - - while (i > 0) { - assert!(v.pop_back() == i - 1, i); - i = i - 1; - }; - } +#[test] +fun pop_push_back() { + let mut v = vector[]; + let mut i = 0; + let max_len = 42; + + while (i < max_len) { + v.push_back(i); + i = i + 1; + }; + + while (i > 0) { + assert!(v.pop_back() == i - 1, i); + i = i - 1; + }; +} - #[test_only] - fun test_natives_with_type(mut x1: T, mut x2: T): (T, T) { - let mut v = vector[]; - assert!(v.length() == 0); - v.push_back(x1); - assert!(v.length() == 1); - v.push_back(x2); - assert!(v.length() == 2); - v.swap(0, 1); - x1 = v.pop_back(); - assert!(v.length() == 1); - x2 = v.pop_back(); - assert!(v.length() == 0); - v.destroy_empty(); - (x1, x2) - } +#[test_only] +fun test_natives_with_type(mut x1: T, mut x2: T): (T, T) { + let mut v = vector[]; + assert!(v.length() == 0); + v.push_back(x1); + assert!(v.length() == 1); + v.push_back(x2); + assert!(v.length() == 2); + v.swap(0, 1); + x1 = v.pop_back(); + assert!(v.length() == 1); + x2 = v.pop_back(); + assert!(v.length() == 0); + v.destroy_empty(); + (x1, x2) +} - #[test] - fun test_natives_with_different_instantiations() { - test_natives_with_type(1u8, 2u8); - test_natives_with_type(45356u16, 25345u16); - test_natives_with_type(45356u32, 28768867u32); - test_natives_with_type(1u64, 2u64); - test_natives_with_type(1u128, 2u128); - test_natives_with_type(45356u256, 253458768867u256); - test_natives_with_type(true, false); - test_natives_with_type
(@0x1, @0x2); - - test_natives_with_type>(vector[], vector[]); - - test_natives_with_type(Droppable{}, Droppable{}); - (NotDroppable {}, NotDroppable {}) = test_natives_with_type( +#[test] +fun test_natives_with_different_instantiations() { + test_natives_with_type(1u8, 2u8); + test_natives_with_type(45356u16, 25345u16); + test_natives_with_type(45356u32, 28768867u32); + test_natives_with_type(1u64, 2u64); + test_natives_with_type(1u128, 2u128); + test_natives_with_type(45356u256, 253458768867u256); + test_natives_with_type(true, false); + test_natives_with_type
(@0x1, @0x2); + + test_natives_with_type>(vector[], vector[]); + + test_natives_with_type(Droppable {}, Droppable {}); + (NotDroppable {}, NotDroppable {}) = + test_natives_with_type( + NotDroppable {}, NotDroppable {}, - NotDroppable {} ); - } +} - #[test] - fun test_insert() { - let mut v = vector[7]; - v.insert(6, 0); - assert!(v == vector[6, 7]); +#[test] +fun test_insert() { + let mut v = vector[7]; + v.insert(6, 0); + assert!(v == vector[6, 7]); - let mut v = vector[7, 9]; - v.insert(8, 1); - assert!(v == vector[7, 8, 9]); + let mut v = vector[7, 9]; + v.insert(8, 1); + assert!(v == vector[7, 8, 9]); - let mut v = vector[6, 7]; - v.insert(5, 0); - assert!(v == vector[5, 6, 7]); + let mut v = vector[6, 7]; + v.insert(5, 0); + assert!(v == vector[5, 6, 7]); - let mut v = vector[5, 6, 8]; - v.insert(7, 2); - assert!(v == vector[5, 6, 7, 8]); - } + let mut v = vector[5, 6, 8]; + v.insert(7, 2); + assert!(v == vector[5, 6, 7, 8]); +} - #[test] - fun insert_at_end() { - let mut v = vector[]; - v.insert(6, 0); - assert!(v == vector[6]); +#[test] +fun insert_at_end() { + let mut v = vector[]; + v.insert(6, 0); + assert!(v == vector[6]); - v.insert(7, 1); - assert!(v == vector[6, 7]); - } + v.insert(7, 1); + assert!(v == vector[6, 7]); +} - #[test] - #[expected_failure(abort_code = std::vector::EINDEX_OUT_OF_BOUNDS)] - fun insert_out_of_range() { - let mut v = vector[7]; - v.insert(6, 2); - } +#[test] +#[expected_failure(abort_code = std::vector::EINDEX_OUT_OF_BOUNDS)] +fun insert_out_of_range() { + let mut v = vector[7]; + v.insert(6, 2); +} - #[test] - fun size_limit_ok() { - let mut v = vector[]; - let mut i = 0; - // Limit is currently 1024 * 54 - let max_len = 1024 * 53; - - while (i < max_len) { - v.push_back(i); - i = i + 1; - }; - } +#[test] +fun size_limit_ok() { + let mut v = vector[]; + let mut i = 0; + // Limit is currently 1024 * 54 + let max_len = 1024 * 53; + + while (i < max_len) { + v.push_back(i); + i = i + 1; + }; +} - #[test] - #[expected_failure(out_of_gas, location = Self)] - fun size_limit_fail() { - let mut v = vector[]; - let mut i = 0; - // Choose value beyond limit - let max_len = 1024 * 1024; - - while (i < max_len) { - v.push_back(i); - i = i + 1; - }; - } +#[test] +#[expected_failure(out_of_gas, location = Self)] +fun size_limit_fail() { + let mut v = vector[]; + let mut i = 0; + // Choose value beyond limit + let max_len = 1024 * 1024; + + while (i < max_len) { + v.push_back(i); + i = i + 1; + }; +} - #[test] - fun test_string_aliases() { - assert!(b"hello_world".to_string().length() == 11); - assert!(b"hello_world".try_to_string().is_some()); +#[test] +fun test_string_aliases() { + assert!(b"hello_world".to_string().length() == 11); + assert!(b"hello_world".try_to_string().is_some()); - assert!(b"hello_world".to_ascii_string().length() == 11); - assert!(b"hello_world".try_to_ascii_string().is_some()); - } + assert!(b"hello_world".to_ascii_string().length() == 11); + assert!(b"hello_world".try_to_ascii_string().is_some()); +} - // === Macros === +// === Macros === - #[test] - fun test_destroy_macro() { - vector[].destroy!(|_| assert!(false)); // very funky +#[test] +fun test_destroy_macro() { + vector[].destroy!(|_| assert!(false)); // very funky - let mut acc = 0; - vector[10, 20, 30, 40].destroy!(|e| acc = acc + e); - assert!(acc == 100); - } + let mut acc = 0; + vector[10, 20, 30, 40].destroy!(|e| acc = acc + e); + assert!(acc == 100); +} - #[test] - fun test_count_macro() { - assert!(vector[].count!(|e| *e == 2) == 0); - assert!(vector[0, 1, 2, 3].count!(|e| *e == 2) == 1); - assert!(vector[0, 1, 2, 3].count!(|e| *e % 2 == 0) == vector[0, 2].length()); - } +#[test] +fun test_count_macro() { + assert!(vector[].count!(|e| *e == 2) == 0); + assert!(vector[0, 1, 2, 3].count!(|e| *e == 2) == 1); + assert!(vector[0, 1, 2, 3].count!(|e| *e % 2 == 0) == vector[0, 2].length()); +} - #[test] - fun test_tabulate_macro() { - let v = vector::tabulate!(10, |i| i); - assert!(v == vector[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); +#[test] +fun test_tabulate_macro() { + let v = vector::tabulate!(10, |i| i); + assert!(v == vector[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); - let v = vector::tabulate!(5, |i| 10 - i); - assert!(v == vector[10, 9, 8, 7, 6]); + let v = vector::tabulate!(5, |i| 10 - i); + assert!(v == vector[10, 9, 8, 7, 6]); - let v = vector::tabulate!(0, |i| i); - assert!(v == vector[]); - } + let v = vector::tabulate!(0, |i| i); + assert!(v == vector[]); +} - #[test] - fun test_do_macro() { - vector[].do!(|_| assert!(false)); // should never run - vector[].do_ref!(|_| assert!(false)); - vector[].do_mut!(|_| assert!(false)); +#[test] +fun test_do_macro() { + vector[].do!(|_| assert!(false)); // should never run + vector[].do_ref!(|_| assert!(false)); + vector[].do_mut!(|_| assert!(false)); - let mut acc = 0; - vector[10, 20, 30, 40].do!(|e| acc = acc + e); - assert!(acc == 100); + let mut acc = 0; + vector[10, 20, 30, 40].do!(|e| acc = acc + e); + assert!(acc == 100); - let vec = vector[10, 20]; - vec.do!(|e| acc = acc + e); - assert!(vector[10, 20] == vec); + let vec = vector[10, 20]; + vec.do!(|e| acc = acc + e); + assert!(vector[10, 20] == vec); - let mut acc = 0; - vector[10, 20, 30, 40].do_ref!(|e| acc = acc + *e); - assert!(acc == 100); + let mut acc = 0; + vector[10, 20, 30, 40].do_ref!(|e| acc = acc + *e); + assert!(acc == 100); - let mut vec = vector[10, 20, 30, 40]; - vec.do_mut!(|e| *e = *e + 1); - assert!(vec == vector[11, 21, 31, 41]); - } + let mut vec = vector[10, 20, 30, 40]; + vec.do_mut!(|e| *e = *e + 1); + assert!(vec == vector[11, 21, 31, 41]); +} - #[test] - fun test_map_macro() { - let e = vector[]; - assert!(e.map!(|e| e + 1) == vector[]); +#[test] +fun test_map_macro() { + let e = vector[]; + assert!(e.map!(|e| e + 1) == vector[]); - let r = vector[0, 1, 2, 3]; - assert!(r.map!(|e| e + 1) == vector[1, 2, 3, 4]); + let r = vector[0, 1, 2, 3]; + assert!(r.map!(|e| e + 1) == vector[1, 2, 3, 4]); - let r = vector[0, 1, 2, 3]; - assert!(r.map_ref!(|e| *e * 2) == vector[0, 2, 4, 6]); - } + let r = vector[0, 1, 2, 3]; + assert!(r.map_ref!(|e| *e * 2) == vector[0, 2, 4, 6]); +} - #[test] - fun filter_macro() { - let e = vector[]; - assert!(e.filter!(|e| *e % 2 == 0) == vector[]); +#[test] +fun filter_macro() { + let e = vector[]; + assert!(e.filter!(|e| *e % 2 == 0) == vector[]); - let r = vector[0, 1, 2, 3]; - assert!(r.filter!(|e| *e % 2 == 0) == vector[0, 2]); - } + let r = vector[0, 1, 2, 3]; + assert!(r.filter!(|e| *e % 2 == 0) == vector[0, 2]); +} - #[test] - fun partition_macro() { - let e = vector[]; - let (even, odd) = e.partition!(|e| (*e % 2) == 0); - assert!(even == vector[]); - assert!(odd == vector[]); - - let r = vector[0, 1, 2, 3]; - let (even, odd) = r.partition!(|e| (*e % 2) == 0); - assert!(even == vector[0, 2]); - assert!(odd == vector[1, 3]); - } +#[test] +fun partition_macro() { + let e = vector[]; + let (even, odd) = e.partition!(|e| (*e % 2) == 0); + assert!(even == vector[]); + assert!(odd == vector[]); + + let r = vector[0, 1, 2, 3]; + let (even, odd) = r.partition!(|e| (*e % 2) == 0); + assert!(even == vector[0, 2]); + assert!(odd == vector[1, 3]); +} - #[test] - fun find_index_macro() { - let e = vector[]; - assert!(e.find_index!(|e| *e == 0).is_none()); - assert!(e.find_index!(|_| true).is_none()); +#[test] +fun find_index_macro() { + let e = vector[]; + assert!(e.find_index!(|e| *e == 0).is_none()); + assert!(e.find_index!(|_| true).is_none()); - let r = vector[0, 10, 100, 1_000]; - assert!(r.find_index!(|e| *e == 100).destroy_some() == 2); - assert!(r.find_index!(|e| *e == 10_000).is_none()); + let r = vector[0, 10, 100, 1_000]; + assert!(r.find_index!(|e| *e == 100).destroy_some() == 2); + assert!(r.find_index!(|e| *e == 10_000).is_none()); - let v = vector[Droppable{}, Droppable{}]; - let idx = v.find_index!(|e| e == Droppable{}); - assert!(idx.destroy_some() == 0); - assert!(&v[idx.destroy_some()] == Droppable{}); - } + let v = vector[Droppable {}, Droppable {}]; + let idx = v.find_index!(|e| e == Droppable{}); + assert!(idx.destroy_some() == 0); + assert!(&v[idx.destroy_some()] == Droppable{}); +} - #[test] - fun fold_macro() { - let e = vector[]; - assert!(e.fold!(0, |acc, e| acc + e) == 0); +#[test] +fun fold_macro() { + let e = vector[]; + assert!(e.fold!(0, |acc, e| acc + e) == 0); - let r = vector[0, 1, 2, 3]; - assert!(r.fold!(10, |acc, e| acc + e) == 16); - } + let r = vector[0, 1, 2, 3]; + assert!(r.fold!(10, |acc, e| acc + e) == 16); +} - #[test] - fun test_flatten() { - assert!(vector>[].flatten().is_empty()); - assert!(vector>[vector[], vector[]].flatten().is_empty()); - assert!(vector[vector[1]].flatten() == vector[1]); - assert!(vector[vector[1], vector[]].flatten() == vector[1]); - assert!(vector[vector[1], vector[2]].flatten() == vector[1, 2]); - assert!(vector[vector[1], vector[2, 3]].flatten() == vector[1, 2, 3]); - } +#[test] +fun test_flatten() { + assert!(vector>[].flatten().is_empty()); + assert!(vector>[vector[], vector[]].flatten().is_empty()); + assert!(vector[vector[1]].flatten() == vector[1]); + assert!(vector[vector[1], vector[]].flatten() == vector[1]); + assert!(vector[vector[1], vector[2]].flatten() == vector[1, 2]); + assert!(vector[vector[1], vector[2, 3]].flatten() == vector[1, 2, 3]); +} - #[test] - fun any_all_macro() { - assert!(vector[].any!(|e| *e == 2) == false); - assert!(vector[].all!(|e| *e == 2) == true); - assert!(vector[0, 1, 2, 3].any!(|e| *e == 2)); - assert!(!vector[0, 1, 2, 3].any!(|e| *e == 4)); - assert!(vector[0, 1, 2, 3].all!(|e| *e < 4)); - assert!(!vector[0, 1, 2, 3].all!(|e| *e < 3)); - } +#[test] +fun any_all_macro() { + assert!(vector[].any!(|e| *e == 2) == false); + assert!(vector[].all!(|e| *e == 2) == true); + assert!(vector[0, 1, 2, 3].any!(|e| *e == 2)); + assert!(!vector[0, 1, 2, 3].any!(|e| *e == 4)); + assert!(vector[0, 1, 2, 3].all!(|e| *e < 4)); + assert!(!vector[0, 1, 2, 3].all!(|e| *e < 3)); +} - #[test, expected_failure] - fun zip_do_macro_fail() { - let v1 = vector[1u64]; - let v2 = vector[4u64, 5]; - let mut res = vector[]; - v1.zip_do!(v2, |a, b| res.push_back(a + b)); - } +#[test, expected_failure] +fun zip_do_macro_fail() { + let v1 = vector[1u64]; + let v2 = vector[4u64, 5]; + let mut res = vector[]; + v1.zip_do!(v2, |a, b| res.push_back(a + b)); +} - #[test] - fun zip_do_macro() { - let v1 = vector[1u64, 2, 3]; - let v2 = vector[4u64, 5, 6]; - let mut res = vector[]; - v1.zip_do!(v2, |a, b| res.push_back(a + b)); - assert!(res == vector[5, 7, 9]); - } +#[test] +fun zip_do_macro() { + let v1 = vector[1u64, 2, 3]; + let v2 = vector[4u64, 5, 6]; + let mut res = vector[]; + v1.zip_do!(v2, |a, b| res.push_back(a + b)); + assert!(res == vector[5, 7, 9]); +} - #[test, expected_failure] - fun zip_do_reverse_macro_fail() { - let v1 = vector[1u64]; - let v2 = vector[4u64, 5]; - let mut res = vector[]; - v2.zip_do_reverse!(v1, |a, b| res.push_back(a + b)); - } +#[test, expected_failure] +fun zip_do_reverse_macro_fail() { + let v1 = vector[1u64]; + let v2 = vector[4u64, 5]; + let mut res = vector[]; + v2.zip_do_reverse!(v1, |a, b| res.push_back(a + b)); +} - #[test] - fun zip_do_reverse_macro() { - let v1 = vector[1u64, 2, 3]; - let v2 = vector[4u64, 5, 6]; - let mut res = vector[]; - v2.zip_do_reverse!(v1, |a, b| res.push_back(a + b)); - assert!(res == vector[9, 7, 5]); - } +#[test] +fun zip_do_reverse_macro() { + let v1 = vector[1u64, 2, 3]; + let v2 = vector[4u64, 5, 6]; + let mut res = vector[]; + v2.zip_do_reverse!(v1, |a, b| res.push_back(a + b)); + assert!(res == vector[9, 7, 5]); +} - #[test, expected_failure] - fun zip_do_ref_macro_fail() { - let v1 = vector[1u64]; - let v2 = vector[4u64, 5]; - let mut res = vector[]; - v2.zip_do_ref!(&v1, |a, b| res.push_back(*a + *b)); - } +#[test, expected_failure] +fun zip_do_ref_macro_fail() { + let v1 = vector[1u64]; + let v2 = vector[4u64, 5]; + let mut res = vector[]; + v2.zip_do_ref!(&v1, |a, b| res.push_back(*a + *b)); +} - #[test] - fun zip_do_ref_macro() { - let v1 = vector[1u64, 2, 3]; - let v2 = vector[4u64, 5, 6]; - let mut res = vector[]; - v1.zip_do_ref!(&v2, |a, b| res.push_back(*a + *b)); - assert!(res == vector[5, 7, 9]); - } +#[test] +fun zip_do_ref_macro() { + let v1 = vector[1u64, 2, 3]; + let v2 = vector[4u64, 5, 6]; + let mut res = vector[]; + v1.zip_do_ref!(&v2, |a, b| res.push_back(*a + *b)); + assert!(res == vector[5, 7, 9]); +} - #[test, expected_failure] - fun zip_do_mut_macro_fail() { - let mut v1 = vector[1u64]; - let mut v2 = vector[4u64, 5]; - v1.zip_do_mut!(&mut v2, |a, b| { - let c = *a; - *a = *b; - *b = c; - }); - } +#[test, expected_failure] +fun zip_do_mut_macro_fail() { + let mut v1 = vector[1u64]; + let mut v2 = vector[4u64, 5]; + v1.zip_do_mut!(&mut v2, |a, b| { + let c = *a; + *a = *b; + *b = c; + }); +} - #[test] - fun zip_do_mut_macro() { - let mut v1 = vector[1u64, 2, 3]; - let mut v2 = vector[4u64, 5, 6]; - v1.zip_do_mut!(&mut v2, |a, b| { - let c = *a; - *a = *b; - *b = c; - }); - assert!(v1 == vector[4, 5, 6]); - assert!(v2 == vector[1, 2, 3]); - } +#[test] +fun zip_do_mut_macro() { + let mut v1 = vector[1u64, 2, 3]; + let mut v2 = vector[4u64, 5, 6]; + v1.zip_do_mut!(&mut v2, |a, b| { + let c = *a; + *a = *b; + *b = c; + }); + assert!(v1 == vector[4, 5, 6]); + assert!(v2 == vector[1, 2, 3]); +} - #[test] - fun zip_map_macro() { - let v1 = vector[1u64, 2, 3]; - let v2 = vector[4u64, 5, 6]; - assert!(v1.zip_map!(v2, |a, b| a + b) == vector[5, 7, 9]); - } +#[test] +fun zip_map_macro() { + let v1 = vector[1u64, 2, 3]; + let v2 = vector[4u64, 5, 6]; + assert!(v1.zip_map!(v2, |a, b| a + b) == vector[5, 7, 9]); +} - #[test] - fun zip_map_ref_macro() { - let v1 = vector[1u64, 2, 3]; - let v2 = vector[4u64, 5, 6]; - assert!(v2.zip_map_ref!(&v1, |a, b| *a + *b) == vector[5, 7, 9]); - } +#[test] +fun zip_map_ref_macro() { + let v1 = vector[1u64, 2, 3]; + let v2 = vector[4u64, 5, 6]; + assert!(v2.zip_map_ref!(&v1, |a, b| *a + *b) == vector[5, 7, 9]); } diff --git a/crates/sui-framework/packages/sui-framework/sources/address.move b/crates/sui-framework/packages/sui-framework/sources/address.move index 129908910df05..51d65d7b98b5a 100644 --- a/crates/sui-framework/packages/sui-framework/sources/address.move +++ b/crates/sui-framework/packages/sui-framework/sources/address.move @@ -2,85 +2,85 @@ // SPDX-License-Identifier: Apache-2.0 #[defines_primitive(address)] -module sui::address { - use sui::hex; - use std::ascii; - use std::bcs; - use std::string; - - /// Allows calling `.to_id()` on an address to get its `ID`. - public use fun sui::object::id_from_address as address.to_id; - - /// The length of an address, in bytes - const LENGTH: u64 = 32; - - // The largest integer that can be represented with 32 bytes: 2^(8*32) - 1 - const MAX: u256 = 115792089237316195423570985008687907853269984665640564039457584007913129639935; - - #[allow(unused_const)] - /// Error from `from_bytes` when it is supplied too many or too few bytes. - const EAddressParseError: u64 = 0; - - /// Convert `a` into a u256 by interpreting `a` as the bytes of a big-endian integer - /// (e.g., `to_u256(0x1) == 1`) - public native fun to_u256(a: address): u256; - - /// Convert `n` into an address by encoding it as a big-endian integer (e.g., `from_u256(1) = @0x1`) - /// Aborts if `n` > `MAX_ADDRESS` - public native fun from_u256(n: u256): address; - - /// Convert `bytes` into an address. - /// Aborts with `EAddressParseError` if the length of `bytes` is not 32 - public native fun from_bytes(bytes: vector): address; - - /// Convert `a` into BCS-encoded bytes. - public fun to_bytes(a: address): vector { - bcs::to_bytes(&a) - } - - /// Convert `a` to a hex-encoded ASCII string - public fun to_ascii_string(a: address): ascii::String { - hex::encode(to_bytes(a)).to_ascii_string() - } - - /// Convert `a` to a hex-encoded string - public fun to_string(a: address): string::String { - to_ascii_string(a).to_string() - } - - /// Converts an ASCII string to an address, taking the numerical value for each character. The - /// string must be Base16 encoded, and thus exactly 64 characters long. - /// For example, the string "00000000000000000000000000000000000000000000000000000000DEADB33F" - /// will be converted to the address @0xDEADB33F. - /// Aborts with `EAddressParseError` if the length of `s` is not 64, - /// or if an invalid character is encountered. - public fun from_ascii_bytes(bytes: &vector): address { - assert!(bytes.length() == 64, EAddressParseError); - let mut hex_bytes = vector[]; - let mut i = 0; - while (i < 64) { - let hi = hex_char_value(bytes[i]); - let lo = hex_char_value(bytes[i+1]); - hex_bytes.push_back((hi << 4) | lo); - i = i + 2; - }; - from_bytes(hex_bytes) - } - - fun hex_char_value(c: u8): u8 { - if (c >= 48 && c <= 57) c - 48 // 0-9 - else if (c >= 65 && c <= 70) c - 55 // A-F - else if (c >= 97 && c <= 102) c - 87 // a-f - else abort EAddressParseError - } - - /// Length of a Sui address in bytes - public fun length(): u64 { - LENGTH - } - - /// Largest possible address - public fun max(): u256 { - MAX - } +module sui::address; + +use std::ascii; +use std::bcs; +use std::string; +use sui::hex; + +/// Allows calling `.to_id()` on an address to get its `ID`. +public use fun sui::object::id_from_address as address.to_id; + +/// The length of an address, in bytes +const LENGTH: u64 = 32; + +// The largest integer that can be represented with 32 bytes: 2^(8*32) - 1 +const MAX: u256 = 115792089237316195423570985008687907853269984665640564039457584007913129639935; + +#[allow(unused_const)] +/// Error from `from_bytes` when it is supplied too many or too few bytes. +const EAddressParseError: u64 = 0; + +/// Convert `a` into a u256 by interpreting `a` as the bytes of a big-endian integer +/// (e.g., `to_u256(0x1) == 1`) +public native fun to_u256(a: address): u256; + +/// Convert `n` into an address by encoding it as a big-endian integer (e.g., `from_u256(1) = @0x1`) +/// Aborts if `n` > `MAX_ADDRESS` +public native fun from_u256(n: u256): address; + +/// Convert `bytes` into an address. +/// Aborts with `EAddressParseError` if the length of `bytes` is not 32 +public native fun from_bytes(bytes: vector): address; + +/// Convert `a` into BCS-encoded bytes. +public fun to_bytes(a: address): vector { + bcs::to_bytes(&a) +} + +/// Convert `a` to a hex-encoded ASCII string +public fun to_ascii_string(a: address): ascii::String { + hex::encode(to_bytes(a)).to_ascii_string() +} + +/// Convert `a` to a hex-encoded string +public fun to_string(a: address): string::String { + to_ascii_string(a).to_string() +} + +/// Converts an ASCII string to an address, taking the numerical value for each character. The +/// string must be Base16 encoded, and thus exactly 64 characters long. +/// For example, the string "00000000000000000000000000000000000000000000000000000000DEADB33F" +/// will be converted to the address @0xDEADB33F. +/// Aborts with `EAddressParseError` if the length of `s` is not 64, +/// or if an invalid character is encountered. +public fun from_ascii_bytes(bytes: &vector): address { + assert!(bytes.length() == 64, EAddressParseError); + let mut hex_bytes = vector[]; + let mut i = 0; + while (i < 64) { + let hi = hex_char_value(bytes[i]); + let lo = hex_char_value(bytes[i+1]); + hex_bytes.push_back((hi << 4) | lo); + i = i + 2; + }; + from_bytes(hex_bytes) +} + +fun hex_char_value(c: u8): u8 { + if (c >= 48 && c <= 57) c - 48 // 0-9 + else if (c >= 65 && c <= 70) c - 55 // A-F + else if (c >= 97 && c <= 102) c - 87 // a-f + else abort EAddressParseError +} + +/// Length of a Sui address in bytes +public fun length(): u64 { + LENGTH +} + +/// Largest possible address +public fun max(): u256 { + MAX } diff --git a/crates/sui-framework/packages/sui-framework/sources/authenticator_state.move b/crates/sui-framework/packages/sui-framework/sources/authenticator_state.move index beca79feea46c..30f9f5402d0eb 100644 --- a/crates/sui-framework/packages/sui-framework/sources/authenticator_state.move +++ b/crates/sui-framework/packages/sui-framework/sources/authenticator_state.move @@ -7,389 +7,377 @@ // // This module is not currently accessible from user contracts, and is used only to record the JWK // state to the chain for auditability + restore from snapshot purposes. -module sui::authenticator_state { - use std::string; - use sui::dynamic_field; - use std::string::{String, utf8}; - - /// Sender is not @0x0 the system address. - const ENotSystemAddress: u64 = 0; - const EWrongInnerVersion: u64 = 1; - const EJwksNotSorted: u64 = 2; - - const CurrentVersion: u64 = 1; - - /// Singleton shared object which stores the global authenticator state. - /// The actual state is stored in a dynamic field of type AuthenticatorStateInner to support - /// future versions of the authenticator state. - public struct AuthenticatorState has key { - id: UID, - version: u64, - } +module sui::authenticator_state; - public struct AuthenticatorStateInner has store { - version: u64, +use std::string::{Self, String, utf8}; +use sui::dynamic_field; - /// List of currently active JWKs. - active_jwks: vector, - } +/// Sender is not @0x0 the system address. +const ENotSystemAddress: u64 = 0; +const EWrongInnerVersion: u64 = 1; +const EJwksNotSorted: u64 = 2; - #[allow(unused_field)] - /// Must match the JWK struct in fastcrypto-zkp - public struct JWK has store, drop, copy { - kty: String, - e: String, - n: String, - alg: String, - } +const CurrentVersion: u64 = 1; - #[allow(unused_field)] - /// Must match the JwkId struct in fastcrypto-zkp - public struct JwkId has store, drop, copy { - iss: String, - kid: String, - } +/// Singleton shared object which stores the global authenticator state. +/// The actual state is stored in a dynamic field of type AuthenticatorStateInner to support +/// future versions of the authenticator state. +public struct AuthenticatorState has key { + id: UID, + version: u64, +} - #[allow(unused_field)] - public struct ActiveJwk has store, drop, copy { - jwk_id: JwkId, - jwk: JWK, - epoch: u64, - } +public struct AuthenticatorStateInner has store { + version: u64, + /// List of currently active JWKs. + active_jwks: vector, +} - #[test_only] - public fun create_active_jwk(iss: String, kid: String, kty: String, epoch: u64): ActiveJwk { - ActiveJwk { - jwk_id: JwkId { - iss: iss, - kid: kid, - }, - jwk: JWK { - kty: kty, - e: utf8(b"AQAB"), - n: utf8(b"test"), - alg: utf8(b"RS256"), - }, - epoch, - } - } +#[allow(unused_field)] +/// Must match the JWK struct in fastcrypto-zkp +public struct JWK has store, drop, copy { + kty: String, + e: String, + n: String, + alg: String, +} - fun active_jwk_equal(a: &ActiveJwk, b: &ActiveJwk): bool { - // note: epoch is ignored - jwk_equal(&a.jwk, &b.jwk) && jwk_id_equal(&a.jwk_id, &b.jwk_id) - } +#[allow(unused_field)] +/// Must match the JwkId struct in fastcrypto-zkp +public struct JwkId has store, drop, copy { + iss: String, + kid: String, +} - fun jwk_equal(a: &JWK, b: &JWK): bool { - (&a.kty == &b.kty) && - (&a.e == &b.e) && - (&a.n == &b.n) && - (&a.alg == &b.alg) - } +#[allow(unused_field)] +public struct ActiveJwk has store, drop, copy { + jwk_id: JwkId, + jwk: JWK, + epoch: u64, +} - fun jwk_id_equal(a: &JwkId, b: &JwkId): bool { - (&a.iss == &b.iss) && (&a.kid == &b.kid) +#[test_only] +public fun create_active_jwk(iss: String, kid: String, kty: String, epoch: u64): ActiveJwk { + ActiveJwk { + jwk_id: JwkId { + iss: iss, + kid: kid, + }, + jwk: JWK { + kty: kty, + e: utf8(b"AQAB"), + n: utf8(b"test"), + alg: utf8(b"RS256"), + }, + epoch, } +} - // Compare the underlying byte arrays lexicographically. Since the strings may be utf8 this - // ordering is not necessarily the same as the string ordering, but we just need some - // canonical that is cheap to compute. - fun string_bytes_lt(a: &String, b: &String): bool { - let a_bytes = a.as_bytes(); - let b_bytes = b.as_bytes(); - - if (a_bytes.length() < b_bytes.length()) { - true - } else if (a_bytes.length() > b_bytes.length()) { - false - } else { - let mut i = 0; - while (i < a_bytes.length()) { - let a_byte = a_bytes[i]; - let b_byte = b_bytes[i]; - if (a_byte < b_byte) { - return true - } else if (a_byte > b_byte) { - return false - }; - i = i + 1; - }; - // all bytes are equal - false - } - } +fun active_jwk_equal(a: &ActiveJwk, b: &ActiveJwk): bool { + // note: epoch is ignored + jwk_equal(&a.jwk, &b.jwk) && jwk_id_equal(&a.jwk_id, &b.jwk_id) +} - fun jwk_lt(a: &ActiveJwk, b: &ActiveJwk): bool { - // note: epoch is ignored - if (&a.jwk_id.iss != &b.jwk_id.iss) { - return string_bytes_lt(&a.jwk_id.iss, &b.jwk_id.iss) - }; - if (&a.jwk_id.kid != &b.jwk_id.kid) { - return string_bytes_lt(&a.jwk_id.kid, &b.jwk_id.kid) - }; - if (&a.jwk.kty != &b.jwk.kty) { - return string_bytes_lt(&a.jwk.kty, &b.jwk.kty) - }; - if (&a.jwk.e != &b.jwk.e) { - return string_bytes_lt(&a.jwk.e, &b.jwk.e) - }; - if (&a.jwk.n != &b.jwk.n) { - return string_bytes_lt(&a.jwk.n, &b.jwk.n) +fun jwk_equal(a: &JWK, b: &JWK): bool { + (&a.kty == &b.kty) && + (&a.e == &b.e) && + (&a.n == &b.n) && + (&a.alg == &b.alg) +} + +fun jwk_id_equal(a: &JwkId, b: &JwkId): bool { + (&a.iss == &b.iss) && (&a.kid == &b.kid) +} + +// Compare the underlying byte arrays lexicographically. Since the strings may be utf8 this +// ordering is not necessarily the same as the string ordering, but we just need some +// canonical that is cheap to compute. +fun string_bytes_lt(a: &String, b: &String): bool { + let a_bytes = a.as_bytes(); + let b_bytes = b.as_bytes(); + + if (a_bytes.length() < b_bytes.length()) { + true + } else if (a_bytes.length() > b_bytes.length()) { + false + } else { + let mut i = 0; + while (i < a_bytes.length()) { + let a_byte = a_bytes[i]; + let b_byte = b_bytes[i]; + if (a_byte < b_byte) { + return true + } else if (a_byte > b_byte) { + return false + }; + i = i + 1; }; - string_bytes_lt(&a.jwk.alg, &b.jwk.alg) + // all bytes are equal + false } +} - #[allow(unused_function)] - /// Create and share the AuthenticatorState object. This function is call exactly once, when - /// the authenticator state object is first created. - /// Can only be called by genesis or change_epoch transactions. - fun create(ctx: &TxContext) { - assert!(ctx.sender() == @0x0, ENotSystemAddress); +fun jwk_lt(a: &ActiveJwk, b: &ActiveJwk): bool { + // note: epoch is ignored + if (&a.jwk_id.iss != &b.jwk_id.iss) { + return string_bytes_lt(&a.jwk_id.iss, &b.jwk_id.iss) + }; + if (&a.jwk_id.kid != &b.jwk_id.kid) { + return string_bytes_lt(&a.jwk_id.kid, &b.jwk_id.kid) + }; + if (&a.jwk.kty != &b.jwk.kty) { + return string_bytes_lt(&a.jwk.kty, &b.jwk.kty) + }; + if (&a.jwk.e != &b.jwk.e) { + return string_bytes_lt(&a.jwk.e, &b.jwk.e) + }; + if (&a.jwk.n != &b.jwk.n) { + return string_bytes_lt(&a.jwk.n, &b.jwk.n) + }; + string_bytes_lt(&a.jwk.alg, &b.jwk.alg) +} - let version = CurrentVersion; +#[allow(unused_function)] +/// Create and share the AuthenticatorState object. This function is call exactly once, when +/// the authenticator state object is first created. +/// Can only be called by genesis or change_epoch transactions. +fun create(ctx: &TxContext) { + assert!(ctx.sender() == @0x0, ENotSystemAddress); - let inner = AuthenticatorStateInner { - version, - active_jwks: vector[], - }; + let version = CurrentVersion; - let mut self = AuthenticatorState { - id: object::authenticator_state(), - version, - }; + let inner = AuthenticatorStateInner { + version, + active_jwks: vector[], + }; - dynamic_field::add(&mut self.id, version, inner); - transfer::share_object(self); - } + let mut self = AuthenticatorState { + id: object::authenticator_state(), + version, + }; - fun load_inner_mut( - self: &mut AuthenticatorState, - ): &mut AuthenticatorStateInner { - let version = self.version; + dynamic_field::add(&mut self.id, version, inner); + transfer::share_object(self); +} - // replace this with a lazy update function when we add a new version of the inner object. - assert!(version == CurrentVersion, EWrongInnerVersion); +fun load_inner_mut(self: &mut AuthenticatorState): &mut AuthenticatorStateInner { + let version = self.version; - let inner: &mut AuthenticatorStateInner = dynamic_field::borrow_mut(&mut self.id, self.version); + // replace this with a lazy update function when we add a new version of the inner object. + assert!(version == CurrentVersion, EWrongInnerVersion); - assert!(inner.version == version, EWrongInnerVersion); - inner - } + let inner: &mut AuthenticatorStateInner = dynamic_field::borrow_mut(&mut self.id, self.version); - fun load_inner( - self: &AuthenticatorState, - ): &AuthenticatorStateInner { - let version = self.version; + assert!(inner.version == version, EWrongInnerVersion); + inner +} - // replace this with a lazy update function when we add a new version of the inner object. - assert!(version == CurrentVersion, EWrongInnerVersion); +fun load_inner(self: &AuthenticatorState): &AuthenticatorStateInner { + let version = self.version; - let inner: &AuthenticatorStateInner = dynamic_field::borrow(&self.id, self.version); + // replace this with a lazy update function when we add a new version of the inner object. + assert!(version == CurrentVersion, EWrongInnerVersion); - assert!(inner.version == version, EWrongInnerVersion); - inner - } + let inner: &AuthenticatorStateInner = dynamic_field::borrow(&self.id, self.version); - fun check_sorted(new_active_jwks: &vector) { - let mut i = 0; - while (i < new_active_jwks.length() - 1) { - let a = &new_active_jwks[i]; - let b = &new_active_jwks[i + 1]; - assert!(jwk_lt(a, b), EJwksNotSorted); - i = i + 1; - }; - } + assert!(inner.version == version, EWrongInnerVersion); + inner +} - #[allow(unused_function)] - /// Record a new set of active_jwks. Called when executing the AuthenticatorStateUpdate system - /// transaction. The new input vector must be sorted and must not contain duplicates. - /// If a new JWK is already present, but with a previous epoch, then the epoch is updated to - /// indicate that the JWK has been validated in the current epoch and should not be expired. - fun update_authenticator_state( - self: &mut AuthenticatorState, - new_active_jwks: vector, - ctx: &TxContext, - ) { - // Validator will make a special system call with sender set as 0x0. - assert!(ctx.sender() == @0x0, ENotSystemAddress); - - check_sorted(&new_active_jwks); - let new_active_jwks = deduplicate(new_active_jwks); - - let inner = self.load_inner_mut(); - - let mut res = vector[]; - let mut i = 0; - let mut j = 0; - let active_jwks_len = inner.active_jwks.length(); - let new_active_jwks_len = new_active_jwks.length(); - - while (i < active_jwks_len && j < new_active_jwks_len) { - let old_jwk = &inner.active_jwks[i]; - let new_jwk = &new_active_jwks[j]; - - // when they are equal, push only one, but use the max epoch of the two - if (active_jwk_equal(old_jwk, new_jwk)) { - let mut jwk = *old_jwk; - jwk.epoch = old_jwk.epoch.max(new_jwk.epoch); - res.push_back(jwk); - i = i + 1; - j = j + 1; - } else if (jwk_id_equal(&old_jwk.jwk_id, &new_jwk.jwk_id)) { - // if only jwk_id is equal, then the key has changed. Providers should not send - // JWKs like this, but if they do, we must ignore the new JWK to avoid having a - // liveness / forking issues - res.push_back(*old_jwk); - i = i + 1; - j = j + 1; - } else if (jwk_lt(old_jwk, new_jwk)) { - res.push_back(*old_jwk); - i = i + 1; - } else { - res.push_back(*new_jwk); - j = j + 1; - } - }; +fun check_sorted(new_active_jwks: &vector) { + let mut i = 0; + while (i < new_active_jwks.length() - 1) { + let a = &new_active_jwks[i]; + let b = &new_active_jwks[i + 1]; + assert!(jwk_lt(a, b), EJwksNotSorted); + i = i + 1; + }; +} - while (i < active_jwks_len) { - res.push_back(inner.active_jwks[i]); +#[allow(unused_function)] +/// Record a new set of active_jwks. Called when executing the AuthenticatorStateUpdate system +/// transaction. The new input vector must be sorted and must not contain duplicates. +/// If a new JWK is already present, but with a previous epoch, then the epoch is updated to +/// indicate that the JWK has been validated in the current epoch and should not be expired. +fun update_authenticator_state( + self: &mut AuthenticatorState, + new_active_jwks: vector, + ctx: &TxContext, +) { + // Validator will make a special system call with sender set as 0x0. + assert!(ctx.sender() == @0x0, ENotSystemAddress); + + check_sorted(&new_active_jwks); + let new_active_jwks = deduplicate(new_active_jwks); + + let inner = self.load_inner_mut(); + + let mut res = vector[]; + let mut i = 0; + let mut j = 0; + let active_jwks_len = inner.active_jwks.length(); + let new_active_jwks_len = new_active_jwks.length(); + + while (i < active_jwks_len && j < new_active_jwks_len) { + let old_jwk = &inner.active_jwks[i]; + let new_jwk = &new_active_jwks[j]; + + // when they are equal, push only one, but use the max epoch of the two + if (active_jwk_equal(old_jwk, new_jwk)) { + let mut jwk = *old_jwk; + jwk.epoch = old_jwk.epoch.max(new_jwk.epoch); + res.push_back(jwk); i = i + 1; - }; - while (j < new_active_jwks_len) { - res.push_back(new_active_jwks[j]); j = j + 1; - }; - - inner.active_jwks = res; - } - - fun deduplicate(jwks: vector): vector { - let mut res = vector[]; - let mut i = 0; - let mut prev: Option = option::none(); - while (i < jwks.length()) { - let jwk = &jwks[i]; - if (prev.is_none()) { - prev.fill(jwk.jwk_id); - } else if (jwk_id_equal(prev.borrow(), &jwk.jwk_id)) { - // skip duplicate jwks in input - i = i + 1; - continue - } else { - *prev.borrow_mut() = jwk.jwk_id; - }; - res.push_back(*jwk); + } else if (jwk_id_equal(&old_jwk.jwk_id, &new_jwk.jwk_id)) { + // if only jwk_id is equal, then the key has changed. Providers should not send + // JWKs like this, but if they do, we must ignore the new JWK to avoid having a + // liveness / forking issues + res.push_back(*old_jwk); i = i + 1; - }; - res - } - - #[allow(unused_function)] - // Called directly by rust when constructing the ChangeEpoch transaction. - fun expire_jwks( - self: &mut AuthenticatorState, - // any jwk below this epoch is not retained - min_epoch: u64, - ctx: &TxContext) { - // This will only be called by sui_system::advance_epoch - assert!(ctx.sender() == @0x0, ENotSystemAddress); - - let inner = load_inner_mut(self); - - let len = inner.active_jwks.length(); - - // first we count how many jwks from each issuer are above the min_epoch - // and store the counts in a vector that parallels the (sorted) active_jwks vector - let mut issuer_max_epochs = vector[]; - let mut i = 0; - let mut prev_issuer: Option = option::none(); + j = j + 1; + } else if (jwk_lt(old_jwk, new_jwk)) { + res.push_back(*old_jwk); + i = i + 1; + } else { + res.push_back(*new_jwk); + j = j + 1; + } + }; + + while (i < active_jwks_len) { + res.push_back(inner.active_jwks[i]); + i = i + 1; + }; + while (j < new_active_jwks_len) { + res.push_back(new_active_jwks[j]); + j = j + 1; + }; + + inner.active_jwks = res; +} - while (i < len) { - let cur = &inner.active_jwks[i]; - let cur_iss = &cur.jwk_id.iss; - if (prev_issuer.is_none()) { - prev_issuer.fill(*cur_iss); - issuer_max_epochs.push_back(cur.epoch); - } else { - if (cur_iss == prev_issuer.borrow()) { - let back = issuer_max_epochs.length() - 1; - let prev_max_epoch = &mut issuer_max_epochs[back]; - *prev_max_epoch = (*prev_max_epoch).max(cur.epoch); - } else { - *prev_issuer.borrow_mut() = *cur_iss; - issuer_max_epochs.push_back(cur.epoch); - } - }; +fun deduplicate(jwks: vector): vector { + let mut res = vector[]; + let mut i = 0; + let mut prev: Option = option::none(); + while (i < jwks.length()) { + let jwk = &jwks[i]; + if (prev.is_none()) { + prev.fill(jwk.jwk_id); + } else if (jwk_id_equal(prev.borrow(), &jwk.jwk_id)) { + // skip duplicate jwks in input i = i + 1; + continue + } else { + *prev.borrow_mut() = jwk.jwk_id; }; + res.push_back(*jwk); + i = i + 1; + }; + res +} - // Now, filter out any JWKs that are below the min_epoch, unless that issuer has no - // JWKs >= the min_epoch, in which case we keep all of them. - let mut new_active_jwks: vector = vector[]; - let mut prev_issuer: Option = option::none(); - let mut i = 0; - let mut j = 0; - while (i < len) { - let jwk = &inner.active_jwks[i]; - let cur_iss = &jwk.jwk_id.iss; - - if (prev_issuer.is_none()) { - prev_issuer.fill(*cur_iss); - } else if (cur_iss != prev_issuer.borrow()) { +#[allow(unused_function)] +// Called directly by rust when constructing the ChangeEpoch transaction. +fun expire_jwks( + self: &mut AuthenticatorState, + // any jwk below this epoch is not retained + min_epoch: u64, + ctx: &TxContext, +) { + // This will only be called by sui_system::advance_epoch + assert!(ctx.sender() == @0x0, ENotSystemAddress); + + let inner = load_inner_mut(self); + + let len = inner.active_jwks.length(); + + // first we count how many jwks from each issuer are above the min_epoch + // and store the counts in a vector that parallels the (sorted) active_jwks vector + let mut issuer_max_epochs = vector[]; + let mut i = 0; + let mut prev_issuer: Option = option::none(); + + while (i < len) { + let cur = &inner.active_jwks[i]; + let cur_iss = &cur.jwk_id.iss; + if (prev_issuer.is_none()) { + prev_issuer.fill(*cur_iss); + issuer_max_epochs.push_back(cur.epoch); + } else { + if (cur_iss == prev_issuer.borrow()) { + let back = issuer_max_epochs.length() - 1; + let prev_max_epoch = &mut issuer_max_epochs[back]; + *prev_max_epoch = (*prev_max_epoch).max(cur.epoch); + } else { *prev_issuer.borrow_mut() = *cur_iss; - j = j + 1; - }; + issuer_max_epochs.push_back(cur.epoch); + } + }; + i = i + 1; + }; + + // Now, filter out any JWKs that are below the min_epoch, unless that issuer has no + // JWKs >= the min_epoch, in which case we keep all of them. + let mut new_active_jwks: vector = vector[]; + let mut prev_issuer: Option = option::none(); + let mut i = 0; + let mut j = 0; + while (i < len) { + let jwk = &inner.active_jwks[i]; + let cur_iss = &jwk.jwk_id.iss; + + if (prev_issuer.is_none()) { + prev_issuer.fill(*cur_iss); + } else if (cur_iss != prev_issuer.borrow()) { + *prev_issuer.borrow_mut() = *cur_iss; + j = j + 1; + }; - let max_epoch_for_iss = &issuer_max_epochs[j]; + let max_epoch_for_iss = &issuer_max_epochs[j]; - // TODO: if the iss for this jwk has *no* jwks that meet the minimum epoch, - // then expire nothing. - if (*max_epoch_for_iss < min_epoch || jwk.epoch >= min_epoch) { - new_active_jwks.push_back(*jwk); - }; - i = i + 1; + // TODO: if the iss for this jwk has *no* jwks that meet the minimum epoch, + // then expire nothing. + if (*max_epoch_for_iss < min_epoch || jwk.epoch >= min_epoch) { + new_active_jwks.push_back(*jwk); }; - inner.active_jwks = new_active_jwks; - } + i = i + 1; + }; + inner.active_jwks = new_active_jwks; +} - #[allow(unused_function)] - /// Get the current active_jwks. Called when the node starts up in order to load the current - /// JWK state from the chain. - fun get_active_jwks( - self: &AuthenticatorState, - ctx: &TxContext, - ): vector { - assert!(ctx.sender() == @0x0, ENotSystemAddress); - self.load_inner().active_jwks - } +#[allow(unused_function)] +/// Get the current active_jwks. Called when the node starts up in order to load the current +/// JWK state from the chain. +fun get_active_jwks(self: &AuthenticatorState, ctx: &TxContext): vector { + assert!(ctx.sender() == @0x0, ENotSystemAddress); + self.load_inner().active_jwks +} - #[test_only] - public fun create_for_testing(ctx: &TxContext) { - create(ctx); - } +#[test_only] +public fun create_for_testing(ctx: &TxContext) { + create(ctx); +} - #[test_only] - public fun update_authenticator_state_for_testing( - self: &mut AuthenticatorState, - new_active_jwks: vector, - ctx: &TxContext, - ) { - self.update_authenticator_state(new_active_jwks, ctx); - } +#[test_only] +public fun update_authenticator_state_for_testing( + self: &mut AuthenticatorState, + new_active_jwks: vector, + ctx: &TxContext, +) { + self.update_authenticator_state(new_active_jwks, ctx); +} - #[test_only] - public fun expire_jwks_for_testing( - self: &mut AuthenticatorState, - min_epoch: u64, - ctx: &TxContext, - ) { - self.expire_jwks(min_epoch, ctx); - } +#[test_only] +public fun expire_jwks_for_testing(self: &mut AuthenticatorState, min_epoch: u64, ctx: &TxContext) { + self.expire_jwks(min_epoch, ctx); +} - #[test_only] - public fun get_active_jwks_for_testing( - self: &AuthenticatorState, - ctx: &TxContext, - ): vector { - self.get_active_jwks(ctx) - } +#[test_only] +public fun get_active_jwks_for_testing( + self: &AuthenticatorState, + ctx: &TxContext, +): vector { + self.get_active_jwks(ctx) } diff --git a/crates/sui-framework/packages/sui-framework/sources/bag.move b/crates/sui-framework/packages/sui-framework/sources/bag.move index 38352d10fb518..649dd97705d28 100644 --- a/crates/sui-framework/packages/sui-framework/sources/bag.move +++ b/crates/sui-framework/packages/sui-framework/sources/bag.move @@ -21,92 +21,92 @@ /// `sui::dynamic_field` while preventing accidentally stranding field values. A `UID` can be /// deleted, even if it has dynamic fields associated with it, but a bag, on the other hand, must be /// empty to be destroyed. -module sui::bag { - use sui::dynamic_field as field; +module sui::bag; - // Attempted to destroy a non-empty bag - const EBagNotEmpty: u64 = 0; +use sui::dynamic_field as field; - public struct Bag has key, store { - /// the ID of this bag - id: UID, - /// the number of key-value pairs in the bag - size: u64, - } +// Attempted to destroy a non-empty bag +const EBagNotEmpty: u64 = 0; - /// Creates a new, empty bag - public fun new(ctx: &mut TxContext): Bag { - Bag { - id: object::new(ctx), - size: 0, - } - } +public struct Bag has key, store { + /// the ID of this bag + id: UID, + /// the number of key-value pairs in the bag + size: u64, +} - /// Adds a key-value pair to the bag `bag: &mut Bag` - /// Aborts with `sui::dynamic_field::EFieldAlreadyExists` if the bag already has an entry with - /// that key `k: K`. - public fun add(bag: &mut Bag, k: K, v: V) { - field::add(&mut bag.id, k, v); - bag.size = bag.size + 1; +/// Creates a new, empty bag +public fun new(ctx: &mut TxContext): Bag { + Bag { + id: object::new(ctx), + size: 0, } +} - #[syntax(index)] - /// Immutable borrows the value associated with the key in the bag `bag: &Bag`. - /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the bag does not have an entry with - /// that key `k: K`. - /// Aborts with `sui::dynamic_field::EFieldTypeMismatch` if the bag has an entry for the key, but - /// the value does not have the specified type. - public fun borrow(bag: &Bag, k: K): &V { - field::borrow(&bag.id, k) - } +/// Adds a key-value pair to the bag `bag: &mut Bag` +/// Aborts with `sui::dynamic_field::EFieldAlreadyExists` if the bag already has an entry with +/// that key `k: K`. +public fun add(bag: &mut Bag, k: K, v: V) { + field::add(&mut bag.id, k, v); + bag.size = bag.size + 1; +} - #[syntax(index)] - /// Mutably borrows the value associated with the key in the bag `bag: &mut Bag`. - /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the bag does not have an entry with - /// that key `k: K`. - /// Aborts with `sui::dynamic_field::EFieldTypeMismatch` if the bag has an entry for the key, but - /// the value does not have the specified type. - public fun borrow_mut(bag: &mut Bag, k: K): &mut V { - field::borrow_mut(&mut bag.id, k) - } +#[syntax(index)] +/// Immutable borrows the value associated with the key in the bag `bag: &Bag`. +/// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the bag does not have an entry with +/// that key `k: K`. +/// Aborts with `sui::dynamic_field::EFieldTypeMismatch` if the bag has an entry for the key, but +/// the value does not have the specified type. +public fun borrow(bag: &Bag, k: K): &V { + field::borrow(&bag.id, k) +} - /// Mutably borrows the key-value pair in the bag `bag: &mut Bag` and returns the value. - /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the bag does not have an entry with - /// that key `k: K`. - /// Aborts with `sui::dynamic_field::EFieldTypeMismatch` if the bag has an entry for the key, but - /// the value does not have the specified type. - public fun remove(bag: &mut Bag, k: K): V { - let v = field::remove(&mut bag.id, k); - bag.size = bag.size - 1; - v - } +#[syntax(index)] +/// Mutably borrows the value associated with the key in the bag `bag: &mut Bag`. +/// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the bag does not have an entry with +/// that key `k: K`. +/// Aborts with `sui::dynamic_field::EFieldTypeMismatch` if the bag has an entry for the key, but +/// the value does not have the specified type. +public fun borrow_mut(bag: &mut Bag, k: K): &mut V { + field::borrow_mut(&mut bag.id, k) +} - /// Returns true iff there is an value associated with the key `k: K` in the bag `bag: &Bag` - public fun contains(bag: &Bag, k: K): bool { - field::exists_(&bag.id, k) - } +/// Mutably borrows the key-value pair in the bag `bag: &mut Bag` and returns the value. +/// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the bag does not have an entry with +/// that key `k: K`. +/// Aborts with `sui::dynamic_field::EFieldTypeMismatch` if the bag has an entry for the key, but +/// the value does not have the specified type. +public fun remove(bag: &mut Bag, k: K): V { + let v = field::remove(&mut bag.id, k); + bag.size = bag.size - 1; + v +} - /// Returns true iff there is an value associated with the key `k: K` in the bag `bag: &Bag` - /// with an assigned value of type `V` - public fun contains_with_type(bag: &Bag, k: K): bool { - field::exists_with_type(&bag.id, k) - } +/// Returns true iff there is an value associated with the key `k: K` in the bag `bag: &Bag` +public fun contains(bag: &Bag, k: K): bool { + field::exists_(&bag.id, k) +} - /// Returns the size of the bag, the number of key-value pairs - public fun length(bag: &Bag): u64 { - bag.size - } +/// Returns true iff there is an value associated with the key `k: K` in the bag `bag: &Bag` +/// with an assigned value of type `V` +public fun contains_with_type(bag: &Bag, k: K): bool { + field::exists_with_type(&bag.id, k) +} - /// Returns true iff the bag is empty (if `length` returns `0`) - public fun is_empty(bag: &Bag): bool { - bag.size == 0 - } +/// Returns the size of the bag, the number of key-value pairs +public fun length(bag: &Bag): u64 { + bag.size +} - /// Destroys an empty bag - /// Aborts with `EBagNotEmpty` if the bag still contains values - public fun destroy_empty(bag: Bag) { - let Bag { id, size } = bag; - assert!(size == 0, EBagNotEmpty); - id.delete() - } +/// Returns true iff the bag is empty (if `length` returns `0`) +public fun is_empty(bag: &Bag): bool { + bag.size == 0 +} + +/// Destroys an empty bag +/// Aborts with `EBagNotEmpty` if the bag still contains values +public fun destroy_empty(bag: Bag) { + let Bag { id, size } = bag; + assert!(size == 0, EBagNotEmpty); + id.delete() } diff --git a/crates/sui-framework/packages/sui-framework/sources/balance.move b/crates/sui-framework/packages/sui-framework/sources/balance.move index 90e44002db85c..c69f56bee0762 100644 --- a/crates/sui-framework/packages/sui-framework/sources/balance.move +++ b/crates/sui-framework/packages/sui-framework/sources/balance.move @@ -4,177 +4,139 @@ /// A storable handler for Balances in general. Is used in the `Coin` /// module to allow balance operations and can be used to implement /// custom coins with `Supply` and `Balance`s. -module sui::balance { - - /// Allows calling `.into_coin()` on a `Balance` to turn it into a coin. - public use fun sui::coin::from_balance as Balance.into_coin; - - /// For when trying to destroy a non-zero balance. - const ENonZero: u64 = 0; - /// For when an overflow is happening on Supply operations. - const EOverflow: u64 = 1; - /// For when trying to withdraw more than there is. - const ENotEnough: u64 = 2; - /// Sender is not @0x0 the system address. - const ENotSystemAddress: u64 = 3; - /// System operation performed for a coin other than SUI - const ENotSUI: u64 = 4; - - /// A Supply of T. Used for minting and burning. - /// Wrapped into a `TreasuryCap` in the `Coin` module. - public struct Supply has store { - value: u64 - } - - /// Storable balance - an inner struct of a Coin type. - /// Can be used to store coins which don't need the key ability. - public struct Balance has store { - value: u64 - } - - /// Get the amount stored in a `Balance`. - public fun value(self: &Balance): u64 { - self.value - } - - /// Get the `Supply` value. - public fun supply_value(supply: &Supply): u64 { - supply.value - } - - /// Create a new supply for type T. - public fun create_supply(_: T): Supply { - Supply { value: 0 } - } - - /// Increase supply by `value` and create a new `Balance` with this value. - public fun increase_supply(self: &mut Supply, value: u64): Balance { - assert!(value < (18446744073709551615u64 - self.value), EOverflow); - self.value = self.value + value; - Balance { value } - } - - /// Burn a Balance and decrease Supply. - public fun decrease_supply(self: &mut Supply, balance: Balance): u64 { - let Balance { value } = balance; - assert!(self.value >= value, EOverflow); - self.value = self.value - value; - value - } - - /// Create a zero `Balance` for type `T`. - public fun zero(): Balance { - Balance { value: 0 } - } - - /// Join two balances together. - public fun join(self: &mut Balance, balance: Balance): u64 { - let Balance { value } = balance; - self.value = self.value + value; - self.value - } - - /// Split a `Balance` and take a sub balance from it. - public fun split(self: &mut Balance, value: u64): Balance { - assert!(self.value >= value, ENotEnough); - self.value = self.value - value; - Balance { value } - } - - /// Withdraw all balance. After this the remaining balance must be 0. - public fun withdraw_all(self: &mut Balance): Balance { - let value = self.value; - split(self, value) - } - - /// Destroy a zero `Balance`. - public fun destroy_zero(balance: Balance) { - assert!(balance.value == 0, ENonZero); - let Balance { value: _ } = balance; - } - - const SUI_TYPE_NAME: vector = - b"0000000000000000000000000000000000000000000000000000000000000002::sui::SUI"; - - #[allow(unused_function)] - /// CAUTION: this function creates a `Balance` without increasing the supply. - /// It should only be called by the epoch change system txn to create staking rewards, - /// and nowhere else. - fun create_staking_rewards(value: u64, ctx: &TxContext): Balance { - assert!(ctx.sender() == @0x0, ENotSystemAddress); - assert!( - std::type_name::get().into_string().into_bytes() == SUI_TYPE_NAME, - ENotSUI, - ); - Balance { value } - } - - #[allow(unused_function)] - /// CAUTION: this function destroys a `Balance` without decreasing the supply. - /// It should only be called by the epoch change system txn to destroy storage rebates, - /// and nowhere else. - fun destroy_storage_rebates(self: Balance, ctx: &TxContext) { - assert!(ctx.sender() == @0x0, ENotSystemAddress); - assert!( - std::type_name::get().into_string().into_bytes() == SUI_TYPE_NAME, - ENotSUI, - ); - let Balance { value: _ } = self; - } - - /// Destroy a `Supply` preventing any further minting and burning. - public(package) fun destroy_supply(self: Supply): u64 { - let Supply { value } = self; - value - } - - #[test_only] - /// Create a `Balance` of any coin for testing purposes. - public fun create_for_testing(value: u64): Balance { - Balance { value } - } - - #[test_only] - /// Destroy a `Balance` of any coin for testing purposes. - public fun destroy_for_testing(self: Balance): u64 { - let Balance { value } = self; - value - } - - #[test_only] - /// Create a `Supply` of any coin for testing purposes. - public fun create_supply_for_testing(): Supply { - Supply { value: 0 } - } +module sui::balance; + +/// Allows calling `.into_coin()` on a `Balance` to turn it into a coin. +public use fun sui::coin::from_balance as Balance.into_coin; + +/// For when trying to destroy a non-zero balance. +const ENonZero: u64 = 0; +/// For when an overflow is happening on Supply operations. +const EOverflow: u64 = 1; +/// For when trying to withdraw more than there is. +const ENotEnough: u64 = 2; +/// Sender is not @0x0 the system address. +const ENotSystemAddress: u64 = 3; +/// System operation performed for a coin other than SUI +const ENotSUI: u64 = 4; + +/// A Supply of T. Used for minting and burning. +/// Wrapped into a `TreasuryCap` in the `Coin` module. +public struct Supply has store { + value: u64, } -#[test_only] -module sui::balance_tests { - use sui::balance; - use sui::sui::SUI; - use sui::test_utils; +/// Storable balance - an inner struct of a Coin type. +/// Can be used to store coins which don't need the key ability. +public struct Balance has store { + value: u64, +} - #[test] - fun test_balance() { - let mut balance = balance::zero(); - let another = balance::create_for_testing(1000); +/// Get the amount stored in a `Balance`. +public fun value(self: &Balance): u64 { + self.value +} - balance.join(another); +/// Get the `Supply` value. +public fun supply_value(supply: &Supply): u64 { + supply.value +} - assert!(balance.value() == 1000); +/// Create a new supply for type T. +public fun create_supply(_: T): Supply { + Supply { value: 0 } +} - let balance1 = balance.split(333); - let balance2 = balance.split(333); - let balance3 = balance.split(334); +/// Increase supply by `value` and create a new `Balance` with this value. +public fun increase_supply(self: &mut Supply, value: u64): Balance { + assert!(value < (18446744073709551615u64 - self.value), EOverflow); + self.value = self.value + value; + Balance { value } +} - balance.destroy_zero(); +/// Burn a Balance and decrease Supply. +public fun decrease_supply(self: &mut Supply, balance: Balance): u64 { + let Balance { value } = balance; + assert!(self.value >= value, EOverflow); + self.value = self.value - value; + value +} - assert!(balance1.value() == 333); - assert!(balance2.value() == 333); - assert!(balance3.value() == 334); +/// Create a zero `Balance` for type `T`. +public fun zero(): Balance { + Balance { value: 0 } +} - test_utils::destroy(balance1); - test_utils::destroy(balance2); - test_utils::destroy(balance3); - } +/// Join two balances together. +public fun join(self: &mut Balance, balance: Balance): u64 { + let Balance { value } = balance; + self.value = self.value + value; + self.value +} + +/// Split a `Balance` and take a sub balance from it. +public fun split(self: &mut Balance, value: u64): Balance { + assert!(self.value >= value, ENotEnough); + self.value = self.value - value; + Balance { value } +} + +/// Withdraw all balance. After this the remaining balance must be 0. +public fun withdraw_all(self: &mut Balance): Balance { + let value = self.value; + split(self, value) +} + +/// Destroy a zero `Balance`. +public fun destroy_zero(balance: Balance) { + assert!(balance.value == 0, ENonZero); + let Balance { value: _ } = balance; +} + +const SUI_TYPE_NAME: vector = + b"0000000000000000000000000000000000000000000000000000000000000002::sui::SUI"; + +#[allow(unused_function)] +/// CAUTION: this function creates a `Balance` without increasing the supply. +/// It should only be called by the epoch change system txn to create staking rewards, +/// and nowhere else. +fun create_staking_rewards(value: u64, ctx: &TxContext): Balance { + assert!(ctx.sender() == @0x0, ENotSystemAddress); + assert!(std::type_name::get().into_string().into_bytes() == SUI_TYPE_NAME, ENotSUI); + Balance { value } +} + +#[allow(unused_function)] +/// CAUTION: this function destroys a `Balance` without decreasing the supply. +/// It should only be called by the epoch change system txn to destroy storage rebates, +/// and nowhere else. +fun destroy_storage_rebates(self: Balance, ctx: &TxContext) { + assert!(ctx.sender() == @0x0, ENotSystemAddress); + assert!(std::type_name::get().into_string().into_bytes() == SUI_TYPE_NAME, ENotSUI); + let Balance { value: _ } = self; +} + +/// Destroy a `Supply` preventing any further minting and burning. +public(package) fun destroy_supply(self: Supply): u64 { + let Supply { value } = self; + value +} + +#[test_only] +/// Create a `Balance` of any coin for testing purposes. +public fun create_for_testing(value: u64): Balance { + Balance { value } +} + +#[test_only] +/// Destroy a `Balance` of any coin for testing purposes. +public fun destroy_for_testing(self: Balance): u64 { + let Balance { value } = self; + value +} + +#[test_only] +/// Create a `Supply` of any coin for testing purposes. +public fun create_supply_for_testing(): Supply { + Supply { value: 0 } } diff --git a/crates/sui-framework/packages/sui-framework/sources/bcs.move b/crates/sui-framework/packages/sui-framework/sources/bcs.move index 66ecb2b2e6b40..83f69cdc0c554 100644 --- a/crates/sui-framework/packages/sui-framework/sources/bcs.move +++ b/crates/sui-framework/packages/sui-framework/sources/bcs.move @@ -32,237 +32,237 @@ /// (u8_value, u64_value, leftovers) /// } /// ``` -module sui::bcs { - use sui::address; - use std::bcs; - - /// For when bytes length is less than required for deserialization. - const EOutOfRange: u64 = 0; - /// For when the boolean value different than `0` or `1`. - const ENotBool: u64 = 1; - /// For when ULEB byte is out of range (or not found). - const ELenOutOfRange: u64 = 2; - - /// A helper struct that saves resources on operations. For better - /// vector performance, it stores reversed bytes of the BCS and - /// enables use of `vector::pop_back`. - public struct BCS has store, copy, drop { - bytes: vector - } - - /// Get BCS serialized bytes for any value. - /// Re-exports stdlib `bcs::to_bytes`. - public fun to_bytes(value: &T): vector { - bcs::to_bytes(value) - } - - /// Creates a new instance of BCS wrapper that holds inversed - /// bytes for better performance. - public fun new(mut bytes: vector): BCS { - bytes.reverse(); - BCS { bytes } - } - - /// Unpack the `BCS` struct returning the leftover bytes. - /// Useful for passing the data further after partial deserialization. - public fun into_remainder_bytes(bcs: BCS): vector { - let BCS { mut bytes } = bcs; - bytes.reverse(); - bytes - } - - /// Read address from the bcs-serialized bytes. - public fun peel_address(bcs: &mut BCS): address { - assert!(bcs.bytes.length() >= address::length(), EOutOfRange); - let (mut addr_bytes, mut i) = (vector[], 0); - while (i < address::length()) { - addr_bytes.push_back(bcs.bytes.pop_back()); - i = i + 1; - }; - address::from_bytes(addr_bytes) - } - - /// Read a `bool` value from bcs-serialized bytes. - public fun peel_bool(bcs: &mut BCS): bool { - let value = bcs.peel_u8(); - if (value == 0) false - else if (value == 1) true - else abort ENotBool - } - - /// Read `u8` value from bcs-serialized bytes. - public fun peel_u8(bcs: &mut BCS): u8 { - assert!(bcs.bytes.length() >= 1, EOutOfRange); - bcs.bytes.pop_back() - } - - macro fun peel_num<$I, $T>($bcs: &mut BCS, $len: u64, $bits: $I): $T { - let bcs = $bcs; - assert!(bcs.bytes.length() >= $len, EOutOfRange); - - let mut value: $T = 0; - let mut i: $I = 0; - let bits = $bits; - while (i < bits) { - let byte = bcs.bytes.pop_back() as $T; - value = value + (byte << (i as u8)); - i = i + 8; - }; - - value - } - - /// Read `u16` value from bcs-serialized bytes. - public fun peel_u16(bcs: &mut BCS): u16 { - bcs.peel_num!(2, 16u8) - } - - /// Read `u32` value from bcs-serialized bytes. - public fun peel_u32(bcs: &mut BCS): u32 { - bcs.peel_num!(4, 32u8) - } - - /// Read `u64` value from bcs-serialized bytes. - public fun peel_u64(bcs: &mut BCS): u64 { - bcs.peel_num!(8, 64u8) - } - - /// Read `u128` value from bcs-serialized bytes. - public fun peel_u128(bcs: &mut BCS): u128 { - bcs.peel_num!(16, 128u8) - } - - /// Read `u256` value from bcs-serialized bytes. - public fun peel_u256(bcs: &mut BCS): u256 { - bcs.peel_num!(32, 256u16) - } - - // === Vector === - - /// Read ULEB bytes expecting a vector length. Result should - /// then be used to perform `peel_*` operation LEN times. - /// - /// In BCS `vector` length is implemented with ULEB128; - /// See more here: https://en.wikipedia.org/wiki/LEB128 - public fun peel_vec_length(bcs: &mut BCS): u64 { - let (mut total, mut shift, mut len) = (0u64, 0, 0); - loop { - assert!(len <= 4, ELenOutOfRange); - let byte = bcs.bytes.pop_back() as u64; - len = len + 1; - total = total | ((byte & 0x7f) << shift); - if ((byte & 0x80) == 0) break; - shift = shift + 7; - }; - total - } - - /// Peel `vector<$T>` from serialized bytes, where `$peel: |&mut BCS| -> $T` gives the - /// functionality of peeling each value. - public macro fun peel_vec<$T>($bcs: &mut BCS, $peel: |&mut BCS| -> $T): vector<$T> { - let bcs = $bcs; - let len = bcs.peel_vec_length(); - let mut i = 0; - let mut res = vector[]; - while (i < len) { - res.push_back($peel(bcs)); - i = i + 1; - }; - res - } - - /// Peel a vector of `address` from serialized bytes. - public fun peel_vec_address(bcs: &mut BCS): vector
{ - bcs.peel_vec!(|bcs| bcs.peel_address()) - } - - /// Peel a vector of `address` from serialized bytes. - public fun peel_vec_bool(bcs: &mut BCS): vector { - bcs.peel_vec!(|bcs| bcs.peel_bool()) - } - - /// Peel a vector of `u8` (eg string) from serialized bytes. - public fun peel_vec_u8(bcs: &mut BCS): vector { - bcs.peel_vec!(|bcs| bcs.peel_u8()) - } - - /// Peel a `vector>` (eg vec of string) from serialized bytes. - public fun peel_vec_vec_u8(bcs: &mut BCS): vector> { - bcs.peel_vec!(|bcs| bcs.peel_vec_u8()) - } - - /// Peel a vector of `u16` from serialized bytes. - public fun peel_vec_u16(bcs: &mut BCS): vector { - bcs.peel_vec!(|bcs| bcs.peel_u16()) - } - - /// Peel a vector of `u32` from serialized bytes. - public fun peel_vec_u32(bcs: &mut BCS): vector { - bcs.peel_vec!(|bcs| bcs.peel_u32()) - } - - /// Peel a vector of `u64` from serialized bytes. - public fun peel_vec_u64(bcs: &mut BCS): vector { - bcs.peel_vec!(|bcs| bcs.peel_u64()) - } - - /// Peel a vector of `u128` from serialized bytes. - public fun peel_vec_u128(bcs: &mut BCS): vector { - bcs.peel_vec!(|bcs| bcs.peel_u128()) - } - - /// Peel a vector of `u256` from serialized bytes. - public fun peel_vec_u256(bcs: &mut BCS): vector { - bcs.peel_vec!(|bcs| bcs.peel_u256()) - } - - // === Option === - - /// Peel `Option<$T>` from serialized bytes, where `$peel: |&mut BCS| -> $T` gives the - /// functionality of peeling the inner value. - public macro fun peel_option<$T>($bcs: &mut BCS, $peel: |&mut BCS| -> $T): Option<$T> { - let bcs = $bcs; - if (bcs.peel_bool())option::some($peel(bcs)) - else option::none() - } - - /// Peel `Option
` from serialized bytes. - public fun peel_option_address(bcs: &mut BCS): Option
{ - bcs.peel_option!(|bcs| bcs.peel_address()) - } - - /// Peel `Option` from serialized bytes. - public fun peel_option_bool(bcs: &mut BCS): Option { - bcs.peel_option!(|bcs| bcs.peel_bool()) - } - - /// Peel `Option` from serialized bytes. - public fun peel_option_u8(bcs: &mut BCS): Option { - bcs.peel_option!(|bcs| bcs.peel_u8()) - } - - /// Peel `Option` from serialized bytes. - public fun peel_option_u16(bcs: &mut BCS): Option { - bcs.peel_option!(|bcs| bcs.peel_u16()) - } - - /// Peel `Option` from serialized bytes. - public fun peel_option_u32(bcs: &mut BCS): Option { - bcs.peel_option!(|bcs| bcs.peel_u32()) - } - - /// Peel `Option` from serialized bytes. - public fun peel_option_u64(bcs: &mut BCS): Option { - bcs.peel_option!(|bcs| bcs.peel_u64()) - } - - /// Peel `Option` from serialized bytes. - public fun peel_option_u128(bcs: &mut BCS): Option { - bcs.peel_option!(|bcs| bcs.peel_u128()) - } - - /// Peel `Option` from serialized bytes. - public fun peel_option_u256(bcs: &mut BCS): Option { - bcs.peel_option!(|bcs| bcs.peel_u256()) - } +module sui::bcs; + +use std::bcs; +use sui::address; + +/// For when bytes length is less than required for deserialization. +const EOutOfRange: u64 = 0; +/// For when the boolean value different than `0` or `1`. +const ENotBool: u64 = 1; +/// For when ULEB byte is out of range (or not found). +const ELenOutOfRange: u64 = 2; + +/// A helper struct that saves resources on operations. For better +/// vector performance, it stores reversed bytes of the BCS and +/// enables use of `vector::pop_back`. +public struct BCS has store, copy, drop { + bytes: vector, +} + +/// Get BCS serialized bytes for any value. +/// Re-exports stdlib `bcs::to_bytes`. +public fun to_bytes(value: &T): vector { + bcs::to_bytes(value) +} + +/// Creates a new instance of BCS wrapper that holds inversed +/// bytes for better performance. +public fun new(mut bytes: vector): BCS { + bytes.reverse(); + BCS { bytes } +} + +/// Unpack the `BCS` struct returning the leftover bytes. +/// Useful for passing the data further after partial deserialization. +public fun into_remainder_bytes(bcs: BCS): vector { + let BCS { mut bytes } = bcs; + bytes.reverse(); + bytes +} + +/// Read address from the bcs-serialized bytes. +public fun peel_address(bcs: &mut BCS): address { + assert!(bcs.bytes.length() >= address::length(), EOutOfRange); + let (mut addr_bytes, mut i) = (vector[], 0); + while (i < address::length()) { + addr_bytes.push_back(bcs.bytes.pop_back()); + i = i + 1; + }; + address::from_bytes(addr_bytes) +} + +/// Read a `bool` value from bcs-serialized bytes. +public fun peel_bool(bcs: &mut BCS): bool { + let value = bcs.peel_u8(); + if (value == 0) false + else if (value == 1) true + else abort ENotBool +} + +/// Read `u8` value from bcs-serialized bytes. +public fun peel_u8(bcs: &mut BCS): u8 { + assert!(bcs.bytes.length() >= 1, EOutOfRange); + bcs.bytes.pop_back() +} + +macro fun peel_num<$I, $T>($bcs: &mut BCS, $len: u64, $bits: $I): $T { + let bcs = $bcs; + assert!(bcs.bytes.length() >= $len, EOutOfRange); + + let mut value: $T = 0; + let mut i: $I = 0; + let bits = $bits; + while (i < bits) { + let byte = bcs.bytes.pop_back() as $T; + value = value + (byte << (i as u8)); + i = i + 8; + }; + + value +} + +/// Read `u16` value from bcs-serialized bytes. +public fun peel_u16(bcs: &mut BCS): u16 { + bcs.peel_num!(2, 16u8) +} + +/// Read `u32` value from bcs-serialized bytes. +public fun peel_u32(bcs: &mut BCS): u32 { + bcs.peel_num!(4, 32u8) +} + +/// Read `u64` value from bcs-serialized bytes. +public fun peel_u64(bcs: &mut BCS): u64 { + bcs.peel_num!(8, 64u8) +} + +/// Read `u128` value from bcs-serialized bytes. +public fun peel_u128(bcs: &mut BCS): u128 { + bcs.peel_num!(16, 128u8) +} + +/// Read `u256` value from bcs-serialized bytes. +public fun peel_u256(bcs: &mut BCS): u256 { + bcs.peel_num!(32, 256u16) +} + +// === Vector === + +/// Read ULEB bytes expecting a vector length. Result should +/// then be used to perform `peel_*` operation LEN times. +/// +/// In BCS `vector` length is implemented with ULEB128; +/// See more here: https://en.wikipedia.org/wiki/LEB128 +public fun peel_vec_length(bcs: &mut BCS): u64 { + let (mut total, mut shift, mut len) = (0u64, 0, 0); + loop { + assert!(len <= 4, ELenOutOfRange); + let byte = bcs.bytes.pop_back() as u64; + len = len + 1; + total = total | ((byte & 0x7f) << shift); + if ((byte & 0x80) == 0) break; + shift = shift + 7; + }; + total +} + +/// Peel `vector<$T>` from serialized bytes, where `$peel: |&mut BCS| -> $T` gives the +/// functionality of peeling each value. +public macro fun peel_vec<$T>($bcs: &mut BCS, $peel: |&mut BCS| -> $T): vector<$T> { + let bcs = $bcs; + let len = bcs.peel_vec_length(); + let mut i = 0; + let mut res = vector[]; + while (i < len) { + res.push_back($peel(bcs)); + i = i + 1; + }; + res +} + +/// Peel a vector of `address` from serialized bytes. +public fun peel_vec_address(bcs: &mut BCS): vector
{ + bcs.peel_vec!(|bcs| bcs.peel_address()) +} + +/// Peel a vector of `address` from serialized bytes. +public fun peel_vec_bool(bcs: &mut BCS): vector { + bcs.peel_vec!(|bcs| bcs.peel_bool()) +} + +/// Peel a vector of `u8` (eg string) from serialized bytes. +public fun peel_vec_u8(bcs: &mut BCS): vector { + bcs.peel_vec!(|bcs| bcs.peel_u8()) +} + +/// Peel a `vector>` (eg vec of string) from serialized bytes. +public fun peel_vec_vec_u8(bcs: &mut BCS): vector> { + bcs.peel_vec!(|bcs| bcs.peel_vec_u8()) +} + +/// Peel a vector of `u16` from serialized bytes. +public fun peel_vec_u16(bcs: &mut BCS): vector { + bcs.peel_vec!(|bcs| bcs.peel_u16()) +} + +/// Peel a vector of `u32` from serialized bytes. +public fun peel_vec_u32(bcs: &mut BCS): vector { + bcs.peel_vec!(|bcs| bcs.peel_u32()) +} + +/// Peel a vector of `u64` from serialized bytes. +public fun peel_vec_u64(bcs: &mut BCS): vector { + bcs.peel_vec!(|bcs| bcs.peel_u64()) +} + +/// Peel a vector of `u128` from serialized bytes. +public fun peel_vec_u128(bcs: &mut BCS): vector { + bcs.peel_vec!(|bcs| bcs.peel_u128()) +} + +/// Peel a vector of `u256` from serialized bytes. +public fun peel_vec_u256(bcs: &mut BCS): vector { + bcs.peel_vec!(|bcs| bcs.peel_u256()) +} + +// === Option === + +/// Peel `Option<$T>` from serialized bytes, where `$peel: |&mut BCS| -> $T` gives the +/// functionality of peeling the inner value. +public macro fun peel_option<$T>($bcs: &mut BCS, $peel: |&mut BCS| -> $T): Option<$T> { + let bcs = $bcs; + if (bcs.peel_bool()) option::some($peel(bcs)) + else option::none() +} + +/// Peel `Option
` from serialized bytes. +public fun peel_option_address(bcs: &mut BCS): Option
{ + bcs.peel_option!(|bcs| bcs.peel_address()) +} + +/// Peel `Option` from serialized bytes. +public fun peel_option_bool(bcs: &mut BCS): Option { + bcs.peel_option!(|bcs| bcs.peel_bool()) +} + +/// Peel `Option` from serialized bytes. +public fun peel_option_u8(bcs: &mut BCS): Option { + bcs.peel_option!(|bcs| bcs.peel_u8()) +} + +/// Peel `Option` from serialized bytes. +public fun peel_option_u16(bcs: &mut BCS): Option { + bcs.peel_option!(|bcs| bcs.peel_u16()) +} + +/// Peel `Option` from serialized bytes. +public fun peel_option_u32(bcs: &mut BCS): Option { + bcs.peel_option!(|bcs| bcs.peel_u32()) +} + +/// Peel `Option` from serialized bytes. +public fun peel_option_u64(bcs: &mut BCS): Option { + bcs.peel_option!(|bcs| bcs.peel_u64()) +} + +/// Peel `Option` from serialized bytes. +public fun peel_option_u128(bcs: &mut BCS): Option { + bcs.peel_option!(|bcs| bcs.peel_u128()) +} + +/// Peel `Option` from serialized bytes. +public fun peel_option_u256(bcs: &mut BCS): Option { + bcs.peel_option!(|bcs| bcs.peel_u256()) } diff --git a/crates/sui-framework/packages/sui-framework/sources/borrow.move b/crates/sui-framework/packages/sui-framework/sources/borrow.move index 55eb1e1ae5bd3..483a6aab4912a 100644 --- a/crates/sui-framework/packages/sui-framework/sources/borrow.move +++ b/crates/sui-framework/packages/sui-framework/sources/borrow.move @@ -6,113 +6,115 @@ /// With Programmable transactions, it is possible to borrow a value within /// a transaction, use it and put back in the end. Hot-potato `Borrow` makes /// sure the object is returned and was not swapped for another one. -module sui::borrow { +module sui::borrow; - /// The `Borrow` does not match the `Referent`. - const EWrongBorrow: u64 = 0; - /// An attempt to swap the `Referent.value` with another object of the same type. - const EWrongValue: u64 = 1; +/// The `Borrow` does not match the `Referent`. +const EWrongBorrow: u64 = 0; +/// An attempt to swap the `Referent.value` with another object of the same type. +const EWrongValue: u64 = 1; - /// An object wrapping a `T` and providing the borrow API. - public struct Referent has store { - id: address, - value: Option - } +/// An object wrapping a `T` and providing the borrow API. +public struct Referent has store { + id: address, + value: Option, +} - /// A hot potato making sure the object is put back once borrowed. - public struct Borrow { ref: address, obj: ID } +/// A hot potato making sure the object is put back once borrowed. +public struct Borrow { ref: address, obj: ID } - /// Create a new `Referent` struct - public fun new(value: T, ctx: &mut TxContext): Referent { - Referent { - id: tx_context::fresh_object_address(ctx), - value: option::some(value) - } +/// Create a new `Referent` struct +public fun new(value: T, ctx: &mut TxContext): Referent { + Referent { + id: tx_context::fresh_object_address(ctx), + value: option::some(value), } +} - /// Borrow the `T` from the `Referent` receiving the `T` and a `Borrow` - /// hot potato. - public fun borrow(self: &mut Referent): (T, Borrow) { - let value = self.value.extract(); - let id = object::id(&value); +/// Borrow the `T` from the `Referent` receiving the `T` and a `Borrow` +/// hot potato. +public fun borrow(self: &mut Referent): (T, Borrow) { + let value = self.value.extract(); + let id = object::id(&value); - (value, Borrow { + ( + value, + Borrow { ref: self.id, - obj: id - }) - } + obj: id, + }, + ) +} - /// Put an object and the `Borrow` hot potato back. - public fun put_back(self: &mut Referent, value: T, borrow: Borrow) { - let Borrow { ref, obj } = borrow; +/// Put an object and the `Borrow` hot potato back. +public fun put_back(self: &mut Referent, value: T, borrow: Borrow) { + let Borrow { ref, obj } = borrow; - assert!(object::id(&value) == obj, EWrongValue); - assert!(self.id == ref, EWrongBorrow); - self.value.fill(value); - } + assert!(object::id(&value) == obj, EWrongValue); + assert!(self.id == ref, EWrongBorrow); + self.value.fill(value); +} - /// Unpack the `Referent` struct and return the value. - public fun destroy(self: Referent): T { - let Referent { id: _, value } = self; - value.destroy_some() - } +/// Unpack the `Referent` struct and return the value. +public fun destroy(self: Referent): T { + let Referent { id: _, value } = self; + value.destroy_some() +} - #[test_only] - public struct Test has key, store { - id: object::UID - } +#[test_only] +public struct Test has key, store { + id: object::UID, +} - #[test] - fun test_borrow() { - let ctx = &mut sui::tx_context::dummy(); - let mut ref = new(Test { id: object::new(ctx) }, ctx); +#[test] +fun test_borrow() { + let ctx = &mut sui::tx_context::dummy(); + let mut ref = new(Test { id: object::new(ctx) }, ctx); - let (value, borrow) = borrow(&mut ref); - put_back(&mut ref, value, borrow); + let (value, borrow) = borrow(&mut ref); + put_back(&mut ref, value, borrow); - let Test { id } = destroy(ref); - id.delete(); - } + let Test { id } = destroy(ref); + id.delete(); +} - #[test] - #[expected_failure(abort_code = EWrongValue)] - /// The `value` is swapped with another instance of the type `T`. - fun test_object_swap() { - let ctx = &mut sui::tx_context::dummy(); - let mut ref_1 = new(Test { id: object::new(ctx) }, ctx); - let mut ref_2 = new(Test { id: object::new(ctx) }, ctx); +#[test] +#[expected_failure(abort_code = EWrongValue)] +/// The `value` is swapped with another instance of the type `T`. +fun test_object_swap() { + let ctx = &mut sui::tx_context::dummy(); + let mut ref_1 = new(Test { id: object::new(ctx) }, ctx); + let mut ref_2 = new(Test { id: object::new(ctx) }, ctx); - let (v_1, b_1) = borrow(&mut ref_1); - let (v_2, b_2) = borrow(&mut ref_2); + let (v_1, b_1) = borrow(&mut ref_1); + let (v_2, b_2) = borrow(&mut ref_2); - put_back(&mut ref_1, v_2, b_1); - put_back(&mut ref_2, v_1, b_2); + put_back(&mut ref_1, v_2, b_1); + put_back(&mut ref_2, v_1, b_2); - let Test { id } = destroy(ref_1); - id.delete(); + let Test { id } = destroy(ref_1); + id.delete(); - let Test { id } = destroy(ref_2); - id.delete(); - } + let Test { id } = destroy(ref_2); + id.delete(); +} - #[test] - #[expected_failure(abort_code = EWrongBorrow)] - /// The both `borrow` and `value` are swapped with another `Referent`. - fun test_borrow_fail() { - let ctx = &mut sui::tx_context::dummy(); - let mut ref_1 = new(Test { id: object::new(ctx) }, ctx); - let mut ref_2 = new(Test { id: object::new(ctx) }, ctx); +#[test] +#[expected_failure(abort_code = EWrongBorrow)] +/// The both `borrow` and `value` are swapped with another `Referent`. +fun test_borrow_fail() { + let ctx = &mut sui::tx_context::dummy(); + let mut ref_1 = new(Test { id: object::new(ctx) }, ctx); + let mut ref_2 = new(Test { id: object::new(ctx) }, ctx); - let (v_1, b_1) = borrow(&mut ref_1); - let (v_2, b_2) = borrow(&mut ref_2); + let (v_1, b_1) = borrow(&mut ref_1); + let (v_2, b_2) = borrow(&mut ref_2); - put_back(&mut ref_1, v_2, b_2); - put_back(&mut ref_2, v_1, b_1); + put_back(&mut ref_1, v_2, b_2); + put_back(&mut ref_2, v_1, b_1); - let Test { id } = destroy(ref_1); - id.delete(); + let Test { id } = destroy(ref_1); + id.delete(); - let Test { id } = destroy(ref_2); - id.delete(); - } + let Test { id } = destroy(ref_2); + id.delete(); } diff --git a/crates/sui-framework/packages/sui-framework/sources/clock.move b/crates/sui-framework/packages/sui-framework/sources/clock.move index 9cfa08c9c6872..11b72322248a1 100644 --- a/crates/sui-framework/packages/sui-framework/sources/clock.move +++ b/crates/sui-framework/packages/sui-framework/sources/clock.move @@ -3,91 +3,86 @@ /// APIs for accessing time from move calls, via the `Clock`: a unique /// shared object that is created at 0x6 during genesis. -module sui::clock { +module sui::clock; - /// Sender is not @0x0 the system address. - const ENotSystemAddress: u64 = 0; +/// Sender is not @0x0 the system address. +const ENotSystemAddress: u64 = 0; - /// Singleton shared object that exposes time to Move calls. This - /// object is found at address 0x6, and can only be read (accessed - /// via an immutable reference) by entry functions. - /// - /// Entry Functions that attempt to accept `Clock` by mutable - /// reference or value will fail to verify, and honest validators - /// will not sign or execute transactions that use `Clock` as an - /// input parameter, unless it is passed by immutable reference. - public struct Clock has key { - id: UID, - /// The clock's timestamp, which is set automatically by a - /// system transaction every time consensus commits a - /// schedule, or by `sui::clock::increment_for_testing` during - /// testing. - timestamp_ms: u64, - } +/// Singleton shared object that exposes time to Move calls. This +/// object is found at address 0x6, and can only be read (accessed +/// via an immutable reference) by entry functions. +/// +/// Entry Functions that attempt to accept `Clock` by mutable +/// reference or value will fail to verify, and honest validators +/// will not sign or execute transactions that use `Clock` as an +/// input parameter, unless it is passed by immutable reference. +public struct Clock has key { + id: UID, + /// The clock's timestamp, which is set automatically by a + /// system transaction every time consensus commits a + /// schedule, or by `sui::clock::increment_for_testing` during + /// testing. + timestamp_ms: u64, +} - /// The `clock`'s current timestamp as a running total of - /// milliseconds since an arbitrary point in the past. - public fun timestamp_ms(clock: &Clock): u64 { - clock.timestamp_ms - } +/// The `clock`'s current timestamp as a running total of +/// milliseconds since an arbitrary point in the past. +public fun timestamp_ms(clock: &Clock): u64 { + clock.timestamp_ms +} - #[allow(unused_function)] - /// Create and share the singleton Clock -- this function is - /// called exactly once, during genesis. - fun create(ctx: &TxContext) { - assert!(ctx.sender() == @0x0, ENotSystemAddress); +#[allow(unused_function)] +/// Create and share the singleton Clock -- this function is +/// called exactly once, during genesis. +fun create(ctx: &TxContext) { + assert!(ctx.sender() == @0x0, ENotSystemAddress); - transfer::share_object(Clock { - id: object::clock(), - // Initialised to zero, but set to a real timestamp by a - // system transaction before it can be witnessed by a move - // call. - timestamp_ms: 0, - }) - } + transfer::share_object(Clock { + id: object::clock(), + // Initialised to zero, but set to a real timestamp by a + // system transaction before it can be witnessed by a move + // call. + timestamp_ms: 0, + }) +} - #[allow(unused_function)] - fun consensus_commit_prologue( - clock: &mut Clock, - timestamp_ms: u64, - ctx: &TxContext, - ) { - // Validator will make a special system call with sender set as 0x0. - assert!(ctx.sender() == @0x0, ENotSystemAddress); +#[allow(unused_function)] +fun consensus_commit_prologue(clock: &mut Clock, timestamp_ms: u64, ctx: &TxContext) { + // Validator will make a special system call with sender set as 0x0. + assert!(ctx.sender() == @0x0, ENotSystemAddress); - clock.timestamp_ms = timestamp_ms - } + clock.timestamp_ms = timestamp_ms +} - #[test_only] - /// Expose the functionality of `create()` (usually only done during - /// genesis) for tests that want to create a Clock. - public fun create_for_testing(ctx: &mut TxContext): Clock { - Clock { - id: object::new(ctx), - timestamp_ms: 0, - } +#[test_only] +/// Expose the functionality of `create()` (usually only done during +/// genesis) for tests that want to create a Clock. +public fun create_for_testing(ctx: &mut TxContext): Clock { + Clock { + id: object::new(ctx), + timestamp_ms: 0, } +} - #[test_only] - /// For transactional tests (if a Clock is used as a shared object). - public fun share_for_testing(clock: Clock) { - transfer::share_object(clock) - } +#[test_only] +/// For transactional tests (if a Clock is used as a shared object). +public fun share_for_testing(clock: Clock) { + transfer::share_object(clock) +} - #[test_only] - public fun increment_for_testing(clock: &mut Clock, tick: u64) { - clock.timestamp_ms = clock.timestamp_ms + tick; - } +#[test_only] +public fun increment_for_testing(clock: &mut Clock, tick: u64) { + clock.timestamp_ms = clock.timestamp_ms + tick; +} - #[test_only] - public fun set_for_testing(clock: &mut Clock, timestamp_ms: u64) { - assert!(timestamp_ms >= clock.timestamp_ms); - clock.timestamp_ms = timestamp_ms; - } +#[test_only] +public fun set_for_testing(clock: &mut Clock, timestamp_ms: u64) { + assert!(timestamp_ms >= clock.timestamp_ms); + clock.timestamp_ms = timestamp_ms; +} - #[test_only] - public fun destroy_for_testing(clock: Clock) { - let Clock { id, timestamp_ms: _ } = clock; - id.delete(); - } +#[test_only] +public fun destroy_for_testing(clock: Clock) { + let Clock { id, timestamp_ms: _ } = clock; + id.delete(); } diff --git a/crates/sui-framework/packages/sui-framework/sources/coin.move b/crates/sui-framework/packages/sui-framework/sources/coin.move index 66de87af13c24..92dfc3d59832a 100644 --- a/crates/sui-framework/packages/sui-framework/sources/coin.move +++ b/crates/sui-framework/packages/sui-framework/sources/coin.move @@ -4,613 +4,609 @@ /// Defines the `Coin` type - platform wide representation of fungible /// tokens and coins. `Coin` can be described as a secure wrapper around /// `Balance` type. -module sui::coin { - use std::string; - use std::ascii; - use sui::balance::{Self, Balance, Supply}; - use sui::url::{Self, Url}; - use sui::deny_list::DenyList; - use std::type_name; - - // Allows calling `.split_vec(amounts, ctx)` on `coin` - public use fun sui::pay::split_vec as Coin.split_vec; - - // Allows calling `.join_vec(coins)` on `coin` - public use fun sui::pay::join_vec as Coin.join_vec; - - // Allows calling `.split_and_transfer(amount, recipient, ctx)` on `coin` - public use fun sui::pay::split_and_transfer as Coin.split_and_transfer; - - // Allows calling `.divide_and_keep(n, ctx)` on `coin` - public use fun sui::pay::divide_and_keep as Coin.divide_and_keep; - - /// A type passed to create_supply is not a one-time witness. - const EBadWitness: u64 = 0; - /// Invalid arguments are passed to a function. - const EInvalidArg: u64 = 1; - /// Trying to split a coin more times than its balance allows. - const ENotEnough: u64 = 2; - // #[error] - // const EGlobalPauseNotAllowed: vector = - // b"Kill switch was not allowed at the creation of the DenyCapV2"; - const EGlobalPauseNotAllowed: u64 = 3; - - /// A coin of type `T` worth `value`. Transferable and storable - public struct Coin has key, store { - id: UID, - balance: Balance - } - - /// Each Coin type T created through `create_currency` function will have a - /// unique instance of CoinMetadata that stores the metadata for this coin type. - public struct CoinMetadata has key, store { - id: UID, - /// Number of decimal places the coin uses. - /// A coin with `value ` N and `decimals` D should be shown as N / 10^D - /// E.g., a coin with `value` 7002 and decimals 3 should be displayed as 7.002 - /// This is metadata for display usage only. - decimals: u8, - /// Name for the token - name: string::String, - /// Symbol for the token - symbol: ascii::String, - /// Description of the token - description: string::String, - /// URL for the token logo - icon_url: Option - } +module sui::coin; + +use std::ascii; +use std::string; +use std::type_name; +use sui::balance::{Self, Balance, Supply}; +use sui::deny_list::DenyList; +use sui::url::{Self, Url}; + +// Allows calling `.split_vec(amounts, ctx)` on `coin` +public use fun sui::pay::split_vec as Coin.split_vec; + +// Allows calling `.join_vec(coins)` on `coin` +public use fun sui::pay::join_vec as Coin.join_vec; + +// Allows calling `.split_and_transfer(amount, recipient, ctx)` on `coin` +public use fun sui::pay::split_and_transfer as Coin.split_and_transfer; + +// Allows calling `.divide_and_keep(n, ctx)` on `coin` +public use fun sui::pay::divide_and_keep as Coin.divide_and_keep; + +/// A type passed to create_supply is not a one-time witness. +const EBadWitness: u64 = 0; +/// Invalid arguments are passed to a function. +const EInvalidArg: u64 = 1; +/// Trying to split a coin more times than its balance allows. +const ENotEnough: u64 = 2; +// #[error] +// const EGlobalPauseNotAllowed: vector = +// b"Kill switch was not allowed at the creation of the DenyCapV2"; +const EGlobalPauseNotAllowed: u64 = 3; + +/// A coin of type `T` worth `value`. Transferable and storable +public struct Coin has key, store { + id: UID, + balance: Balance, +} - /// Similar to CoinMetadata, but created only for regulated coins that use the DenyList. - /// This object is always immutable. - public struct RegulatedCoinMetadata has key { - id: UID, - /// The ID of the coin's CoinMetadata object. - coin_metadata_object: ID, - /// The ID of the coin's DenyCap object. - deny_cap_object: ID, - } +/// Each Coin type T created through `create_currency` function will have a +/// unique instance of CoinMetadata that stores the metadata for this coin type. +public struct CoinMetadata has key, store { + id: UID, + /// Number of decimal places the coin uses. + /// A coin with `value ` N and `decimals` D should be shown as N / 10^D + /// E.g., a coin with `value` 7002 and decimals 3 should be displayed as 7.002 + /// This is metadata for display usage only. + decimals: u8, + /// Name for the token + name: string::String, + /// Symbol for the token + symbol: ascii::String, + /// Description of the token + description: string::String, + /// URL for the token logo + icon_url: Option, +} - /// Capability allowing the bearer to mint and burn - /// coins of type `T`. Transferable - public struct TreasuryCap has key, store { - id: UID, - total_supply: Supply - } +/// Similar to CoinMetadata, but created only for regulated coins that use the DenyList. +/// This object is always immutable. +public struct RegulatedCoinMetadata has key { + id: UID, + /// The ID of the coin's CoinMetadata object. + coin_metadata_object: ID, + /// The ID of the coin's DenyCap object. + deny_cap_object: ID, +} - /// Capability allowing the bearer to deny addresses from using the currency's coins-- - /// immediately preventing those addresses from interacting with the coin as an input to a - /// transaction and at the start of the next preventing them from receiving the coin. - /// If `allow_global_pause` is true, the bearer can enable a global pause that behaves as if - /// all addresses were added to the deny list. - public struct DenyCapV2 has key, store { - id: UID, - allow_global_pause: bool, - } +/// Capability allowing the bearer to mint and burn +/// coins of type `T`. Transferable +public struct TreasuryCap has key, store { + id: UID, + total_supply: Supply, +} - // === Supply <-> TreasuryCap morphing and accessors === +/// Capability allowing the bearer to deny addresses from using the currency's coins-- +/// immediately preventing those addresses from interacting with the coin as an input to a +/// transaction and at the start of the next preventing them from receiving the coin. +/// If `allow_global_pause` is true, the bearer can enable a global pause that behaves as if +/// all addresses were added to the deny list. +public struct DenyCapV2 has key, store { + id: UID, + allow_global_pause: bool, +} - /// Return the total number of `T`'s in circulation. - public fun total_supply(cap: &TreasuryCap): u64 { - balance::supply_value(&cap.total_supply) - } +// === Supply <-> TreasuryCap morphing and accessors === - /// Unwrap `TreasuryCap` getting the `Supply`. - /// - /// Operation is irreversible. Supply cannot be converted into a `TreasuryCap` due - /// to different security guarantees (TreasuryCap can be created only once for a type) - public fun treasury_into_supply(treasury: TreasuryCap): Supply { - let TreasuryCap { id, total_supply } = treasury; - id.delete(); - total_supply - } +/// Return the total number of `T`'s in circulation. +public fun total_supply(cap: &TreasuryCap): u64 { + balance::supply_value(&cap.total_supply) +} - /// Get immutable reference to the treasury's `Supply`. - public fun supply_immut(treasury: &TreasuryCap): &Supply { - &treasury.total_supply - } +/// Unwrap `TreasuryCap` getting the `Supply`. +/// +/// Operation is irreversible. Supply cannot be converted into a `TreasuryCap` due +/// to different security guarantees (TreasuryCap can be created only once for a type) +public fun treasury_into_supply(treasury: TreasuryCap): Supply { + let TreasuryCap { id, total_supply } = treasury; + id.delete(); + total_supply +} - /// Get mutable reference to the treasury's `Supply`. - public fun supply_mut(treasury: &mut TreasuryCap): &mut Supply { - &mut treasury.total_supply - } +/// Get immutable reference to the treasury's `Supply`. +public fun supply_immut(treasury: &TreasuryCap): &Supply { + &treasury.total_supply +} - // === Balance <-> Coin accessors and type morphing === +/// Get mutable reference to the treasury's `Supply`. +public fun supply_mut(treasury: &mut TreasuryCap): &mut Supply { + &mut treasury.total_supply +} - /// Public getter for the coin's value - public fun value(self: &Coin): u64 { - self.balance.value() - } +// === Balance <-> Coin accessors and type morphing === - /// Get immutable reference to the balance of a coin. - public fun balance(coin: &Coin): &Balance { - &coin.balance - } +/// Public getter for the coin's value +public fun value(self: &Coin): u64 { + self.balance.value() +} - /// Get a mutable reference to the balance of a coin. - public fun balance_mut(coin: &mut Coin): &mut Balance { - &mut coin.balance - } +/// Get immutable reference to the balance of a coin. +public fun balance(coin: &Coin): &Balance { + &coin.balance +} - /// Wrap a balance into a Coin to make it transferable. - public fun from_balance(balance: Balance, ctx: &mut TxContext): Coin { - Coin { id: object::new(ctx), balance } - } +/// Get a mutable reference to the balance of a coin. +public fun balance_mut(coin: &mut Coin): &mut Balance { + &mut coin.balance +} - /// Destruct a Coin wrapper and keep the balance. - public fun into_balance(coin: Coin): Balance { - let Coin { id, balance } = coin; - id.delete(); - balance - } +/// Wrap a balance into a Coin to make it transferable. +public fun from_balance(balance: Balance, ctx: &mut TxContext): Coin { + Coin { id: object::new(ctx), balance } +} - /// Take a `Coin` worth of `value` from `Balance`. - /// Aborts if `value > balance.value` - public fun take( - balance: &mut Balance, value: u64, ctx: &mut TxContext, - ): Coin { - Coin { - id: object::new(ctx), - balance: balance.split(value) - } - } +/// Destruct a Coin wrapper and keep the balance. +public fun into_balance(coin: Coin): Balance { + let Coin { id, balance } = coin; + id.delete(); + balance +} - /// Put a `Coin` to the `Balance`. - public fun put(balance: &mut Balance, coin: Coin) { - balance.join(into_balance(coin)); +/// Take a `Coin` worth of `value` from `Balance`. +/// Aborts if `value > balance.value` +public fun take(balance: &mut Balance, value: u64, ctx: &mut TxContext): Coin { + Coin { + id: object::new(ctx), + balance: balance.split(value), } +} - // === Base Coin functionality === +/// Put a `Coin` to the `Balance`. +public fun put(balance: &mut Balance, coin: Coin) { + balance.join(into_balance(coin)); +} - /// Consume the coin `c` and add its value to `self`. - /// Aborts if `c.value + self.value > U64_MAX` - public entry fun join(self: &mut Coin, c: Coin) { - let Coin { id, balance } = c; - id.delete(); - self.balance.join(balance); - } +// === Base Coin functionality === - /// Split coin `self` to two coins, one with balance `split_amount`, - /// and the remaining balance is left is `self`. - public fun split( - self: &mut Coin, split_amount: u64, ctx: &mut TxContext - ): Coin { - take(&mut self.balance, split_amount, ctx) - } +/// Consume the coin `c` and add its value to `self`. +/// Aborts if `c.value + self.value > U64_MAX` +public entry fun join(self: &mut Coin, c: Coin) { + let Coin { id, balance } = c; + id.delete(); + self.balance.join(balance); +} - /// Split coin `self` into `n - 1` coins with equal balances. The remainder is left in - /// `self`. Return newly created coins. - public fun divide_into_n( - self: &mut Coin, n: u64, ctx: &mut TxContext - ): vector> { - assert!(n > 0, EInvalidArg); - assert!(n <= value(self), ENotEnough); - - let mut vec = vector[]; - let mut i = 0; - let split_amount = value(self) / n; - while (i < n - 1) { - vec.push_back(self.split(split_amount, ctx)); - i = i + 1; - }; - vec - } +/// Split coin `self` to two coins, one with balance `split_amount`, +/// and the remaining balance is left is `self`. +public fun split(self: &mut Coin, split_amount: u64, ctx: &mut TxContext): Coin { + take(&mut self.balance, split_amount, ctx) +} - /// Make any Coin with a zero value. Useful for placeholding - /// bids/payments or preemptively making empty balances. - public fun zero(ctx: &mut TxContext): Coin { - Coin { id: object::new(ctx), balance: balance::zero() } - } +/// Split coin `self` into `n - 1` coins with equal balances. The remainder is left in +/// `self`. Return newly created coins. +public fun divide_into_n(self: &mut Coin, n: u64, ctx: &mut TxContext): vector> { + assert!(n > 0, EInvalidArg); + assert!(n <= value(self), ENotEnough); + + let mut vec = vector[]; + let mut i = 0; + let split_amount = value(self) / n; + while (i < n - 1) { + vec.push_back(self.split(split_amount, ctx)); + i = i + 1; + }; + vec +} - /// Destroy a coin with value zero - public fun destroy_zero(c: Coin) { - let Coin { id, balance } = c; - id.delete(); - balance.destroy_zero() - } +/// Make any Coin with a zero value. Useful for placeholding +/// bids/payments or preemptively making empty balances. +public fun zero(ctx: &mut TxContext): Coin { + Coin { id: object::new(ctx), balance: balance::zero() } +} - // === Registering new coin types and managing the coin supply === - - /// Create a new currency type `T` as and return the `TreasuryCap` for - /// `T` to the caller. Can only be called with a `one-time-witness` - /// type, ensuring that there's only one `TreasuryCap` per `T`. - public fun create_currency( - witness: T, - decimals: u8, - symbol: vector, - name: vector, - description: vector, - icon_url: Option, - ctx: &mut TxContext - ): (TreasuryCap, CoinMetadata) { - // Make sure there's only one instance of the type T - assert!(sui::types::is_one_time_witness(&witness), EBadWitness); - - ( - TreasuryCap { - id: object::new(ctx), - total_supply: balance::create_supply(witness) - }, - CoinMetadata { - id: object::new(ctx), - decimals, - name: string::utf8(name), - symbol: ascii::string(symbol), - description: string::utf8(description), - icon_url - } - ) - } +/// Destroy a coin with value zero +public fun destroy_zero(c: Coin) { + let Coin { id, balance } = c; + id.delete(); + balance.destroy_zero() +} - /// This creates a new currency, via `create_currency`, but with an extra capability that - /// allows for specific addresses to have their coins frozen. When an address is added to the - /// deny list, it is immediately unable to interact with the currency's coin as input objects. - /// Additionally at the start of the next epoch, they will be unable to receive the currency's - /// coin. - /// The `allow_global_pause` flag enables an additional API that will cause all addresses to be - /// be denied. Note however, that this doesn't affect per-address entries of the deny list and - /// will not change the result of the "contains" APIs. - public fun create_regulated_currency_v2( - witness: T, - decimals: u8, - symbol: vector, - name: vector, - description: vector, - icon_url: Option, - allow_global_pause: bool, - ctx: &mut TxContext, - ): (TreasuryCap, DenyCapV2, CoinMetadata) { - let (treasury_cap, metadata) = create_currency( - witness, - decimals, - symbol, - name, - description, - icon_url, - ctx - ); - let deny_cap = DenyCapV2 { - id: object::new(ctx), - allow_global_pause, - }; - transfer::freeze_object(RegulatedCoinMetadata { +// === Registering new coin types and managing the coin supply === + +/// Create a new currency type `T` as and return the `TreasuryCap` for +/// `T` to the caller. Can only be called with a `one-time-witness` +/// type, ensuring that there's only one `TreasuryCap` per `T`. +public fun create_currency( + witness: T, + decimals: u8, + symbol: vector, + name: vector, + description: vector, + icon_url: Option, + ctx: &mut TxContext, +): (TreasuryCap, CoinMetadata) { + // Make sure there's only one instance of the type T + assert!(sui::types::is_one_time_witness(&witness), EBadWitness); + + ( + TreasuryCap { id: object::new(ctx), - coin_metadata_object: object::id(&metadata), - deny_cap_object: object::id(&deny_cap), - }); - (treasury_cap, deny_cap, metadata) - } - - /// Given the `DenyCap` for a regulated currency, migrate it to the new `DenyCapV2` type. - /// All entries in the deny list will be migrated to the new format. - /// See `create_regulated_currency_v2` for details on the new v2 of the deny list. - public fun migrate_regulated_currency_to_v2( - deny_list: &mut DenyList, - cap: DenyCap, - allow_global_pause: bool, - ctx: &mut TxContext, - ): DenyCapV2 { - let DenyCap { id } = cap; - object::delete(id); - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.migrate_v1_to_v2(DENY_LIST_COIN_INDEX, ty, ctx); - DenyCapV2 { + total_supply: balance::create_supply(witness), + }, + CoinMetadata { id: object::new(ctx), - allow_global_pause, - } - } + decimals, + name: string::utf8(name), + symbol: ascii::string(symbol), + description: string::utf8(description), + icon_url, + }, + ) +} - /// Create a coin worth `value` and increase the total supply - /// in `cap` accordingly. - public fun mint( - cap: &mut TreasuryCap, value: u64, ctx: &mut TxContext, - ): Coin { - Coin { - id: object::new(ctx), - balance: cap.total_supply.increase_supply(value) - } - } +/// This creates a new currency, via `create_currency`, but with an extra capability that +/// allows for specific addresses to have their coins frozen. When an address is added to the +/// deny list, it is immediately unable to interact with the currency's coin as input objects. +/// Additionally at the start of the next epoch, they will be unable to receive the currency's +/// coin. +/// The `allow_global_pause` flag enables an additional API that will cause all addresses to be +/// be denied. Note however, that this doesn't affect per-address entries of the deny list and +/// will not change the result of the "contains" APIs. +public fun create_regulated_currency_v2( + witness: T, + decimals: u8, + symbol: vector, + name: vector, + description: vector, + icon_url: Option, + allow_global_pause: bool, + ctx: &mut TxContext, +): (TreasuryCap, DenyCapV2, CoinMetadata) { + let (treasury_cap, metadata) = create_currency( + witness, + decimals, + symbol, + name, + description, + icon_url, + ctx, + ); + let deny_cap = DenyCapV2 { + id: object::new(ctx), + allow_global_pause, + }; + transfer::freeze_object(RegulatedCoinMetadata { + id: object::new(ctx), + coin_metadata_object: object::id(&metadata), + deny_cap_object: object::id(&deny_cap), + }); + (treasury_cap, deny_cap, metadata) +} - /// Mint some amount of T as a `Balance` and increase the total - /// supply in `cap` accordingly. - /// Aborts if `value` + `cap.total_supply` >= U64_MAX - public fun mint_balance( - cap: &mut TreasuryCap, value: u64 - ): Balance { - cap.total_supply.increase_supply(value) +/// Given the `DenyCap` for a regulated currency, migrate it to the new `DenyCapV2` type. +/// All entries in the deny list will be migrated to the new format. +/// See `create_regulated_currency_v2` for details on the new v2 of the deny list. +public fun migrate_regulated_currency_to_v2( + deny_list: &mut DenyList, + cap: DenyCap, + allow_global_pause: bool, + ctx: &mut TxContext, +): DenyCapV2 { + let DenyCap { id } = cap; + object::delete(id); + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.migrate_v1_to_v2(DENY_LIST_COIN_INDEX, ty, ctx); + DenyCapV2 { + id: object::new(ctx), + allow_global_pause, } +} - /// Destroy the coin `c` and decrease the total supply in `cap` - /// accordingly. - public entry fun burn(cap: &mut TreasuryCap, c: Coin): u64 { - let Coin { id, balance } = c; - id.delete(); - cap.total_supply.decrease_supply(balance) +/// Create a coin worth `value` and increase the total supply +/// in `cap` accordingly. +public fun mint(cap: &mut TreasuryCap, value: u64, ctx: &mut TxContext): Coin { + Coin { + id: object::new(ctx), + balance: cap.total_supply.increase_supply(value), } +} - /// Adds the given address to the deny list, preventing it from interacting with the specified - /// coin type as an input to a transaction. Additionally at the start of the next epoch, the - /// address will be unable to receive objects of this coin type. - public fun deny_list_v2_add( - deny_list: &mut DenyList, - _deny_cap: &mut DenyCapV2, - addr: address, - ctx: &mut TxContext, - ) { - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.v2_add(DENY_LIST_COIN_INDEX, ty, addr, ctx) - } +/// Mint some amount of T as a `Balance` and increase the total +/// supply in `cap` accordingly. +/// Aborts if `value` + `cap.total_supply` >= U64_MAX +public fun mint_balance(cap: &mut TreasuryCap, value: u64): Balance { + cap.total_supply.increase_supply(value) +} - /// Removes an address from the deny list. Similar to `deny_list_v2_add`, the effect for input - /// objects will be immediate, but the effect for receiving objects will be delayed until the - /// next epoch. - public fun deny_list_v2_remove( - deny_list: &mut DenyList, - _deny_cap: &mut DenyCapV2, - addr: address, - ctx: &mut TxContext, - ) { - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.v2_remove(DENY_LIST_COIN_INDEX, ty, addr, ctx) - } +/// Destroy the coin `c` and decrease the total supply in `cap` +/// accordingly. +public entry fun burn(cap: &mut TreasuryCap, c: Coin): u64 { + let Coin { id, balance } = c; + id.delete(); + cap.total_supply.decrease_supply(balance) +} - /// Check if the deny list contains the given address for the current epoch. Denied addresses - /// in the current epoch will be unable to receive objects of this coin type. - public fun deny_list_v2_contains_current_epoch( - deny_list: &DenyList, - addr: address, - ctx: &TxContext, - ): bool { - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.v2_contains_current_epoch(DENY_LIST_COIN_INDEX, ty, addr, ctx) - } +/// Adds the given address to the deny list, preventing it from interacting with the specified +/// coin type as an input to a transaction. Additionally at the start of the next epoch, the +/// address will be unable to receive objects of this coin type. +public fun deny_list_v2_add( + deny_list: &mut DenyList, + _deny_cap: &mut DenyCapV2, + addr: address, + ctx: &mut TxContext, +) { + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_add(DENY_LIST_COIN_INDEX, ty, addr, ctx) +} - /// Check if the deny list contains the given address for the next epoch. Denied addresses in - /// the next epoch will immediately be unable to use objects of this coin type as inputs. At the - /// start of the next epoch, the address will be unable to receive objects of this coin type. - public fun deny_list_v2_contains_next_epoch( - deny_list: &DenyList, - addr: address, - ): bool { - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.v2_contains_next_epoch(DENY_LIST_COIN_INDEX, ty, addr) - } +/// Removes an address from the deny list. Similar to `deny_list_v2_add`, the effect for input +/// objects will be immediate, but the effect for receiving objects will be delayed until the +/// next epoch. +public fun deny_list_v2_remove( + deny_list: &mut DenyList, + _deny_cap: &mut DenyCapV2, + addr: address, + ctx: &mut TxContext, +) { + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_remove(DENY_LIST_COIN_INDEX, ty, addr, ctx) +} - /// Enable the global pause for the given coin type. This will immediately prevent all addresses - /// from using objects of this coin type as inputs. At the start of the next epoch, all - /// addresses will be unable to receive objects of this coin type. - #[allow(unused_mut_parameter)] - public fun deny_list_v2_enable_global_pause( - deny_list: &mut DenyList, - deny_cap: &mut DenyCapV2, - ctx: &mut TxContext, - ) { - assert!(deny_cap.allow_global_pause, EGlobalPauseNotAllowed); - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.v2_enable_global_pause(DENY_LIST_COIN_INDEX, ty, ctx) - } +/// Check if the deny list contains the given address for the current epoch. Denied addresses +/// in the current epoch will be unable to receive objects of this coin type. +public fun deny_list_v2_contains_current_epoch( + deny_list: &DenyList, + addr: address, + ctx: &TxContext, +): bool { + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_contains_current_epoch(DENY_LIST_COIN_INDEX, ty, addr, ctx) +} - /// Disable the global pause for the given coin type. This will immediately allow all addresses - /// to resume using objects of this coin type as inputs. However, receiving objects of this coin - /// type will still be paused until the start of the next epoch. - #[allow(unused_mut_parameter)] - public fun deny_list_v2_disable_global_pause( - deny_list: &mut DenyList, - deny_cap: &mut DenyCapV2, - ctx: &mut TxContext, - ) { - assert!(deny_cap.allow_global_pause, EGlobalPauseNotAllowed); - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.v2_disable_global_pause(DENY_LIST_COIN_INDEX, ty, ctx) - } +/// Check if the deny list contains the given address for the next epoch. Denied addresses in +/// the next epoch will immediately be unable to use objects of this coin type as inputs. At the +/// start of the next epoch, the address will be unable to receive objects of this coin type. +public fun deny_list_v2_contains_next_epoch(deny_list: &DenyList, addr: address): bool { + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_contains_next_epoch(DENY_LIST_COIN_INDEX, ty, addr) +} - /// Check if the global pause is enabled for the given coin type in the current epoch. - public fun deny_list_v2_is_global_pause_enabled_current_epoch( - deny_list: &DenyList, - ctx: &TxContext, - ): bool { - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.v2_is_global_pause_enabled_current_epoch(DENY_LIST_COIN_INDEX, ty, ctx) - } +/// Enable the global pause for the given coin type. This will immediately prevent all addresses +/// from using objects of this coin type as inputs. At the start of the next epoch, all +/// addresses will be unable to receive objects of this coin type. +#[allow(unused_mut_parameter)] +public fun deny_list_v2_enable_global_pause( + deny_list: &mut DenyList, + deny_cap: &mut DenyCapV2, + ctx: &mut TxContext, +) { + assert!(deny_cap.allow_global_pause, EGlobalPauseNotAllowed); + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_enable_global_pause(DENY_LIST_COIN_INDEX, ty, ctx) +} - /// Check if the global pause is enabled for the given coin type in the next epoch. - public fun deny_list_v2_is_global_pause_enabled_next_epoch( - deny_list: &DenyList, - ): bool { - let ty = type_name::get_with_original_ids().into_string().into_bytes(); - deny_list.v2_is_global_pause_enabled_next_epoch(DENY_LIST_COIN_INDEX, ty) - } +/// Disable the global pause for the given coin type. This will immediately allow all addresses +/// to resume using objects of this coin type as inputs. However, receiving objects of this coin +/// type will still be paused until the start of the next epoch. +#[allow(unused_mut_parameter)] +public fun deny_list_v2_disable_global_pause( + deny_list: &mut DenyList, + deny_cap: &mut DenyCapV2, + ctx: &mut TxContext, +) { + assert!(deny_cap.allow_global_pause, EGlobalPauseNotAllowed); + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_disable_global_pause(DENY_LIST_COIN_INDEX, ty, ctx) +} - // === Entrypoints === +/// Check if the global pause is enabled for the given coin type in the current epoch. +public fun deny_list_v2_is_global_pause_enabled_current_epoch( + deny_list: &DenyList, + ctx: &TxContext, +): bool { + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_is_global_pause_enabled_current_epoch(DENY_LIST_COIN_INDEX, ty, ctx) +} - /// Mint `amount` of `Coin` and send it to `recipient`. Invokes `mint()`. - public entry fun mint_and_transfer( - c: &mut TreasuryCap, amount: u64, recipient: address, ctx: &mut TxContext - ) { - transfer::public_transfer(mint(c, amount, ctx), recipient) - } +/// Check if the global pause is enabled for the given coin type in the next epoch. +public fun deny_list_v2_is_global_pause_enabled_next_epoch(deny_list: &DenyList): bool { + let ty = type_name::get_with_original_ids().into_string().into_bytes(); + deny_list.v2_is_global_pause_enabled_next_epoch(DENY_LIST_COIN_INDEX, ty) +} - // === Update coin metadata === +// === Entrypoints === - /// Update name of the coin in `CoinMetadata` - public entry fun update_name( - _treasury: &TreasuryCap, metadata: &mut CoinMetadata, name: string::String - ) { - metadata.name = name; - } +/// Mint `amount` of `Coin` and send it to `recipient`. Invokes `mint()`. +public entry fun mint_and_transfer( + c: &mut TreasuryCap, + amount: u64, + recipient: address, + ctx: &mut TxContext, +) { + transfer::public_transfer(mint(c, amount, ctx), recipient) +} - /// Update the symbol of the coin in `CoinMetadata` - public entry fun update_symbol( - _treasury: &TreasuryCap, metadata: &mut CoinMetadata, symbol: ascii::String - ) { - metadata.symbol = symbol; - } +// === Update coin metadata === - /// Update the description of the coin in `CoinMetadata` - public entry fun update_description( - _treasury: &TreasuryCap, metadata: &mut CoinMetadata, description: string::String - ) { - metadata.description = description; - } +/// Update name of the coin in `CoinMetadata` +public entry fun update_name( + _treasury: &TreasuryCap, + metadata: &mut CoinMetadata, + name: string::String, +) { + metadata.name = name; +} - /// Update the url of the coin in `CoinMetadata` - public entry fun update_icon_url( - _treasury: &TreasuryCap, metadata: &mut CoinMetadata, url: ascii::String - ) { - metadata.icon_url = option::some(url::new_unsafe(url)); - } +/// Update the symbol of the coin in `CoinMetadata` +public entry fun update_symbol( + _treasury: &TreasuryCap, + metadata: &mut CoinMetadata, + symbol: ascii::String, +) { + metadata.symbol = symbol; +} - // === Get coin metadata fields for on-chain consumption === +/// Update the description of the coin in `CoinMetadata` +public entry fun update_description( + _treasury: &TreasuryCap, + metadata: &mut CoinMetadata, + description: string::String, +) { + metadata.description = description; +} - public fun get_decimals(metadata: &CoinMetadata): u8 { - metadata.decimals - } +/// Update the url of the coin in `CoinMetadata` +public entry fun update_icon_url( + _treasury: &TreasuryCap, + metadata: &mut CoinMetadata, + url: ascii::String, +) { + metadata.icon_url = option::some(url::new_unsafe(url)); +} - public fun get_name(metadata: &CoinMetadata): string::String { - metadata.name - } +// === Get coin metadata fields for on-chain consumption === - public fun get_symbol(metadata: &CoinMetadata): ascii::String { - metadata.symbol - } +public fun get_decimals(metadata: &CoinMetadata): u8 { + metadata.decimals +} - public fun get_description(metadata: &CoinMetadata): string::String { - metadata.description - } +public fun get_name(metadata: &CoinMetadata): string::String { + metadata.name +} - public fun get_icon_url(metadata: &CoinMetadata): Option { - metadata.icon_url - } +public fun get_symbol(metadata: &CoinMetadata): ascii::String { + metadata.symbol +} - // === Test-only code === +public fun get_description(metadata: &CoinMetadata): string::String { + metadata.description +} - #[test_only] - /// Mint coins of any type for (obviously!) testing purposes only - public fun mint_for_testing(value: u64, ctx: &mut TxContext): Coin { - Coin { id: object::new(ctx), balance: balance::create_for_testing(value) } - } +public fun get_icon_url(metadata: &CoinMetadata): Option { + metadata.icon_url +} - #[test_only] - /// Burn coins of any type for testing purposes only - public fun burn_for_testing(coin: Coin): u64 { - let Coin { id, balance } = coin; - id.delete(); - balance.destroy_for_testing() - } +// === Test-only code === - #[test_only] - /// Create a `TreasuryCap` for any `Coin` for testing purposes. - public fun create_treasury_cap_for_testing( - ctx: &mut TxContext - ): TreasuryCap { - TreasuryCap { - id: object::new(ctx), - total_supply: balance::create_supply_for_testing() - } - } +#[test_only] +/// Mint coins of any type for (obviously!) testing purposes only +public fun mint_for_testing(value: u64, ctx: &mut TxContext): Coin { + Coin { id: object::new(ctx), balance: balance::create_for_testing(value) } +} - // === Deprecated code === +#[test_only] +/// Burn coins of any type for testing purposes only +public fun burn_for_testing(coin: Coin): u64 { + let Coin { id, balance } = coin; + id.delete(); + balance.destroy_for_testing() +} - // oops, wanted treasury: &TreasuryCap - public fun supply(treasury: &mut TreasuryCap): &Supply { - &treasury.total_supply +#[test_only] +/// Create a `TreasuryCap` for any `Coin` for testing purposes. +public fun create_treasury_cap_for_testing(ctx: &mut TxContext): TreasuryCap { + TreasuryCap { + id: object::new(ctx), + total_supply: balance::create_supply_for_testing(), } +} - // deprecated as we have CoinMetadata now - #[allow(unused_field)] - public struct CurrencyCreated has copy, drop { - decimals: u8 - } +// === Deprecated code === - /// Capability allowing the bearer to freeze addresses, preventing those addresses from - /// interacting with the coin as an input to a transaction. - public struct DenyCap has key, store { - id: UID, - } +// oops, wanted treasury: &TreasuryCap +public fun supply(treasury: &mut TreasuryCap): &Supply { + &treasury.total_supply +} - /// This creates a new currency, via `create_currency`, but with an extra capability that - /// allows for specific addresses to have their coins frozen. Those addresses cannot interact - /// with the coin as input objects. - #[deprecated(note = b"For new coins, use `create_regulated_currency_v2`. To migrate existing regulated currencies, migrate with `migrate_regulated_currency_to_v2`")] - public fun create_regulated_currency( - witness: T, - decimals: u8, - symbol: vector, - name: vector, - description: vector, - icon_url: Option, - ctx: &mut TxContext - ): (TreasuryCap, DenyCap, CoinMetadata) { - let (treasury_cap, metadata) = create_currency( - witness, - decimals, - symbol, - name, - description, - icon_url, - ctx - ); - let deny_cap = DenyCap { - id: object::new(ctx), - }; - transfer::freeze_object(RegulatedCoinMetadata { - id: object::new(ctx), - coin_metadata_object: object::id(&metadata), - deny_cap_object: object::id(&deny_cap), - }); - (treasury_cap, deny_cap, metadata) - } +// deprecated as we have CoinMetadata now +#[allow(unused_field)] +public struct CurrencyCreated has copy, drop { + decimals: u8, +} +/// Capability allowing the bearer to freeze addresses, preventing those addresses from +/// interacting with the coin as an input to a transaction. +public struct DenyCap has key, store { + id: UID, +} - /// The index into the deny list vector for the `sui::coin::Coin` type. - const DENY_LIST_COIN_INDEX: u64 = 0; // TODO public(package) const - - /// Adds the given address to the deny list, preventing it - /// from interacting with the specified coin type as an input to a transaction. - #[deprecated(note = b"Use `migrate_regulated_currency_to_v2` to migrate to v2 and then use `deny_list_v2_add`")] - public fun deny_list_add( - deny_list: &mut DenyList, - _deny_cap: &mut DenyCap, - addr: address, - _ctx: &mut TxContext - ) { - let `type` = - type_name::into_string(type_name::get_with_original_ids()).into_bytes(); - deny_list.v1_add( - DENY_LIST_COIN_INDEX, - `type`, - addr, - ) - } +/// This creates a new currency, via `create_currency`, but with an extra capability that +/// allows for specific addresses to have their coins frozen. Those addresses cannot interact +/// with the coin as input objects. +#[ + deprecated( + note = b"For new coins, use `create_regulated_currency_v2`. To migrate existing regulated currencies, migrate with `migrate_regulated_currency_to_v2`", + ), +] +public fun create_regulated_currency( + witness: T, + decimals: u8, + symbol: vector, + name: vector, + description: vector, + icon_url: Option, + ctx: &mut TxContext, +): (TreasuryCap, DenyCap, CoinMetadata) { + let (treasury_cap, metadata) = create_currency( + witness, + decimals, + symbol, + name, + description, + icon_url, + ctx, + ); + let deny_cap = DenyCap { + id: object::new(ctx), + }; + transfer::freeze_object(RegulatedCoinMetadata { + id: object::new(ctx), + coin_metadata_object: object::id(&metadata), + deny_cap_object: object::id(&deny_cap), + }); + (treasury_cap, deny_cap, metadata) +} - /// Removes an address from the deny list. - /// Aborts with `ENotFrozen` if the address is not already in the list. - #[deprecated(note = b"Use `migrate_regulated_currency_to_v2` to migrate to v2 and then use `deny_list_v2_remove`")] - public fun deny_list_remove( - deny_list: &mut DenyList, - _deny_cap: &mut DenyCap, - addr: address, - _ctx: &mut TxContext - ) { - let `type` = - type_name::into_string(type_name::get_with_original_ids()).into_bytes(); - deny_list.v1_remove( - DENY_LIST_COIN_INDEX, - `type`, - addr, - ) - } +/// The index into the deny list vector for the `sui::coin::Coin` type. +const DENY_LIST_COIN_INDEX: u64 = 0; // TODO public(package) const + +/// Adds the given address to the deny list, preventing it +/// from interacting with the specified coin type as an input to a transaction. +#[ + deprecated( + note = b"Use `migrate_regulated_currency_to_v2` to migrate to v2 and then use `deny_list_v2_add`", + ), +] +public fun deny_list_add( + deny_list: &mut DenyList, + _deny_cap: &mut DenyCap, + addr: address, + _ctx: &mut TxContext, +) { + let `type` = type_name::into_string(type_name::get_with_original_ids()).into_bytes(); + deny_list.v1_add(DENY_LIST_COIN_INDEX, `type`, addr) +} - /// Returns true iff the given address is denied for the given coin type. It will - /// return false if given a non-coin type. - #[deprecated(note = b"Use `migrate_regulated_currency_to_v2` to migrate to v2 and then use `deny_list_v2_contains_next_epoch` or `deny_list_v2_contains_current_epoch`")] - public fun deny_list_contains( - deny_list: &DenyList, - addr: address, - ): bool { - let name = type_name::get_with_original_ids(); - if (type_name::is_primitive(&name)) return false; - - let `type` = type_name::into_string(name).into_bytes(); - deny_list.v1_contains(DENY_LIST_COIN_INDEX, `type`, addr) - } +/// Removes an address from the deny list. +/// Aborts with `ENotFrozen` if the address is not already in the list. +#[ + deprecated( + note = b"Use `migrate_regulated_currency_to_v2` to migrate to v2 and then use `deny_list_v2_remove`", + ), +] +public fun deny_list_remove( + deny_list: &mut DenyList, + _deny_cap: &mut DenyCap, + addr: address, + _ctx: &mut TxContext, +) { + let `type` = type_name::into_string(type_name::get_with_original_ids()).into_bytes(); + deny_list.v1_remove(DENY_LIST_COIN_INDEX, `type`, addr) +} + +/// Returns true iff the given address is denied for the given coin type. It will +/// return false if given a non-coin type. +#[ + deprecated( + note = b"Use `migrate_regulated_currency_to_v2` to migrate to v2 and then use `deny_list_v2_contains_next_epoch` or `deny_list_v2_contains_current_epoch`", + ), +] +public fun deny_list_contains(deny_list: &DenyList, addr: address): bool { + let name = type_name::get_with_original_ids(); + if (type_name::is_primitive(&name)) return false; + + let `type` = type_name::into_string(name).into_bytes(); + deny_list.v1_contains(DENY_LIST_COIN_INDEX, `type`, addr) } diff --git a/crates/sui-framework/packages/sui-framework/sources/config.move b/crates/sui-framework/packages/sui-framework/sources/config.move index 5c32204a4d9f2..116203066814b 100644 --- a/crates/sui-framework/packages/sui-framework/sources/config.move +++ b/crates/sui-framework/packages/sui-framework/sources/config.move @@ -1,115 +1,76 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -module sui::config { +module sui::config; - use sui::dynamic_field as field; +use sui::dynamic_field as field; - // #[error] - // const EAlreadySetForEpoch: vector = - // b"Setting was already updated at this epoch for the provided Config"; - const EAlreadySetForEpoch: u64 = 0; +// #[error] +// const EAlreadySetForEpoch: vector = +// b"Setting was already updated at this epoch for the provided Config"; +const EAlreadySetForEpoch: u64 = 0; - // #[error] - // const ENotSetForEpoch: vector = - // b"Setting was not updated at this epoch for the provided Config"; - const ENotSetForEpoch: u64 = 1; +// #[error] +// const ENotSetForEpoch: vector = +// b"Setting was not updated at this epoch for the provided Config"; +const ENotSetForEpoch: u64 = 1; - // #[error] - // const ENotSetForEpoch: vector = b"Could not generate a layout for the type"; - #[allow(unused_const)] - const EBCSSerializationFailure: u64 = 2; +// #[error] +// const ENotSetForEpoch: vector = b"Could not generate a layout for the type"; +#[allow(unused_const)] +const EBCSSerializationFailure: u64 = 2; - public struct Config has key { - id: UID, - } +public struct Config has key { + id: UID, +} - public struct Setting has store, drop { - data: Option>, - } +public struct Setting has store, drop { + data: Option>, +} - public struct SettingData has store, drop { - newer_value_epoch: u64, - newer_value: Option, - older_value_opt: Option, - } +public struct SettingData has store, drop { + newer_value_epoch: u64, + newer_value: Option, + older_value_opt: Option, +} - public(package) fun new(_cap: &mut WriteCap, ctx: &mut TxContext): Config { - Config { id: object::new(ctx) } - } +public(package) fun new(_cap: &mut WriteCap, ctx: &mut TxContext): Config { + Config { id: object::new(ctx) } +} - #[allow(lint(share_owned))] - public(package) fun share(config: Config) { - transfer::share_object(config) - } +#[allow(lint(share_owned))] +public(package) fun share(config: Config) { + transfer::share_object(config) +} - public(package) fun transfer(config: Config, owner: address) { - transfer::transfer(config, owner) - } +public(package) fun transfer(config: Config, owner: address) { + transfer::transfer(config, owner) +} - #[allow(unused_mut_parameter)] - public(package) fun add_for_next_epoch< - WriteCap, - Name: copy + drop + store, - Value: copy + drop + store, - >( - config: &mut Config, - _cap: &mut WriteCap, - name: Name, - value: Value, - ctx: &mut TxContext, - ): Option { - let epoch = ctx.epoch(); - if (!field::exists_(&config.id, name)) { - let sobj = Setting { - data: option::some(SettingData { - newer_value_epoch: epoch, - newer_value: option::some(value), - older_value_opt: option::none(), - }), - }; - field::add(&mut config.id, name, sobj); - option::none() - } else { - let sobj: &mut Setting = field::borrow_mut(&mut config.id, name); - let SettingData { - newer_value_epoch, - newer_value, - older_value_opt, - } = sobj.data.extract(); - let (older_value_opt, removed_value) = - if (epoch > newer_value_epoch) { - // if the `newer_value` is for a previous epoch, move it to `older_value_opt` - (move newer_value, move older_value_opt) - } else { - // the current epoch cannot be less than the `newer_value_epoch` - assert!(epoch == newer_value_epoch); - // if the `newer_value` is for the current epoch, then the option must be `none` - assert!(newer_value.is_none(), EAlreadySetForEpoch); - (move older_value_opt, option::none()) - }; - sobj.data.fill(SettingData { +#[allow(unused_mut_parameter)] +public(package) fun add_for_next_epoch< + WriteCap, + Name: copy + drop + store, + Value: copy + drop + store, +>( + config: &mut Config, + _cap: &mut WriteCap, + name: Name, + value: Value, + ctx: &mut TxContext, +): Option { + let epoch = ctx.epoch(); + if (!field::exists_(&config.id, name)) { + let sobj = Setting { + data: option::some(SettingData { newer_value_epoch: epoch, newer_value: option::some(value), - older_value_opt, - }); - removed_value - } - } - - #[allow(unused_mut_parameter)] - public(package) fun remove_for_next_epoch< - WriteCap, - Name: copy + drop + store, - Value: copy + drop + store, - >( - config: &mut Config, - _cap: &mut WriteCap, - name: Name, - ctx: &mut TxContext, - ): Option { - let epoch = ctx.epoch(); - if (!field::exists_(&config.id, name)) return option::none(); + older_value_opt: option::none(), + }), + }; + field::add(&mut config.id, name, sobj); + option::none() + } else { let sobj: &mut Setting = field::borrow_mut(&mut config.id, name); let SettingData { newer_value_epoch, @@ -119,171 +80,208 @@ module sui::config { let (older_value_opt, removed_value) = if (epoch > newer_value_epoch) { // if the `newer_value` is for a previous epoch, move it to `older_value_opt` - (move newer_value, option::none()) + (move newer_value, move older_value_opt) } else { // the current epoch cannot be less than the `newer_value_epoch` assert!(epoch == newer_value_epoch); - (move older_value_opt, move newer_value) + // if the `newer_value` is for the current epoch, then the option must be `none` + assert!(newer_value.is_none(), EAlreadySetForEpoch); + (move older_value_opt, option::none()) }; - let older_value_opt_is_none = older_value_opt.is_none(); sobj.data.fill(SettingData { newer_value_epoch: epoch, - newer_value: option::none(), + newer_value: option::some(value), older_value_opt, }); - if (older_value_opt_is_none) { - field::remove<_, Setting>(&mut config.id, name); - }; removed_value } +} - public(package) fun exists_with_type< - WriteCap, - Name: copy + drop + store, - Value: copy + drop + store, - >( - config: &Config, - name: Name, - ): bool { - field::exists_with_type<_, Setting>(&config.id, name) - } +#[allow(unused_mut_parameter)] +public(package) fun remove_for_next_epoch< + WriteCap, + Name: copy + drop + store, + Value: copy + drop + store, +>( + config: &mut Config, + _cap: &mut WriteCap, + name: Name, + ctx: &mut TxContext, +): Option { + let epoch = ctx.epoch(); + if (!field::exists_(&config.id, name)) return option::none(); + let sobj: &mut Setting = field::borrow_mut(&mut config.id, name); + let SettingData { + newer_value_epoch, + newer_value, + older_value_opt, + } = sobj.data.extract(); + let (older_value_opt, removed_value) = + if (epoch > newer_value_epoch) { + // if the `newer_value` is for a previous epoch, move it to `older_value_opt` + (move newer_value, option::none()) + } else { + // the current epoch cannot be less than the `newer_value_epoch` + assert!(epoch == newer_value_epoch); + (move older_value_opt, move newer_value) + }; + let older_value_opt_is_none = older_value_opt.is_none(); + sobj.data.fill(SettingData { + newer_value_epoch: epoch, + newer_value: option::none(), + older_value_opt, + }); + if (older_value_opt_is_none) { + field::remove<_, Setting>(&mut config.id, name); + }; + removed_value +} - #[allow(unused_mut_parameter)] - public(package) fun exists_with_type_for_next_epoch< - WriteCap, - Name: copy + drop + store, - Value: copy + drop + store, - >( - config: & Config, - name: Name, - ctx: &TxContext, - ): bool { - field::exists_with_type<_, Setting>(&config.id, name) && { - let epoch = ctx.epoch(); - let sobj: &Setting = field::borrow(&config.id, name); - epoch == sobj.data.borrow().newer_value_epoch && - sobj.data.borrow().newer_value.is_some() - } - } +public(package) fun exists_with_type< + WriteCap, + Name: copy + drop + store, + Value: copy + drop + store, +>( + config: &Config, + name: Name, +): bool { + field::exists_with_type<_, Setting>(&config.id, name) +} - #[allow(unused_mut_parameter)] - public(package) fun borrow_for_next_epoch_mut< - WriteCap, - Name: copy + drop + store, - Value: copy + drop + store, - >( - config: &mut Config, - _cap: &mut WriteCap, - name: Name, - ctx: &mut TxContext, - ): &mut Value { +#[allow(unused_mut_parameter)] +public(package) fun exists_with_type_for_next_epoch< + WriteCap, + Name: copy + drop + store, + Value: copy + drop + store, +>( + config: &Config, + name: Name, + ctx: &TxContext, +): bool { + field::exists_with_type<_, Setting>(&config.id, name) && { let epoch = ctx.epoch(); - let sobj: &mut Setting = field::borrow_mut(&mut config.id, name); - let data = sobj.data.borrow_mut(); - assert!(data.newer_value_epoch == epoch, ENotSetForEpoch); - assert!(data.newer_value.is_some(), ENotSetForEpoch); - data.newer_value.borrow_mut() - } - - public(package) fun read_setting_for_next_epoch< - WriteCap, - Name: copy + drop + store, - Value: copy + drop + store, - >( - config: &Config, - name: Name, - ): Option { - if (!field::exists_with_type<_, Setting>(&config.id, name)) return option::none(); let sobj: &Setting = field::borrow(&config.id, name); - let data = sobj.data.borrow(); - data.newer_value + epoch == sobj.data.borrow().newer_value_epoch && + sobj.data.borrow().newer_value.is_some() } +} + +#[allow(unused_mut_parameter)] +public(package) fun borrow_for_next_epoch_mut< + WriteCap, + Name: copy + drop + store, + Value: copy + drop + store, +>( + config: &mut Config, + _cap: &mut WriteCap, + name: Name, + ctx: &mut TxContext, +): &mut Value { + let epoch = ctx.epoch(); + let sobj: &mut Setting = field::borrow_mut(&mut config.id, name); + let data = sobj.data.borrow_mut(); + assert!(data.newer_value_epoch == epoch, ENotSetForEpoch); + assert!(data.newer_value.is_some(), ENotSetForEpoch); + data.newer_value.borrow_mut() +} + +public(package) fun read_setting_for_next_epoch< + WriteCap, + Name: copy + drop + store, + Value: copy + drop + store, +>( + config: &Config, + name: Name, +): Option { + if (!field::exists_with_type<_, Setting>(&config.id, name)) return option::none(); + let sobj: &Setting = field::borrow(&config.id, name); + let data = sobj.data.borrow(); + data.newer_value +} + +public(package) macro fun entry< + $WriteCap, + $Name: copy + drop + store, + $Value: copy + drop + store, +>( + $config: &mut Config<$WriteCap>, + $cap: &mut $WriteCap, + $name: $Name, + $initial_for_next_epoch: |&mut Config<$WriteCap>, &mut $WriteCap, &mut TxContext| -> $Value, + $ctx: &mut TxContext, +): &mut $Value { + let config = $config; + let cap = $cap; + let name = $name; + let ctx = $ctx; + if (!config.exists_with_type_for_next_epoch<_, _, $Value>(name, ctx)) { + let initial = $initial_for_next_epoch(config, cap, ctx); + config.add_for_next_epoch(cap, name, initial, ctx); + }; + config.borrow_for_next_epoch_mut(cap, name, ctx) +} - public(package) macro fun entry< - $WriteCap, - $Name: copy + drop + store, - $Value: copy + drop + store, - >( - $config: &mut Config<$WriteCap>, - $cap: &mut $WriteCap, - $name: $Name, - $initial_for_next_epoch: |&mut Config<$WriteCap>, &mut $WriteCap, &mut TxContext| -> $Value, - $ctx: &mut TxContext, - ): &mut $Value { - let config = $config; - let cap = $cap; - let name = $name; - let ctx = $ctx; +public(package) macro fun update< + $WriteCap, + $Name: copy + drop + store, + $Value: copy + drop + store, +>( + $config: &mut Config<$WriteCap>, + $cap: &mut $WriteCap, + $name: $Name, + $initial_for_next_epoch: |&mut Config<$WriteCap>, &mut $WriteCap, &mut TxContext| -> $Value, + $update_for_next_epoch: |Option<$Value>, &mut $Value|, + $ctx: &mut TxContext, +) { + let config = $config; + let cap = $cap; + let name = $name; + let ctx = $ctx; + let old_value_opt = if (!config.exists_with_type_for_next_epoch<_, _, $Value>(name, ctx)) { let initial = $initial_for_next_epoch(config, cap, ctx); - config.add_for_next_epoch(cap, name, initial, ctx); + config.add_for_next_epoch(cap, name, initial, ctx) + } else { + option::none() }; - config.borrow_for_next_epoch_mut(cap, name, ctx) - } - - public(package) macro fun update< - $WriteCap, - $Name: copy + drop + store, - $Value: copy + drop + store, - >( - $config: &mut Config<$WriteCap>, - $cap: &mut $WriteCap, - $name: $Name, - $initial_for_next_epoch: |&mut Config<$WriteCap>, &mut $WriteCap, &mut TxContext| -> $Value, - $update_for_next_epoch: |Option<$Value>, &mut $Value|, - $ctx: &mut TxContext, - ) { - let config = $config; - let cap = $cap; - let name = $name; - let ctx = $ctx; - let old_value_opt = - if (!config.exists_with_type_for_next_epoch<_, _, $Value>(name, ctx)) { - let initial = $initial_for_next_epoch(config, cap, ctx); - config.add_for_next_epoch(cap, name, initial, ctx) - } else { - option::none() - }; - $update_for_next_epoch(old_value_opt, config.borrow_for_next_epoch_mut(cap, name, ctx)); - } + $update_for_next_epoch(old_value_opt, config.borrow_for_next_epoch_mut(cap, name, ctx)); +} - public(package) fun read_setting( - config: ID, - name: Name, - ctx: &TxContext, - ): Option { - use sui::dynamic_field::Field; - let config_id = config.to_address(); - let setting_df = field::hash_type_and_key(config_id, name); - read_setting_impl>, Setting, SettingData, Value>( - config_id, - setting_df, - ctx.epoch(), - ) - } +public(package) fun read_setting( + config: ID, + name: Name, + ctx: &TxContext, +): Option { + use sui::dynamic_field::Field; + let config_id = config.to_address(); + let setting_df = field::hash_type_and_key(config_id, name); + read_setting_impl>, Setting, SettingData, Value>( + config_id, + setting_df, + ctx.epoch(), + ) +} +/* +This is kept native to keep gas costing consistent. +*/ +native fun read_setting_impl< + FieldSettingValue: key, + SettingValue: store, + SettingDataValue: store, + Value: copy + drop + store, +>( + config: address, + name: address, + current_epoch: u64, +): Option; /* - This is kept native to keep gas costing consistent. - */ - native fun read_setting_impl< - FieldSettingValue: key, - SettingValue: store, - SettingDataValue: store, - Value: copy + drop + store, - >( - config: address, - name: address, - current_epoch: u64, - ): Option; - /* - // but the code is essentially - if (!field::exists_with_type(&config.id, setting)) return option::none() - let sobj: &Setting = field::borrow(&config.id, setting); - let data = sobj.data.borrow(); - if (current_epoch > data.newer_value_epoch) option::some(data.newer_value) - else data.older_value_opt - - } - */ +// but the code is essentially + if (!field::exists_with_type(&config.id, setting)) return option::none() + let sobj: &Setting = field::borrow(&config.id, setting); + let data = sobj.data.borrow(); + if (current_epoch > data.newer_value_epoch) option::some(data.newer_value) + else data.older_value_opt } +*/ diff --git a/crates/sui-framework/packages/sui-framework/sources/crypto/bls12381.move b/crates/sui-framework/packages/sui-framework/sources/crypto/bls12381.move index 0f3805ccf2c45..a87eb451e93be 100644 --- a/crates/sui-framework/packages/sui-framework/sources/crypto/bls12381.move +++ b/crates/sui-framework/packages/sui-framework/sources/crypto/bls12381.move @@ -2,247 +2,265 @@ // SPDX-License-Identifier: Apache-2.0 /// Group operations of BLS12-381. -module sui::bls12381 { - - use sui::group_ops; - use sui::group_ops::Element; - - /// @param signature: A 48-bytes signature that is a point on the G1 subgroup. - /// @param public_key: A 96-bytes public key that is a point on the G2 subgroup. - /// @param msg: The message that we test the signature against. - /// - /// If the signature is a valid signature of the message and public key according to - /// BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_, return true. Otherwise, return false. - public native fun bls12381_min_sig_verify(signature: &vector, public_key: &vector, msg: &vector): bool; - - /// @param signature: A 96-bytes signature that is a point on the G2 subgroup. - /// @param public_key: A 48-bytes public key that is a point on the G1 subgroup. - /// @param msg: The message that we test the signature against. - /// - /// If the signature is a valid signature of the message and public key according to - /// BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_, return true. Otherwise, return false. - public native fun bls12381_min_pk_verify(signature: &vector, public_key: &vector, msg: &vector): bool; - - - ///////////////////////////////////////////// - ////// Elliptic curve operations ////// - - public struct Scalar {} - public struct G1 {} - public struct G2 {} - public struct GT {} - - - // Scalars are encoded using big-endian byte order. - // G1 and G2 are encoded using big-endian byte order and points are compressed. See - // https://www.ietf.org/archive/id/draft-irtf-cfrg-pairing-friendly-curves-11.html and - // https://docs.rs/bls12_381/latest/bls12_381/notes/serialization/index.html for details. - // GT is encoded using big-endian byte order and points are uncompressed and not intended - // to be deserialized. - - // Const elements. - const SCALAR_ZERO_BYTES: vector = x"0000000000000000000000000000000000000000000000000000000000000000"; - const SCALAR_ONE_BYTES: vector = x"0000000000000000000000000000000000000000000000000000000000000001"; - const G1_IDENTITY_BYTES: vector = x"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; - const G1_GENERATOR_BYTES: vector = x"97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb"; - const G2_IDENTITY_BYTES: vector = x"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; - const G2_GENERATOR_BYTES: vector = x"93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8"; - const GT_IDENTITY_BYTES: vector = x"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; - const GT_GENERATOR_BYTES: vector = x"1250ebd871fc0a92a7b2d83168d0d727272d441befa15c503dd8e90ce98db3e7b6d194f60839c508a84305aaca1789b6089a1c5b46e5110b86750ec6a532348868a84045483c92b7af5af689452eafabf1a8943e50439f1d59882a98eaa0170f19f26337d205fb469cd6bd15c3d5a04dc88784fbb3d0b2dbdea54d43b2b73f2cbb12d58386a8703e0f948226e47ee89d06fba23eb7c5af0d9f80940ca771b6ffd5857baaf222eb95a7d2809d61bfe02e1bfd1b68ff02f0b8102ae1c2d5d5ab1a1368bb445c7c2d209703f239689ce34c0378a68e72a6b3b216da0e22a5031b54ddff57309396b38c881c4c849ec23e87193502b86edb8857c273fa075a50512937e0794e1e65a7617c90d8bd66065b1fffe51d7a579973b1315021ec3c19934f11b8b424cd48bf38fcef68083b0b0ec5c81a93b330ee1a677d0d15ff7b984e8978ef48881e32fac91b93b47333e2ba5703350f55a7aefcd3c31b4fcb6ce5771cc6a0e9786ab5973320c806ad360829107ba810c5a09ffdd9be2291a0c25a99a201b2f522473d171391125ba84dc4007cfbf2f8da752f7c74185203fcca589ac719c34dffbbaad8431dad1c1fb597aaa5018107154f25a764bd3c79937a45b84546da634b8f6be14a8061e55cceba478b23f7dacaa35c8ca78beae9624045b4b604c581234d086a9902249b64728ffd21a189e87935a954051c7cdba7b3872629a4fafc05066245cb9108f0242d0fe3ef0f41e58663bf08cf068672cbd01a7ec73baca4d72ca93544deff686bfd6df543d48eaa24afe47e1efde449383b676631"; - - // Internal types used by group_ops' native functions. - const SCALAR_TYPE: u8 = 0; - const G1_TYPE: u8 = 1; - const G2_TYPE: u8 = 2; - const GT_TYPE: u8 = 3; - - /////////////////////////////// - ////// Scalar operations ////// - - public fun scalar_from_bytes(bytes: &vector): Element { - group_ops::from_bytes(SCALAR_TYPE, bytes, false) - } - - public fun scalar_from_u64(x: u64): Element { - let mut bytes = SCALAR_ZERO_BYTES; - group_ops::set_as_prefix(x, true, &mut bytes); - group_ops::from_bytes(SCALAR_TYPE, &bytes, true) - } - - public fun scalar_zero(): Element { - let zero = SCALAR_ZERO_BYTES; - group_ops::from_bytes(SCALAR_TYPE, &zero, true) - } - - public fun scalar_one(): Element { - let one = SCALAR_ONE_BYTES; - group_ops::from_bytes(SCALAR_TYPE, &one, true) - } - - public fun scalar_add(e1: &Element, e2: &Element): Element { - group_ops::add(SCALAR_TYPE, e1, e2) - } - - public fun scalar_sub(e1: &Element, e2: &Element): Element { - group_ops::sub(SCALAR_TYPE, e1, e2) - } - - public fun scalar_mul(e1: &Element, e2: &Element): Element { - group_ops::mul(SCALAR_TYPE, e1, e2) - } - - /// Returns e2/e1, fails if a is zero. - public fun scalar_div(e1: &Element, e2: &Element): Element { - group_ops::div(SCALAR_TYPE, e1, e2) - } - - public fun scalar_neg(e: &Element): Element { - scalar_sub(&scalar_zero(), e) - } - - // Fails if e is zero. - public fun scalar_inv(e: &Element): Element { - scalar_div(e, &scalar_one()) - } - - ///////////////////////////////// - ////// G1 group operations ////// - - public fun g1_from_bytes(bytes: &vector): Element { - group_ops::from_bytes(G1_TYPE, bytes, false) - } - - public fun g1_identity(): Element { - let identity = G1_IDENTITY_BYTES; - group_ops::from_bytes(G1_TYPE, &identity, true) - } - - public fun g1_generator(): Element { - let generator = G1_GENERATOR_BYTES; - group_ops::from_bytes(G1_TYPE, &generator, true) - } - - public fun g1_add(e1: &Element, e2: &Element): Element { - group_ops::add(G1_TYPE, e1, e2) - } - - public fun g1_sub(e1: &Element, e2: &Element): Element { - group_ops::sub(G1_TYPE, e1, e2) - } - - public fun g1_mul(e1: &Element, e2: &Element): Element { - group_ops::mul(G1_TYPE, e1, e2) - } - - /// Returns e2 / e1, fails if scalar is zero. - public fun g1_div(e1: &Element, e2: &Element): Element { - group_ops::div(G1_TYPE, e1, e2) - } - - public fun g1_neg(e: &Element): Element { - g1_sub(&g1_identity(), e) - } - - /// Hash using DST = BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_ - public fun hash_to_g1(m: &vector): Element { - group_ops::hash_to(G1_TYPE, m) - } - - /// Let 'scalars' be the vector [s1, s2, ..., sn] and 'elements' be the vector [e1, e2, ..., en]. - /// Returns s1*e1 + s2*e2 + ... + sn*en. - /// Aborts with `EInputTooLong` if the vectors are larger than 32 (may increase in the future). - public fun g1_multi_scalar_multiplication(scalars: &vector>, elements: &vector>): Element { - group_ops::multi_scalar_multiplication(G1_TYPE, scalars, elements) - } - - ///////////////////////////////// - ////// G2 group operations ////// - - public fun g2_from_bytes(bytes: &vector): Element { - group_ops::from_bytes(G2_TYPE, bytes, false) - } - - public fun g2_identity(): Element { - let identity = G2_IDENTITY_BYTES; - group_ops::from_bytes(G2_TYPE, &identity, true) - } - - public fun g2_generator(): Element { - let generator = G2_GENERATOR_BYTES; - group_ops::from_bytes(G2_TYPE, &generator, true) - } - - public fun g2_add(e1: &Element, e2: &Element): Element { - group_ops::add(G2_TYPE, e1, e2) - } - - public fun g2_sub(e1: &Element, e2: &Element): Element { - group_ops::sub(G2_TYPE, e1, e2) - } - - public fun g2_mul(e1: &Element, e2: &Element): Element { - group_ops::mul(G2_TYPE, e1, e2) - } - - /// Returns e2 / e1, fails if scalar is zero. - public fun g2_div(e1: &Element, e2: &Element): Element { - group_ops::div(G2_TYPE, e1, e2) - } - - public fun g2_neg(e: &Element): Element { - g2_sub(&g2_identity(), e) - } - - /// Hash using DST = BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_ - public fun hash_to_g2(m: &vector): Element { - group_ops::hash_to(G2_TYPE, m) - } - - /// Let 'scalars' be the vector [s1, s2, ..., sn] and 'elements' be the vector [e1, e2, ..., en]. - /// Returns s1*e1 + s2*e2 + ... + sn*en. - /// Aborts with `EInputTooLong` if the vectors are larger than 32 (may increase in the future). - public fun g2_multi_scalar_multiplication(scalars: &vector>, elements: &vector>): Element { - group_ops::multi_scalar_multiplication(G2_TYPE, scalars, elements) - } - - ///////////////////////////////// - ////// Gt group operations ////// - - public fun gt_identity(): Element { - let identity = GT_IDENTITY_BYTES; - group_ops::from_bytes(GT_TYPE, &identity, true) - } - - public fun gt_generator(): Element { - let generator = GT_GENERATOR_BYTES; - group_ops::from_bytes(GT_TYPE, &generator, true) - } - - public fun gt_add(e1: &Element, e2: &Element): Element { - group_ops::add(GT_TYPE, e1, e2) - } - - public fun gt_sub(e1: &Element, e2: &Element): Element { - group_ops::sub(GT_TYPE, e1, e2) - } - - public fun gt_mul(e1: &Element, e2: &Element): Element { - group_ops::mul(GT_TYPE, e1, e2) - } - - /// Returns e2 / e1, fails if scalar is zero. - public fun gt_div(e1: &Element, e2: &Element): Element { - group_ops::div(GT_TYPE, e1, e2) - } - - public fun gt_neg(e: &Element): Element { - gt_sub(>_identity(), e) - } - - ///////////////////// - ////// Pairing ////// - - public fun pairing(e1: &Element, e2: &Element): Element { - group_ops::pairing(G1_TYPE, e1, e2) - } +module sui::bls12381; + +use sui::group_ops::{Self, Element}; + +/// @param signature: A 48-bytes signature that is a point on the G1 subgroup. +/// @param public_key: A 96-bytes public key that is a point on the G2 subgroup. +/// @param msg: The message that we test the signature against. +/// +/// If the signature is a valid signature of the message and public key according to +/// BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_, return true. Otherwise, return false. +public native fun bls12381_min_sig_verify( + signature: &vector, + public_key: &vector, + msg: &vector, +): bool; + +/// @param signature: A 96-bytes signature that is a point on the G2 subgroup. +/// @param public_key: A 48-bytes public key that is a point on the G1 subgroup. +/// @param msg: The message that we test the signature against. +/// +/// If the signature is a valid signature of the message and public key according to +/// BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_, return true. Otherwise, return false. +public native fun bls12381_min_pk_verify( + signature: &vector, + public_key: &vector, + msg: &vector, +): bool; + +///////////////////////////////////////////// +////// Elliptic curve operations ////// + +public struct Scalar {} +public struct G1 {} +public struct G2 {} +public struct GT {} + +// Scalars are encoded using big-endian byte order. +// G1 and G2 are encoded using big-endian byte order and points are compressed. See +// https://www.ietf.org/archive/id/draft-irtf-cfrg-pairing-friendly-curves-11.html and +// https://docs.rs/bls12_381/latest/bls12_381/notes/serialization/index.html for details. +// GT is encoded using big-endian byte order and points are uncompressed and not intended +// to be deserialized. + +// Const elements. +const SCALAR_ZERO_BYTES: vector = + x"0000000000000000000000000000000000000000000000000000000000000000"; +const SCALAR_ONE_BYTES: vector = + x"0000000000000000000000000000000000000000000000000000000000000001"; +const G1_IDENTITY_BYTES: vector = + x"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; +const G1_GENERATOR_BYTES: vector = + x"97f1d3a73197d7942695638c4fa9ac0fc3688c4f9774b905a14e3a3f171bac586c55e83ff97a1aeffb3af00adb22c6bb"; +const G2_IDENTITY_BYTES: vector = + x"c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; +const G2_GENERATOR_BYTES: vector = + x"93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8"; +const GT_IDENTITY_BYTES: vector = + x"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; +const GT_GENERATOR_BYTES: vector = + x"1250ebd871fc0a92a7b2d83168d0d727272d441befa15c503dd8e90ce98db3e7b6d194f60839c508a84305aaca1789b6089a1c5b46e5110b86750ec6a532348868a84045483c92b7af5af689452eafabf1a8943e50439f1d59882a98eaa0170f19f26337d205fb469cd6bd15c3d5a04dc88784fbb3d0b2dbdea54d43b2b73f2cbb12d58386a8703e0f948226e47ee89d06fba23eb7c5af0d9f80940ca771b6ffd5857baaf222eb95a7d2809d61bfe02e1bfd1b68ff02f0b8102ae1c2d5d5ab1a1368bb445c7c2d209703f239689ce34c0378a68e72a6b3b216da0e22a5031b54ddff57309396b38c881c4c849ec23e87193502b86edb8857c273fa075a50512937e0794e1e65a7617c90d8bd66065b1fffe51d7a579973b1315021ec3c19934f11b8b424cd48bf38fcef68083b0b0ec5c81a93b330ee1a677d0d15ff7b984e8978ef48881e32fac91b93b47333e2ba5703350f55a7aefcd3c31b4fcb6ce5771cc6a0e9786ab5973320c806ad360829107ba810c5a09ffdd9be2291a0c25a99a201b2f522473d171391125ba84dc4007cfbf2f8da752f7c74185203fcca589ac719c34dffbbaad8431dad1c1fb597aaa5018107154f25a764bd3c79937a45b84546da634b8f6be14a8061e55cceba478b23f7dacaa35c8ca78beae9624045b4b604c581234d086a9902249b64728ffd21a189e87935a954051c7cdba7b3872629a4fafc05066245cb9108f0242d0fe3ef0f41e58663bf08cf068672cbd01a7ec73baca4d72ca93544deff686bfd6df543d48eaa24afe47e1efde449383b676631"; + +// Internal types used by group_ops' native functions. +const SCALAR_TYPE: u8 = 0; +const G1_TYPE: u8 = 1; +const G2_TYPE: u8 = 2; +const GT_TYPE: u8 = 3; + +/////////////////////////////// +////// Scalar operations ////// + +public fun scalar_from_bytes(bytes: &vector): Element { + group_ops::from_bytes(SCALAR_TYPE, bytes, false) +} + +public fun scalar_from_u64(x: u64): Element { + let mut bytes = SCALAR_ZERO_BYTES; + group_ops::set_as_prefix(x, true, &mut bytes); + group_ops::from_bytes(SCALAR_TYPE, &bytes, true) +} + +public fun scalar_zero(): Element { + let zero = SCALAR_ZERO_BYTES; + group_ops::from_bytes(SCALAR_TYPE, &zero, true) +} + +public fun scalar_one(): Element { + let one = SCALAR_ONE_BYTES; + group_ops::from_bytes(SCALAR_TYPE, &one, true) +} + +public fun scalar_add(e1: &Element, e2: &Element): Element { + group_ops::add(SCALAR_TYPE, e1, e2) +} + +public fun scalar_sub(e1: &Element, e2: &Element): Element { + group_ops::sub(SCALAR_TYPE, e1, e2) +} + +public fun scalar_mul(e1: &Element, e2: &Element): Element { + group_ops::mul(SCALAR_TYPE, e1, e2) +} + +/// Returns e2/e1, fails if a is zero. +public fun scalar_div(e1: &Element, e2: &Element): Element { + group_ops::div(SCALAR_TYPE, e1, e2) +} + +public fun scalar_neg(e: &Element): Element { + scalar_sub(&scalar_zero(), e) +} + +// Fails if e is zero. +public fun scalar_inv(e: &Element): Element { + scalar_div(e, &scalar_one()) +} + +///////////////////////////////// +////// G1 group operations ////// + +public fun g1_from_bytes(bytes: &vector): Element { + group_ops::from_bytes(G1_TYPE, bytes, false) +} + +public fun g1_identity(): Element { + let identity = G1_IDENTITY_BYTES; + group_ops::from_bytes(G1_TYPE, &identity, true) +} + +public fun g1_generator(): Element { + let generator = G1_GENERATOR_BYTES; + group_ops::from_bytes(G1_TYPE, &generator, true) +} + +public fun g1_add(e1: &Element, e2: &Element): Element { + group_ops::add(G1_TYPE, e1, e2) +} + +public fun g1_sub(e1: &Element, e2: &Element): Element { + group_ops::sub(G1_TYPE, e1, e2) +} + +public fun g1_mul(e1: &Element, e2: &Element): Element { + group_ops::mul(G1_TYPE, e1, e2) +} + +/// Returns e2 / e1, fails if scalar is zero. +public fun g1_div(e1: &Element, e2: &Element): Element { + group_ops::div(G1_TYPE, e1, e2) +} + +public fun g1_neg(e: &Element): Element { + g1_sub(&g1_identity(), e) +} + +/// Hash using DST = BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_ +public fun hash_to_g1(m: &vector): Element { + group_ops::hash_to(G1_TYPE, m) +} + +/// Let 'scalars' be the vector [s1, s2, ..., sn] and 'elements' be the vector [e1, e2, ..., en]. +/// Returns s1*e1 + s2*e2 + ... + sn*en. +/// Aborts with `EInputTooLong` if the vectors are larger than 32 (may increase in the future). +public fun g1_multi_scalar_multiplication( + scalars: &vector>, + elements: &vector>, +): Element { + group_ops::multi_scalar_multiplication(G1_TYPE, scalars, elements) +} + +///////////////////////////////// +////// G2 group operations ////// + +public fun g2_from_bytes(bytes: &vector): Element { + group_ops::from_bytes(G2_TYPE, bytes, false) +} + +public fun g2_identity(): Element { + let identity = G2_IDENTITY_BYTES; + group_ops::from_bytes(G2_TYPE, &identity, true) +} + +public fun g2_generator(): Element { + let generator = G2_GENERATOR_BYTES; + group_ops::from_bytes(G2_TYPE, &generator, true) +} + +public fun g2_add(e1: &Element, e2: &Element): Element { + group_ops::add(G2_TYPE, e1, e2) +} + +public fun g2_sub(e1: &Element, e2: &Element): Element { + group_ops::sub(G2_TYPE, e1, e2) +} + +public fun g2_mul(e1: &Element, e2: &Element): Element { + group_ops::mul(G2_TYPE, e1, e2) +} + +/// Returns e2 / e1, fails if scalar is zero. +public fun g2_div(e1: &Element, e2: &Element): Element { + group_ops::div(G2_TYPE, e1, e2) +} + +public fun g2_neg(e: &Element): Element { + g2_sub(&g2_identity(), e) +} + +/// Hash using DST = BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_ +public fun hash_to_g2(m: &vector): Element { + group_ops::hash_to(G2_TYPE, m) +} + +/// Let 'scalars' be the vector [s1, s2, ..., sn] and 'elements' be the vector [e1, e2, ..., en]. +/// Returns s1*e1 + s2*e2 + ... + sn*en. +/// Aborts with `EInputTooLong` if the vectors are larger than 32 (may increase in the future). +public fun g2_multi_scalar_multiplication( + scalars: &vector>, + elements: &vector>, +): Element { + group_ops::multi_scalar_multiplication(G2_TYPE, scalars, elements) +} + +///////////////////////////////// +////// Gt group operations ////// + +public fun gt_identity(): Element { + let identity = GT_IDENTITY_BYTES; + group_ops::from_bytes(GT_TYPE, &identity, true) +} + +public fun gt_generator(): Element { + let generator = GT_GENERATOR_BYTES; + group_ops::from_bytes(GT_TYPE, &generator, true) +} + +public fun gt_add(e1: &Element, e2: &Element): Element { + group_ops::add(GT_TYPE, e1, e2) +} + +public fun gt_sub(e1: &Element, e2: &Element): Element { + group_ops::sub(GT_TYPE, e1, e2) +} + +public fun gt_mul(e1: &Element, e2: &Element): Element { + group_ops::mul(GT_TYPE, e1, e2) +} + +/// Returns e2 / e1, fails if scalar is zero. +public fun gt_div(e1: &Element, e2: &Element): Element { + group_ops::div(GT_TYPE, e1, e2) +} + +public fun gt_neg(e: &Element): Element { + gt_sub(>_identity(), e) +} + +///////////////////// +////// Pairing ////// + +public fun pairing(e1: &Element, e2: &Element): Element { + group_ops::pairing(G1_TYPE, e1, e2) } diff --git a/crates/sui-framework/packages/sui-framework/sources/crypto/ecdsa_k1.move b/crates/sui-framework/packages/sui-framework/sources/crypto/ecdsa_k1.move index 39b6a1489ce3f..47c5828219997 100644 --- a/crates/sui-framework/packages/sui-framework/sources/crypto/ecdsa_k1.move +++ b/crates/sui-framework/packages/sui-framework/sources/crypto/ecdsa_k1.move @@ -1,101 +1,114 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -module sui::ecdsa_k1 { - - #[allow(unused_const)] - /// Error if the public key cannot be recovered from the signature. - const EFailToRecoverPubKey: u64 = 0; - - #[allow(unused_const)] - /// Error if the signature is invalid. - const EInvalidSignature: u64 = 1; - - #[allow(unused_const)] - /// Error if the public key is invalid. - const EInvalidPubKey: u64 = 2; - - #[allow(unused_const)] - #[test_only] - /// Error if the private key is invalid. - const EInvalidPrivKey: u64 = 3; - - #[allow(unused_const)] - #[test_only] - /// Error if the given hash function does not exist. - const EInvalidHashFunction: u64 = 4; - - #[allow(unused_const)] - #[test_only] - /// Error if the seed is invalid. - const EInvalidSeed: u64 = 5; - - #[allow(unused_const)] - /// Hash function name that are valid for ecrecover and secp256k1_verify. - const KECCAK256: u8 = 0; - #[allow(unused_const)] - const SHA256: u8 = 1; - - /// @param signature: A 65-bytes signature in form (r, s, v) that is signed using - /// Secp256k1. Reference implementation on signature generation using RFC6979: - /// https://github.com/MystenLabs/narwhal/blob/5d6f6df8ccee94446ff88786c0dbbc98be7cfc09/crypto/src/secp256k1.rs - /// The accepted v values are {0, 1, 2, 3}. - /// @param msg: The message that the signature is signed against, this is raw message without hashing. - /// @param hash: The hash function used to hash the message when signing. - /// - /// If the signature is valid, return the corresponding recovered Secpk256k1 public - /// key, otherwise throw error. This is similar to ecrecover in Ethereum, can only be - /// applied to Secp256k1 signatures. May abort with `EFailToRecoverPubKey` or `EInvalidSignature`. - public native fun secp256k1_ecrecover(signature: &vector, msg: &vector, hash: u8): vector; - - /// @param pubkey: A 33-bytes compressed public key, a prefix either 0x02 or 0x03 and a 256-bit integer. - /// - /// If the compressed public key is valid, return the 65-bytes uncompressed public key, - /// otherwise throw error. May abort with `EInvalidPubKey`. - public native fun decompress_pubkey(pubkey: &vector): vector; - - /// @param signature: A 64-bytes signature in form (r, s) that is signed using - /// Secp256k1. This is an non-recoverable signature without recovery id. - /// Reference implementation on signature generation using RFC6979: - /// https://github.com/MystenLabs/fastcrypto/blob/74aec4886e62122a5b769464c2bea5f803cf8ecc/fastcrypto/src/secp256k1/mod.rs#L193 - /// @param public_key: The public key to verify the signature against - /// @param msg: The message that the signature is signed against, this is raw message without hashing. - /// @param hash: The hash function used to hash the message when signing. - /// - /// If the signature is valid to the pubkey and hashed message, return true. Else false. - public native fun secp256k1_verify(signature: &vector, public_key: &vector, msg: &vector, hash: u8): bool; - - #[test_only] - /// @param private_key: A 32-bytes private key that is used to sign the message. - /// @param msg: The message to sign, this is raw message without hashing. - /// @param hash: The hash function used to hash the message when signing. - /// @param recoverable: A boolean flag to indicate if the produced signature should be recoverable. - /// - /// Return the signature in form (r, s) that is signed using Secp256k1. - /// If `recoverable` is true, the signature will be in form (r, s, v) where v is the recovery id. - /// - /// This should ONLY be used in tests, because it will reveal the private key onchain. - public native fun secp256k1_sign(private_key: &vector, msg: &vector, hash: u8, recoverable: bool): vector; - - #[test_only] - public struct KeyPair has drop { - private_key: vector, - public_key: vector, - } - - #[test_only] - public fun private_key(self: &KeyPair): &vector { - &self.private_key - } - - #[test_only] - public fun public_key(self: &KeyPair): &vector { - &self.public_key - } - - #[test_only] - /// @param seed: A 32-bytes seed that is used to generate the keypair. - /// - /// Returns a Secp256k1 keypair deterministically generated from the seed. - public native fun secp256k1_keypair_from_seed(seed: &vector): KeyPair; +module sui::ecdsa_k1; + +#[allow(unused_const)] +/// Error if the public key cannot be recovered from the signature. +const EFailToRecoverPubKey: u64 = 0; + +#[allow(unused_const)] +/// Error if the signature is invalid. +const EInvalidSignature: u64 = 1; + +#[allow(unused_const)] +/// Error if the public key is invalid. +const EInvalidPubKey: u64 = 2; + +#[allow(unused_const)] +#[test_only] +/// Error if the private key is invalid. +const EInvalidPrivKey: u64 = 3; + +#[allow(unused_const)] +#[test_only] +/// Error if the given hash function does not exist. +const EInvalidHashFunction: u64 = 4; + +#[allow(unused_const)] +#[test_only] +/// Error if the seed is invalid. +const EInvalidSeed: u64 = 5; + +#[allow(unused_const)] +/// Hash function name that are valid for ecrecover and secp256k1_verify. +const KECCAK256: u8 = 0; +#[allow(unused_const)] +const SHA256: u8 = 1; + +/// @param signature: A 65-bytes signature in form (r, s, v) that is signed using +/// Secp256k1. Reference implementation on signature generation using RFC6979: +/// https://github.com/MystenLabs/narwhal/blob/5d6f6df8ccee94446ff88786c0dbbc98be7cfc09/crypto/src/secp256k1.rs +/// The accepted v values are {0, 1, 2, 3}. +/// @param msg: The message that the signature is signed against, this is raw message without hashing. +/// @param hash: The hash function used to hash the message when signing. +/// +/// If the signature is valid, return the corresponding recovered Secpk256k1 public +/// key, otherwise throw error. This is similar to ecrecover in Ethereum, can only be +/// applied to Secp256k1 signatures. May abort with `EFailToRecoverPubKey` or `EInvalidSignature`. +public native fun secp256k1_ecrecover( + signature: &vector, + msg: &vector, + hash: u8, +): vector; + +/// @param pubkey: A 33-bytes compressed public key, a prefix either 0x02 or 0x03 and a 256-bit integer. +/// +/// If the compressed public key is valid, return the 65-bytes uncompressed public key, +/// otherwise throw error. May abort with `EInvalidPubKey`. +public native fun decompress_pubkey(pubkey: &vector): vector; + +/// @param signature: A 64-bytes signature in form (r, s) that is signed using +/// Secp256k1. This is an non-recoverable signature without recovery id. +/// Reference implementation on signature generation using RFC6979: +/// https://github.com/MystenLabs/fastcrypto/blob/74aec4886e62122a5b769464c2bea5f803cf8ecc/fastcrypto/src/secp256k1/mod.rs#L193 +/// @param public_key: The public key to verify the signature against +/// @param msg: The message that the signature is signed against, this is raw message without hashing. +/// @param hash: The hash function used to hash the message when signing. +/// +/// If the signature is valid to the pubkey and hashed message, return true. Else false. +public native fun secp256k1_verify( + signature: &vector, + public_key: &vector, + msg: &vector, + hash: u8, +): bool; + +#[test_only] +/// @param private_key: A 32-bytes private key that is used to sign the message. +/// @param msg: The message to sign, this is raw message without hashing. +/// @param hash: The hash function used to hash the message when signing. +/// @param recoverable: A boolean flag to indicate if the produced signature should be recoverable. +/// +/// Return the signature in form (r, s) that is signed using Secp256k1. +/// If `recoverable` is true, the signature will be in form (r, s, v) where v is the recovery id. +/// +/// This should ONLY be used in tests, because it will reveal the private key onchain. +public native fun secp256k1_sign( + private_key: &vector, + msg: &vector, + hash: u8, + recoverable: bool, +): vector; + +#[test_only] +public struct KeyPair has drop { + private_key: vector, + public_key: vector, } + +#[test_only] +public fun private_key(self: &KeyPair): &vector { + &self.private_key +} + +#[test_only] +public fun public_key(self: &KeyPair): &vector { + &self.public_key +} + +#[test_only] +/// @param seed: A 32-bytes seed that is used to generate the keypair. +/// +/// Returns a Secp256k1 keypair deterministically generated from the seed. +public native fun secp256k1_keypair_from_seed(seed: &vector): KeyPair; diff --git a/crates/sui-framework/packages/sui-framework/sources/crypto/ecdsa_r1.move b/crates/sui-framework/packages/sui-framework/sources/crypto/ecdsa_r1.move index 52214b8fc7d10..e388f8d7fd14d 100644 --- a/crates/sui-framework/packages/sui-framework/sources/crypto/ecdsa_r1.move +++ b/crates/sui-framework/packages/sui-framework/sources/crypto/ecdsa_r1.move @@ -1,42 +1,50 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -module sui::ecdsa_r1 { +module sui::ecdsa_r1; - #[allow(unused_const)] - /// Error if the public key cannot be recovered from the signature. - const EFailToRecoverPubKey: u64 = 0; +#[allow(unused_const)] +/// Error if the public key cannot be recovered from the signature. +const EFailToRecoverPubKey: u64 = 0; - #[allow(unused_const)] - /// Error if the signature is invalid. - const EInvalidSignature: u64 = 1; +#[allow(unused_const)] +/// Error if the signature is invalid. +const EInvalidSignature: u64 = 1; - #[allow(unused_const)] - /// Hash function name that are valid for ecrecover and secp256k1_verify. - const KECCAK256: u8 = 0; - #[allow(unused_const)] - const SHA256: u8 = 1; +#[allow(unused_const)] +/// Hash function name that are valid for ecrecover and secp256k1_verify. +const KECCAK256: u8 = 0; +#[allow(unused_const)] +const SHA256: u8 = 1; - /// @param signature: A 65-bytes signature in form (r, s, v) that is signed using - /// Secp256r1. Reference implementation on signature generation using RFC6979: - /// https://github.com/MystenLabs/fastcrypto/blob/74aec4886e62122a5b769464c2bea5f803cf8ecc/fastcrypto/src/secp256r1/mod.rs - /// The accepted v values are {0, 1, 2, 3}. - /// @param msg: The message that the signature is signed against, this is raw message without hashing. - /// @param hash: The u8 representing the name of hash function used to hash the message when signing. - /// - /// If the signature is valid, return the corresponding recovered Secpk256r1 public - /// key, otherwise throw error. This is similar to ecrecover in Ethereum, can only be - /// applied to Secp256r1 signatures. May fail with `EFailToRecoverPubKey` or `EInvalidSignature`. - public native fun secp256r1_ecrecover(signature: &vector, msg: &vector, hash: u8): vector; +/// @param signature: A 65-bytes signature in form (r, s, v) that is signed using +/// Secp256r1. Reference implementation on signature generation using RFC6979: +/// https://github.com/MystenLabs/fastcrypto/blob/74aec4886e62122a5b769464c2bea5f803cf8ecc/fastcrypto/src/secp256r1/mod.rs +/// The accepted v values are {0, 1, 2, 3}. +/// @param msg: The message that the signature is signed against, this is raw message without hashing. +/// @param hash: The u8 representing the name of hash function used to hash the message when signing. +/// +/// If the signature is valid, return the corresponding recovered Secpk256r1 public +/// key, otherwise throw error. This is similar to ecrecover in Ethereum, can only be +/// applied to Secp256r1 signatures. May fail with `EFailToRecoverPubKey` or `EInvalidSignature`. +public native fun secp256r1_ecrecover( + signature: &vector, + msg: &vector, + hash: u8, +): vector; - /// @param signature: A 64-bytes signature in form (r, s) that is signed using - /// Secp256r1. This is an non-recoverable signature without recovery id. - /// Reference implementation on signature generation using RFC6979: - /// https://github.com/MystenLabs/fastcrypto/blob/74aec4886e62122a5b769464c2bea5f803cf8ecc/fastcrypto/src/secp256r1/mod.rs - /// @param public_key: The public key to verify the signature against - /// @param msg: The message that the signature is signed against, this is raw message without hashing. - /// @param hash: The u8 representing the name of hash function used to hash the message when signing. - /// - /// If the signature is valid to the pubkey and hashed message, return true. Else false. - public native fun secp256r1_verify(signature: &vector, public_key: &vector, msg: &vector, hash: u8): bool; -} +/// @param signature: A 64-bytes signature in form (r, s) that is signed using +/// Secp256r1. This is an non-recoverable signature without recovery id. +/// Reference implementation on signature generation using RFC6979: +/// https://github.com/MystenLabs/fastcrypto/blob/74aec4886e62122a5b769464c2bea5f803cf8ecc/fastcrypto/src/secp256r1/mod.rs +/// @param public_key: The public key to verify the signature against +/// @param msg: The message that the signature is signed against, this is raw message without hashing. +/// @param hash: The u8 representing the name of hash function used to hash the message when signing. +/// +/// If the signature is valid to the pubkey and hashed message, return true. Else false. +public native fun secp256r1_verify( + signature: &vector, + public_key: &vector, + msg: &vector, + hash: u8, +): bool; diff --git a/crates/sui-framework/packages/sui-framework/sources/crypto/ecvrf.move b/crates/sui-framework/packages/sui-framework/sources/crypto/ecvrf.move index 3864ffa62da71..b2e00cd488b76 100644 --- a/crates/sui-framework/packages/sui-framework/sources/crypto/ecvrf.move +++ b/crates/sui-framework/packages/sui-framework/sources/crypto/ecvrf.move @@ -1,19 +1,23 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -module sui::ecvrf { - #[allow(unused_const)] - const EInvalidHashLength: u64 = 1; - #[allow(unused_const)] - const EInvalidPublicKeyEncoding: u64 = 2; - #[allow(unused_const)] - const EInvalidProofEncoding: u64 = 3; +module sui::ecvrf; - /// @param hash: The hash/output from a ECVRF to be verified. - /// @param alpha_string: Input/seed to the ECVRF used to generate the output. - /// @param public_key: The public key corresponding to the private key used to generate the output. - /// @param proof: The proof of validity of the output. - /// Verify a proof for a Ristretto ECVRF. Returns true if the proof is valid and corresponds to the given output. May abort with `EInvalidHashLength`, `EInvalidPublicKeyEncoding` or `EInvalidProofEncoding`. - public native fun ecvrf_verify(hash: &vector, alpha_string: &vector, public_key: &vector, proof: &vector): bool; +#[allow(unused_const)] +const EInvalidHashLength: u64 = 1; +#[allow(unused_const)] +const EInvalidPublicKeyEncoding: u64 = 2; +#[allow(unused_const)] +const EInvalidProofEncoding: u64 = 3; -} +/// @param hash: The hash/output from a ECVRF to be verified. +/// @param alpha_string: Input/seed to the ECVRF used to generate the output. +/// @param public_key: The public key corresponding to the private key used to generate the output. +/// @param proof: The proof of validity of the output. +/// Verify a proof for a Ristretto ECVRF. Returns true if the proof is valid and corresponds to the given output. May abort with `EInvalidHashLength`, `EInvalidPublicKeyEncoding` or `EInvalidProofEncoding`. +public native fun ecvrf_verify( + hash: &vector, + alpha_string: &vector, + public_key: &vector, + proof: &vector, +): bool; diff --git a/crates/sui-framework/packages/sui-framework/sources/crypto/ed25519.move b/crates/sui-framework/packages/sui-framework/sources/crypto/ed25519.move index 9afcaea2b44be..b848e94339115 100644 --- a/crates/sui-framework/packages/sui-framework/sources/crypto/ed25519.move +++ b/crates/sui-framework/packages/sui-framework/sources/crypto/ed25519.move @@ -1,12 +1,16 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -module sui::ed25519 { - /// @param signature: 32-byte signature that is a point on the Ed25519 elliptic curve. - /// @param public_key: 32-byte signature that is a point on the Ed25519 elliptic curve. - /// @param msg: The message that we test the signature against. - /// - /// If the signature is a valid Ed25519 signature of the message and public key, return true. - /// Otherwise, return false. - public native fun ed25519_verify(signature: &vector, public_key: &vector, msg: &vector): bool; -} +module sui::ed25519; + +/// @param signature: 32-byte signature that is a point on the Ed25519 elliptic curve. +/// @param public_key: 32-byte signature that is a point on the Ed25519 elliptic curve. +/// @param msg: The message that we test the signature against. +/// +/// If the signature is a valid Ed25519 signature of the message and public key, return true. +/// Otherwise, return false. +public native fun ed25519_verify( + signature: &vector, + public_key: &vector, + msg: &vector, +): bool; diff --git a/crates/sui-framework/packages/sui-framework/sources/crypto/groth16.move b/crates/sui-framework/packages/sui-framework/sources/crypto/groth16.move index ede657b589d58..9720ac9c475c4 100644 --- a/crates/sui-framework/packages/sui-framework/sources/crypto/groth16.move +++ b/crates/sui-framework/packages/sui-framework/sources/crypto/groth16.move @@ -1,119 +1,139 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -module sui::groth16 { +module sui::groth16; - #[allow(unused_const)] - // Error for input is not a valid Arkwork representation of a verifying key. - const EInvalidVerifyingKey: u64 = 0; +#[allow(unused_const)] +// Error for input is not a valid Arkwork representation of a verifying key. +const EInvalidVerifyingKey: u64 = 0; - #[allow(unused_const)] - // Error if the given curve is not supported - const EInvalidCurve: u64 = 1; +#[allow(unused_const)] +// Error if the given curve is not supported +const EInvalidCurve: u64 = 1; - // Error if the number of public inputs given exceeds the max. - const ETooManyPublicInputs: u64 = 2; +// Error if the number of public inputs given exceeds the max. +const ETooManyPublicInputs: u64 = 2; - // Error a public input does not have the correct length. - const EInvalidScalar: u64 = 3; +// Error a public input does not have the correct length. +const EInvalidScalar: u64 = 3; - // We need to set an upper bound on the number of public inputs to avoid a DoS attack. - const MaxPublicInputs: u64 = 8; // This must match the corresponding constant in the native verify function. +// We need to set an upper bound on the number of public inputs to avoid a DoS attack. +const MaxPublicInputs: u64 = 8; // This must match the corresponding constant in the native verify function. - /// Represents an elliptic curve construction to be used in the verifier. Currently we support BLS12-381 and BN254. - /// This should be given as the first parameter to `prepare_verifying_key` or `verify_groth16_proof`. - public struct Curve has store, copy, drop { - id: u8, - } +/// Represents an elliptic curve construction to be used in the verifier. Currently we support BLS12-381 and BN254. +/// This should be given as the first parameter to `prepare_verifying_key` or `verify_groth16_proof`. +public struct Curve has store, copy, drop { + id: u8, +} - /// Return the `Curve` value indicating that the BLS12-381 construction should be used in a given function. - public fun bls12381(): Curve { Curve { id: 0 } } +/// Return the `Curve` value indicating that the BLS12-381 construction should be used in a given function. +public fun bls12381(): Curve { Curve { id: 0 } } - /// Return the `Curve` value indicating that the BN254 construction should be used in a given function. - public fun bn254(): Curve { Curve { id: 1 } } +/// Return the `Curve` value indicating that the BN254 construction should be used in a given function. +public fun bn254(): Curve { Curve { id: 1 } } - /// A `PreparedVerifyingKey` consisting of four components in serialized form. - public struct PreparedVerifyingKey has store, copy, drop { - vk_gamma_abc_g1_bytes: vector, - alpha_g1_beta_g2_bytes: vector, - gamma_g2_neg_pc_bytes: vector, - delta_g2_neg_pc_bytes: vector - } - - /// Creates a `PreparedVerifyingKey` from bytes. - public fun pvk_from_bytes(vk_gamma_abc_g1_bytes: vector, alpha_g1_beta_g2_bytes: vector, gamma_g2_neg_pc_bytes: vector, delta_g2_neg_pc_bytes: vector): PreparedVerifyingKey { - PreparedVerifyingKey { - vk_gamma_abc_g1_bytes, - alpha_g1_beta_g2_bytes, - gamma_g2_neg_pc_bytes, - delta_g2_neg_pc_bytes - } - } +/// A `PreparedVerifyingKey` consisting of four components in serialized form. +public struct PreparedVerifyingKey has store, copy, drop { + vk_gamma_abc_g1_bytes: vector, + alpha_g1_beta_g2_bytes: vector, + gamma_g2_neg_pc_bytes: vector, + delta_g2_neg_pc_bytes: vector, +} - /// Returns bytes of the four components of the `PreparedVerifyingKey`. - public fun pvk_to_bytes(pvk: PreparedVerifyingKey): vector> { - vector[ - pvk.vk_gamma_abc_g1_bytes, - pvk.alpha_g1_beta_g2_bytes, - pvk.gamma_g2_neg_pc_bytes, - pvk.delta_g2_neg_pc_bytes, - ] +/// Creates a `PreparedVerifyingKey` from bytes. +public fun pvk_from_bytes( + vk_gamma_abc_g1_bytes: vector, + alpha_g1_beta_g2_bytes: vector, + gamma_g2_neg_pc_bytes: vector, + delta_g2_neg_pc_bytes: vector, +): PreparedVerifyingKey { + PreparedVerifyingKey { + vk_gamma_abc_g1_bytes, + alpha_g1_beta_g2_bytes, + gamma_g2_neg_pc_bytes, + delta_g2_neg_pc_bytes, } +} - /// A `PublicProofInputs` wrapper around its serialized bytes. - public struct PublicProofInputs has store, copy, drop { - bytes: vector, - } +/// Returns bytes of the four components of the `PreparedVerifyingKey`. +public fun pvk_to_bytes(pvk: PreparedVerifyingKey): vector> { + vector[ + pvk.vk_gamma_abc_g1_bytes, + pvk.alpha_g1_beta_g2_bytes, + pvk.gamma_g2_neg_pc_bytes, + pvk.delta_g2_neg_pc_bytes, + ] +} - /// Creates a `PublicProofInputs` wrapper from bytes. The `bytes` parameter should be a concatenation of a number of - /// 32 bytes scalar field elements to be used as public inputs in little-endian format to a circuit. - public fun public_proof_inputs_from_bytes(bytes: vector): PublicProofInputs { - assert!(bytes.length() % 32 == 0, EInvalidScalar); - assert!(bytes.length() / 32 <= MaxPublicInputs, ETooManyPublicInputs); - PublicProofInputs { bytes } - } +/// A `PublicProofInputs` wrapper around its serialized bytes. +public struct PublicProofInputs has store, copy, drop { + bytes: vector, +} - /// A `ProofPoints` wrapper around the serialized form of three proof points. - public struct ProofPoints has store, copy, drop { - bytes: vector - } +/// Creates a `PublicProofInputs` wrapper from bytes. The `bytes` parameter should be a concatenation of a number of +/// 32 bytes scalar field elements to be used as public inputs in little-endian format to a circuit. +public fun public_proof_inputs_from_bytes(bytes: vector): PublicProofInputs { + assert!(bytes.length() % 32 == 0, EInvalidScalar); + assert!(bytes.length() / 32 <= MaxPublicInputs, ETooManyPublicInputs); + PublicProofInputs { bytes } +} - /// Creates a Groth16 `ProofPoints` from bytes. - public fun proof_points_from_bytes(bytes: vector): ProofPoints { - ProofPoints { bytes } - } +/// A `ProofPoints` wrapper around the serialized form of three proof points. +public struct ProofPoints has store, copy, drop { + bytes: vector, +} - /// @param curve: What elliptic curve construction to use. See `bls12381` and `bn254`. - /// @param verifying_key: An Arkworks canonical compressed serialization of a verifying key. - /// - /// Returns four vectors of bytes representing the four components of a prepared verifying key. - /// This step computes one pairing e(P, Q), and binds the verification to one particular proof statement. - /// This can be used as inputs for the `verify_groth16_proof` function. - public fun prepare_verifying_key(curve: &Curve, verifying_key: &vector): PreparedVerifyingKey { - prepare_verifying_key_internal(curve.id, verifying_key) - } +/// Creates a Groth16 `ProofPoints` from bytes. +public fun proof_points_from_bytes(bytes: vector): ProofPoints { + ProofPoints { bytes } +} - /// Native functions that flattens the inputs into an array and passes to the Rust native function. May abort with `EInvalidVerifyingKey` or `EInvalidCurve`. - native fun prepare_verifying_key_internal(curve: u8, verifying_key: &vector): PreparedVerifyingKey; - - /// @param curve: What elliptic curve construction to use. See the `bls12381` and `bn254` functions. - /// @param prepared_verifying_key: Consists of four vectors of bytes representing the four components of a prepared verifying key. - /// @param public_proof_inputs: Represent inputs that are public. - /// @param proof_points: Represent three proof points. - /// - /// Returns a boolean indicating whether the proof is valid. - public fun verify_groth16_proof(curve: &Curve, prepared_verifying_key: &PreparedVerifyingKey, public_proof_inputs: &PublicProofInputs, proof_points: &ProofPoints): bool { - verify_groth16_proof_internal( - curve.id, - &prepared_verifying_key.vk_gamma_abc_g1_bytes, - &prepared_verifying_key.alpha_g1_beta_g2_bytes, - &prepared_verifying_key.gamma_g2_neg_pc_bytes, - &prepared_verifying_key.delta_g2_neg_pc_bytes, - &public_proof_inputs.bytes, - &proof_points.bytes - ) - } +/// @param curve: What elliptic curve construction to use. See `bls12381` and `bn254`. +/// @param verifying_key: An Arkworks canonical compressed serialization of a verifying key. +/// +/// Returns four vectors of bytes representing the four components of a prepared verifying key. +/// This step computes one pairing e(P, Q), and binds the verification to one particular proof statement. +/// This can be used as inputs for the `verify_groth16_proof` function. +public fun prepare_verifying_key(curve: &Curve, verifying_key: &vector): PreparedVerifyingKey { + prepare_verifying_key_internal(curve.id, verifying_key) +} - /// Native functions that flattens the inputs into arrays of vectors and passed to the Rust native function. May abort with `EInvalidCurve` or `ETooManyPublicInputs`. - native fun verify_groth16_proof_internal(curve: u8, vk_gamma_abc_g1_bytes: &vector, alpha_g1_beta_g2_bytes: &vector, gamma_g2_neg_pc_bytes: &vector, delta_g2_neg_pc_bytes: &vector, public_proof_inputs: &vector, proof_points: &vector): bool; +/// Native functions that flattens the inputs into an array and passes to the Rust native function. May abort with `EInvalidVerifyingKey` or `EInvalidCurve`. +native fun prepare_verifying_key_internal( + curve: u8, + verifying_key: &vector, +): PreparedVerifyingKey; + +/// @param curve: What elliptic curve construction to use. See the `bls12381` and `bn254` functions. +/// @param prepared_verifying_key: Consists of four vectors of bytes representing the four components of a prepared verifying key. +/// @param public_proof_inputs: Represent inputs that are public. +/// @param proof_points: Represent three proof points. +/// +/// Returns a boolean indicating whether the proof is valid. +public fun verify_groth16_proof( + curve: &Curve, + prepared_verifying_key: &PreparedVerifyingKey, + public_proof_inputs: &PublicProofInputs, + proof_points: &ProofPoints, +): bool { + verify_groth16_proof_internal( + curve.id, + &prepared_verifying_key.vk_gamma_abc_g1_bytes, + &prepared_verifying_key.alpha_g1_beta_g2_bytes, + &prepared_verifying_key.gamma_g2_neg_pc_bytes, + &prepared_verifying_key.delta_g2_neg_pc_bytes, + &public_proof_inputs.bytes, + &proof_points.bytes, + ) } + +/// Native functions that flattens the inputs into arrays of vectors and passed to the Rust native function. May abort with `EInvalidCurve` or `ETooManyPublicInputs`. +native fun verify_groth16_proof_internal( + curve: u8, + vk_gamma_abc_g1_bytes: &vector, + alpha_g1_beta_g2_bytes: &vector, + gamma_g2_neg_pc_bytes: &vector, + delta_g2_neg_pc_bytes: &vector, + public_proof_inputs: &vector, + proof_points: &vector, +): bool; diff --git a/crates/sui-framework/packages/sui-framework/sources/crypto/group_ops.move b/crates/sui-framework/packages/sui-framework/sources/crypto/group_ops.move index a19c928c24847..49d19ad7147ce 100644 --- a/crates/sui-framework/packages/sui-framework/sources/crypto/group_ops.move +++ b/crates/sui-framework/packages/sui-framework/sources/crypto/group_ops.move @@ -2,116 +2,131 @@ // SPDX-License-Identifier: Apache-2.0 /// Generic Move and native functions for group operations. -module sui::group_ops { - - use sui::bcs; - - #[allow(unused_const)] - const ENotSupported: u64 = 0; // Operation is not supported by the network. - const EInvalidInput: u64 = 1; - #[allow(unused_const)] - const EInputTooLong: u64 = 2; - const EInvalidBufferLength: u64 = 3; - - ///////////////////////////////////////////////////// - ////// Generic functions for group operations. ////// - - // The caller provides a type identifier that should match the types of enum [Groups] in group_ops.rs. - - // General wrapper for all group elements. - public struct Element has store, copy, drop { - bytes: vector, - } - - public fun bytes(e: &Element): &vector { - &e.bytes - } - - public fun equal(e1: &Element, e2: &Element): bool { - &e1.bytes == &e2.bytes - } - - // Fails if the bytes are not a valid group element and 'is_trusted' is false. - public(package) fun from_bytes(type_: u8, bytes: &vector, is_trusted: bool): Element { - assert!(is_trusted || internal_validate(type_, bytes), EInvalidInput); - Element { bytes: *bytes } - } - - public(package) fun add(type_: u8, e1: &Element, e2: &Element): Element { - Element { bytes: internal_add(type_, &e1.bytes, &e2.bytes) } - } - - public(package) fun sub(type_: u8, e1: &Element, e2: &Element): Element { - Element { bytes: internal_sub(type_, &e1.bytes, &e2.bytes) } - } - - public(package) fun mul(type_: u8, scalar: &Element, e: &Element): Element { - Element { bytes: internal_mul(type_, &scalar.bytes, &e.bytes) } - } - - /// Fails if scalar = 0. Else returns 1/scalar * e. - public(package) fun div(type_: u8, scalar: &Element, e: &Element): Element { - Element { bytes: internal_div(type_, &scalar.bytes, &e.bytes) } - } - - public(package) fun hash_to(type_: u8, m: &vector): Element { - Element { bytes: internal_hash_to(type_, m) } - } - - /// Aborts with `EInputTooLong` if the vectors are too long. - public(package) fun multi_scalar_multiplication(type_: u8, scalars: &vector>, elements: &vector>): Element { - assert!(scalars.length() > 0, EInvalidInput); - assert!(scalars.length() == elements.length(), EInvalidInput); - - let mut scalars_bytes: vector = vector[]; - let mut elements_bytes: vector = vector[]; - let mut i = 0; - while (i < scalars.length()) { - let scalar_vec = scalars[i]; - scalars_bytes.append(scalar_vec.bytes); - let element_vec = elements[i]; - elements_bytes.append(element_vec.bytes); - i = i + 1; - }; - Element { bytes: internal_multi_scalar_mul(type_, &scalars_bytes, &elements_bytes) } - } - - public(package) fun pairing(type_: u8, e1: &Element, e2: &Element): Element { - Element { bytes: internal_pairing(type_, &e1.bytes, &e2.bytes) } - } - - ////////////////////////////// - ////// Native functions ////// - - // The following functions do *not* check whether the right types are used (e.g., Risretto255's scalar is used with - // Ristrertto255's G). The caller to the above functions is responsible for that. - - // 'type' specifies the type of all elements. - native fun internal_validate(type_: u8, bytes: &vector): bool; - native fun internal_add(type_: u8, e1: &vector, e2: &vector): vector; - native fun internal_sub(type_: u8, e1: &vector, e2: &vector): vector; - - // 'type' represents the type of e2, and the type of e1 is determined automatically from e2. e1 is a scalar - // and e2 is a group/scalar element. - native fun internal_mul(type_: u8, e1: &vector, e2: &vector): vector; - native fun internal_div(type_: u8, e1: &vector, e2: &vector): vector; - - native fun internal_hash_to(type_: u8, m: &vector): vector; - native fun internal_multi_scalar_mul(type_: u8, scalars: &vector, elements: &vector): vector; - - // 'type' represents the type of e1, and the rest are determined automatically from e1. - native fun internal_pairing(type_: u8, e1: &vector, e2: &vector): vector; - - // Helper function for encoding a given u64 number as bytes in a given buffer. - public(package) fun set_as_prefix(x: u64, big_endian: bool, buffer: &mut vector) { - let buffer_len = buffer.length(); - assert!(buffer_len > 7, EInvalidBufferLength); - let x_as_bytes = bcs::to_bytes(&x); // little endian - let mut i = 0; - while (i < 8) { - let position = if (big_endian) { buffer_len - i - 1 } else { i }; - *(&mut buffer[position]) = x_as_bytes[i]; - i = i + 1; +module sui::group_ops; + +use sui::bcs; + +#[allow(unused_const)] +const ENotSupported: u64 = 0; // Operation is not supported by the network. +const EInvalidInput: u64 = 1; +#[allow(unused_const)] +const EInputTooLong: u64 = 2; +const EInvalidBufferLength: u64 = 3; + +///////////////////////////////////////////////////// +////// Generic functions for group operations. ////// + +// The caller provides a type identifier that should match the types of enum [Groups] in group_ops.rs. + +// General wrapper for all group elements. +public struct Element has store, copy, drop { + bytes: vector, +} + +public fun bytes(e: &Element): &vector { + &e.bytes +} + +public fun equal(e1: &Element, e2: &Element): bool { + &e1.bytes == &e2.bytes +} + +// Fails if the bytes are not a valid group element and 'is_trusted' is false. +public(package) fun from_bytes(type_: u8, bytes: &vector, is_trusted: bool): Element { + assert!(is_trusted || internal_validate(type_, bytes), EInvalidInput); + Element { bytes: *bytes } +} + +public(package) fun add(type_: u8, e1: &Element, e2: &Element): Element { + Element { bytes: internal_add(type_, &e1.bytes, &e2.bytes) } +} + +public(package) fun sub(type_: u8, e1: &Element, e2: &Element): Element { + Element { bytes: internal_sub(type_, &e1.bytes, &e2.bytes) } +} + +public(package) fun mul(type_: u8, scalar: &Element, e: &Element): Element { + Element { bytes: internal_mul(type_, &scalar.bytes, &e.bytes) } +} + +/// Fails if scalar = 0. Else returns 1/scalar * e. +public(package) fun div(type_: u8, scalar: &Element, e: &Element): Element { + Element { bytes: internal_div(type_, &scalar.bytes, &e.bytes) } +} + +public(package) fun hash_to(type_: u8, m: &vector): Element { + Element { bytes: internal_hash_to(type_, m) } +} + +/// Aborts with `EInputTooLong` if the vectors are too long. +public(package) fun multi_scalar_multiplication( + type_: u8, + scalars: &vector>, + elements: &vector>, +): Element { + assert!(scalars.length() > 0, EInvalidInput); + assert!(scalars.length() == elements.length(), EInvalidInput); + + let mut scalars_bytes: vector = vector[]; + let mut elements_bytes: vector = vector[]; + let mut i = 0; + while (i < scalars.length()) { + let scalar_vec = scalars[i]; + scalars_bytes.append(scalar_vec.bytes); + let element_vec = elements[i]; + elements_bytes.append(element_vec.bytes); + i = i + 1; + }; + Element { bytes: internal_multi_scalar_mul(type_, &scalars_bytes, &elements_bytes) } +} + +public(package) fun pairing( + type_: u8, + e1: &Element, + e2: &Element, +): Element { + Element { bytes: internal_pairing(type_, &e1.bytes, &e2.bytes) } +} + +////////////////////////////// +////// Native functions ////// + +// The following functions do *not* check whether the right types are used (e.g., Risretto255's scalar is used with +// Ristrertto255's G). The caller to the above functions is responsible for that. + +// 'type' specifies the type of all elements. +native fun internal_validate(type_: u8, bytes: &vector): bool; +native fun internal_add(type_: u8, e1: &vector, e2: &vector): vector; +native fun internal_sub(type_: u8, e1: &vector, e2: &vector): vector; + +// 'type' represents the type of e2, and the type of e1 is determined automatically from e2. e1 is a scalar +// and e2 is a group/scalar element. +native fun internal_mul(type_: u8, e1: &vector, e2: &vector): vector; +native fun internal_div(type_: u8, e1: &vector, e2: &vector): vector; + +native fun internal_hash_to(type_: u8, m: &vector): vector; +native fun internal_multi_scalar_mul( + type_: u8, + scalars: &vector, + elements: &vector, +): vector; + +// 'type' represents the type of e1, and the rest are determined automatically from e1. +native fun internal_pairing(type_: u8, e1: &vector, e2: &vector): vector; + +// Helper function for encoding a given u64 number as bytes in a given buffer. +public(package) fun set_as_prefix(x: u64, big_endian: bool, buffer: &mut vector) { + let buffer_len = buffer.length(); + assert!(buffer_len > 7, EInvalidBufferLength); + let x_as_bytes = bcs::to_bytes(&x); // little endian + let mut i = 0; + while (i < 8) { + let position = if (big_endian) { + buffer_len - i - 1 + } else { + i }; - } + *(&mut buffer[position]) = x_as_bytes[i]; + i = i + 1; + }; } diff --git a/crates/sui-framework/packages/sui-framework/sources/crypto/hash.move b/crates/sui-framework/packages/sui-framework/sources/crypto/hash.move index 4b2e9d6bc0e03..3e9a83f741f03 100644 --- a/crates/sui-framework/packages/sui-framework/sources/crypto/hash.move +++ b/crates/sui-framework/packages/sui-framework/sources/crypto/hash.move @@ -3,12 +3,12 @@ /// Module which defines hash functions. Note that Sha-256 and Sha3-256 is available in the std::hash module in the /// standard library. -module sui::hash { - /// @param data: Arbitrary binary data to hash - /// Hash the input bytes using Blake2b-256 and returns 32 bytes. - native public fun blake2b256(data: &vector): vector; +module sui::hash; - /// @param data: Arbitrary binary data to hash - /// Hash the input bytes using keccak256 and returns 32 bytes. - native public fun keccak256(data: &vector): vector; -} +/// @param data: Arbitrary binary data to hash +/// Hash the input bytes using Blake2b-256 and returns 32 bytes. +public native fun blake2b256(data: &vector): vector; + +/// @param data: Arbitrary binary data to hash +/// Hash the input bytes using keccak256 and returns 32 bytes. +public native fun keccak256(data: &vector): vector; diff --git a/crates/sui-framework/packages/sui-framework/sources/crypto/hmac.move b/crates/sui-framework/packages/sui-framework/sources/crypto/hmac.move index 7235658d2bf1d..f5adc10aa5a7e 100644 --- a/crates/sui-framework/packages/sui-framework/sources/crypto/hmac.move +++ b/crates/sui-framework/packages/sui-framework/sources/crypto/hmac.move @@ -1,11 +1,9 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -module sui::hmac { +module sui::hmac; - /// @param key: HMAC key, arbitrary bytes. - /// @param msg: message to sign, arbitrary bytes. - /// Returns the 32 bytes digest of HMAC-SHA3-256(key, msg). - public native fun hmac_sha3_256(key: &vector, msg: &vector): vector; - -} +/// @param key: HMAC key, arbitrary bytes. +/// @param msg: message to sign, arbitrary bytes. +/// Returns the 32 bytes digest of HMAC-SHA3-256(key, msg). +public native fun hmac_sha3_256(key: &vector, msg: &vector): vector; diff --git a/crates/sui-framework/packages/sui-framework/sources/crypto/poseidon.move b/crates/sui-framework/packages/sui-framework/sources/crypto/poseidon.move index 1a6055c7a0f3f..dfcdcd4f12f95 100644 --- a/crates/sui-framework/packages/sui-framework/sources/crypto/poseidon.move +++ b/crates/sui-framework/packages/sui-framework/sources/crypto/poseidon.move @@ -2,40 +2,40 @@ // SPDX-License-Identifier: Apache-2.0 /// Module which defines instances of the poseidon hash functions. -module sui::poseidon { +module sui::poseidon; - use sui::bcs; +use sui::bcs; - /// Error if any of the inputs are larger than or equal to the BN254 field size. - const ENonCanonicalInput: u64 = 0; +/// Error if any of the inputs are larger than or equal to the BN254 field size. +const ENonCanonicalInput: u64 = 0; - /// Error if an empty vector is passed as input. - const EEmptyInput: u64 = 1; +/// Error if an empty vector is passed as input. +const EEmptyInput: u64 = 1; - /// The field size for BN254 curve. - const BN254_MAX: u256 = 21888242871839275222246405745257275088548364400416034343698204186575808495617u256; +/// The field size for BN254 curve. +const BN254_MAX: u256 = + 21888242871839275222246405745257275088548364400416034343698204186575808495617u256; - /// @param data: Vector of BN254 field elements to hash. - /// - /// Hash the inputs using poseidon_bn254 and returns a BN254 field element. - /// - /// Each element has to be a BN254 field element in canonical representation so it must be smaller than the BN254 - /// scalar field size which is 21888242871839275222246405745257275088548364400416034343698204186575808495617. - public fun poseidon_bn254(data: &vector): u256 { - let (mut i, mut b, l) = (0, vector[], data.length()); - assert!(l > 0, EEmptyInput); - while (i < l) { - let field_element = &data[i]; - assert!(*field_element < BN254_MAX, ENonCanonicalInput); - b.push_back(bcs::to_bytes(&data[i])); - i = i + 1; - }; - let binary_output = poseidon_bn254_internal(&b); - bcs::new(binary_output).peel_u256() - } - - /// @param data: Vector of BN254 field elements in little-endian representation. - /// - /// Hash the inputs using poseidon_bn254 and returns a BN254 field element in little-endian representation. - native fun poseidon_bn254_internal(data: &vector>): vector; +/// @param data: Vector of BN254 field elements to hash. +/// +/// Hash the inputs using poseidon_bn254 and returns a BN254 field element. +/// +/// Each element has to be a BN254 field element in canonical representation so it must be smaller than the BN254 +/// scalar field size which is 21888242871839275222246405745257275088548364400416034343698204186575808495617. +public fun poseidon_bn254(data: &vector): u256 { + let (mut i, mut b, l) = (0, vector[], data.length()); + assert!(l > 0, EEmptyInput); + while (i < l) { + let field_element = &data[i]; + assert!(*field_element < BN254_MAX, ENonCanonicalInput); + b.push_back(bcs::to_bytes(&data[i])); + i = i + 1; + }; + let binary_output = poseidon_bn254_internal(&b); + bcs::new(binary_output).peel_u256() } + +/// @param data: Vector of BN254 field elements in little-endian representation. +/// +/// Hash the inputs using poseidon_bn254 and returns a BN254 field element in little-endian representation. +native fun poseidon_bn254_internal(data: &vector>): vector; diff --git a/crates/sui-framework/packages/sui-framework/sources/crypto/vdf.move b/crates/sui-framework/packages/sui-framework/sources/crypto/vdf.move index 0bb9a70aeb63e..61e072291ca3d 100644 --- a/crates/sui-framework/packages/sui-framework/sources/crypto/vdf.move +++ b/crates/sui-framework/packages/sui-framework/sources/crypto/vdf.move @@ -1,35 +1,44 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -module sui::vdf { +module sui::vdf; - #[allow(unused_const)] - const EInvalidInput: u64 = 0; +#[allow(unused_const)] +const EInvalidInput: u64 = 0; - /// Hash an arbitrary binary `message` to a class group element to be used as input for `vdf_verify`. - public fun hash_to_input(message: &vector): vector { - hash_to_input_internal(message) - } - - /// The internal functions for `hash_to_input`. - native fun hash_to_input_internal(message: &vector): vector; +/// Hash an arbitrary binary `message` to a class group element to be used as input for `vdf_verify`. +public fun hash_to_input(message: &vector): vector { + hash_to_input_internal(message) +} - /// Verify the output and proof of a VDF with the given number of iterations. The `input`, `output` and `proof` - /// are all class group elements represented by triples `(a,b,c)` such that `b^2 - 4ac = discriminant`. The are expected - /// to be encoded as a BCS encoding of a triple of byte arrays, each being the big-endian twos-complement encoding of - /// a, b and c in that order. - /// - /// This uses Wesolowski's VDF construction over imaginary class groups as described in Wesolowski (2020), - /// 'Efficient Verifiable Delay Functions.', J. Cryptol. 33, and is compatible with the VDF implementation in - /// fastcrypto. - /// - /// The discriminant for the class group is pre-computed and fixed. See how this was generated in the fastcrypto-vdf - /// crate. The final selection of the discriminant for Mainnet will be computed and announced under a nothing-up-my-sleeve - /// process. - public fun vdf_verify(input: &vector, output: &vector, proof: &vector, iterations: u64): bool { - vdf_verify_internal(input, output, proof, iterations) - } +/// The internal functions for `hash_to_input`. +native fun hash_to_input_internal(message: &vector): vector; - /// The internal functions for `vdf_verify_internal`. - native fun vdf_verify_internal(input: &vector, output: &vector, proof: &vector, iterations: u64): bool; +/// Verify the output and proof of a VDF with the given number of iterations. The `input`, `output` and `proof` +/// are all class group elements represented by triples `(a,b,c)` such that `b^2 - 4ac = discriminant`. The are expected +/// to be encoded as a BCS encoding of a triple of byte arrays, each being the big-endian twos-complement encoding of +/// a, b and c in that order. +/// +/// This uses Wesolowski's VDF construction over imaginary class groups as described in Wesolowski (2020), +/// 'Efficient Verifiable Delay Functions.', J. Cryptol. 33, and is compatible with the VDF implementation in +/// fastcrypto. +/// +/// The discriminant for the class group is pre-computed and fixed. See how this was generated in the fastcrypto-vdf +/// crate. The final selection of the discriminant for Mainnet will be computed and announced under a nothing-up-my-sleeve +/// process. +public fun vdf_verify( + input: &vector, + output: &vector, + proof: &vector, + iterations: u64, +): bool { + vdf_verify_internal(input, output, proof, iterations) } + +/// The internal functions for `vdf_verify_internal`. +native fun vdf_verify_internal( + input: &vector, + output: &vector, + proof: &vector, + iterations: u64, +): bool; diff --git a/crates/sui-framework/packages/sui-framework/sources/crypto/zklogin_verified_id.move b/crates/sui-framework/packages/sui-framework/sources/crypto/zklogin_verified_id.move index 63e59f1dd6e76..4035a3577d4a0 100644 --- a/crates/sui-framework/packages/sui-framework/sources/crypto/zklogin_verified_id.move +++ b/crates/sui-framework/packages/sui-framework/sources/crypto/zklogin_verified_id.move @@ -2,94 +2,95 @@ // SPDX-License-Identifier: Apache-2.0 #[allow(unused_const, unused_function)] -module sui::zklogin_verified_id { - use std::string::String; +module sui::zklogin_verified_id; - const EFunctionDisabled: u64 = 0; +use std::string::String; - /// Possession of a VerifiedID proves that the user's address was created using zklogin and the given parameters. - public struct VerifiedID has key { - /// The ID of this VerifiedID - id: UID, - /// The address this VerifiedID is associated with - owner: address, - /// The name of the key claim - key_claim_name: String, - /// The value of the key claim - key_claim_value: String, - /// The issuer - issuer: String, - /// The audience (wallet) - audience: String, - } +const EFunctionDisabled: u64 = 0; - /// Returns the address associated with the given VerifiedID - public fun owner(verified_id: &VerifiedID): address { - verified_id.owner - } +/// Possession of a VerifiedID proves that the user's address was created using zklogin and the given parameters. +public struct VerifiedID has key { + /// The ID of this VerifiedID + id: UID, + /// The address this VerifiedID is associated with + owner: address, + /// The name of the key claim + key_claim_name: String, + /// The value of the key claim + key_claim_value: String, + /// The issuer + issuer: String, + /// The audience (wallet) + audience: String, +} - /// Returns the name of the key claim associated with the given VerifiedID - public fun key_claim_name(verified_id: &VerifiedID): &String { - &verified_id.key_claim_name - } +/// Returns the address associated with the given VerifiedID +public fun owner(verified_id: &VerifiedID): address { + verified_id.owner +} - /// Returns the value of the key claim associated with the given VerifiedID - public fun key_claim_value(verified_id: &VerifiedID): &String { - &verified_id.key_claim_value - } +/// Returns the name of the key claim associated with the given VerifiedID +public fun key_claim_name(verified_id: &VerifiedID): &String { + &verified_id.key_claim_name +} - /// Returns the issuer associated with the given VerifiedID - public fun issuer(verified_id: &VerifiedID): &String { - &verified_id.issuer - } +/// Returns the value of the key claim associated with the given VerifiedID +public fun key_claim_value(verified_id: &VerifiedID): &String { + &verified_id.key_claim_value +} - /// Returns the audience (wallet) associated with the given VerifiedID - public fun audience(verified_id: &VerifiedID): &String { - &verified_id.audience - } +/// Returns the issuer associated with the given VerifiedID +public fun issuer(verified_id: &VerifiedID): &String { + &verified_id.issuer +} - /// Delete a VerifiedID - public fun delete(verified_id: VerifiedID) { - let VerifiedID { id, owner: _, key_claim_name: _, key_claim_value: _, issuer: _, audience: _ } = verified_id; - id.delete(); - } +/// Returns the audience (wallet) associated with the given VerifiedID +public fun audience(verified_id: &VerifiedID): &String { + &verified_id.audience +} - /// This function has been disabled. - public fun verify_zklogin_id( - _key_claim_name: String, - _key_claim_value: String, - _issuer: String, - _audience: String, - _pin_hash: u256, - _ctx: &mut TxContext, - ) { - assert!(false, EFunctionDisabled); - } +/// Delete a VerifiedID +public fun delete(verified_id: VerifiedID) { + let VerifiedID { id, owner: _, key_claim_name: _, key_claim_value: _, issuer: _, audience: _ } = + verified_id; + id.delete(); +} - /// This function has been disabled. - public fun check_zklogin_id( - _address: address, - _key_claim_name: &String, - _key_claim_value: &String, - _issuer: &String, - _audience: &String, - _pin_hash: u256 - ): bool { - assert!(false, EFunctionDisabled); - false - } +/// This function has been disabled. +public fun verify_zklogin_id( + _key_claim_name: String, + _key_claim_value: String, + _issuer: String, + _audience: String, + _pin_hash: u256, + _ctx: &mut TxContext, +) { + assert!(false, EFunctionDisabled); +} - /// Returns true if `address` was created using zklogin and the given parameters. - /// - /// Aborts with `EInvalidInput` if any of `kc_name`, `kc_value`, `iss` and `aud` is not a properly encoded UTF-8 - /// string or if the inputs are longer than the allowed upper bounds: `kc_name` must be at most 32 characters, - /// `kc_value` must be at most 115 characters and `aud` must be at most 145 characters. - native fun check_zklogin_id_internal( - address: address, - key_claim_name: &vector, - key_claim_value: &vector, - issuer: &vector, - audience: &vector, - pin_hash: u256 - ): bool; +/// This function has been disabled. +public fun check_zklogin_id( + _address: address, + _key_claim_name: &String, + _key_claim_value: &String, + _issuer: &String, + _audience: &String, + _pin_hash: u256, +): bool { + assert!(false, EFunctionDisabled); + false } + +/// Returns true if `address` was created using zklogin and the given parameters. +/// +/// Aborts with `EInvalidInput` if any of `kc_name`, `kc_value`, `iss` and `aud` is not a properly encoded UTF-8 +/// string or if the inputs are longer than the allowed upper bounds: `kc_name` must be at most 32 characters, +/// `kc_value` must be at most 115 characters and `aud` must be at most 145 characters. +native fun check_zklogin_id_internal( + address: address, + key_claim_name: &vector, + key_claim_value: &vector, + issuer: &vector, + audience: &vector, + pin_hash: u256, +): bool; diff --git a/crates/sui-framework/packages/sui-framework/sources/crypto/zklogin_verified_issuer.move b/crates/sui-framework/packages/sui-framework/sources/crypto/zklogin_verified_issuer.move index 502bfd195b4ed..081aa81a606e0 100644 --- a/crates/sui-framework/packages/sui-framework/sources/crypto/zklogin_verified_issuer.move +++ b/crates/sui-framework/packages/sui-framework/sources/crypto/zklogin_verified_issuer.move @@ -2,78 +2,70 @@ // SPDX-License-Identifier: Apache-2.0 #[allow(unused_const)] -module sui::zklogin_verified_issuer { - use std::string::String; +module sui::zklogin_verified_issuer; - /// Error if the proof consisting of the inputs provided to the verification function is invalid. - const EInvalidInput: u64 = 0; +use std::string::String; - /// Error if the proof consisting of the inputs provided to the verification function is invalid. - const EInvalidProof: u64 = 1; +/// Error if the proof consisting of the inputs provided to the verification function is invalid. +const EInvalidInput: u64 = 0; - /// Possession of a VerifiedIssuer proves that the user's address was created using zklogin and with the given issuer - /// (identity provider). - public struct VerifiedIssuer has key { - /// The ID of this VerifiedIssuer - id: UID, - /// The address this VerifiedID is associated with - owner: address, - /// The issuer - issuer: String, - } +/// Error if the proof consisting of the inputs provided to the verification function is invalid. +const EInvalidProof: u64 = 1; - /// Returns the address associated with the given VerifiedIssuer - public fun owner(verified_issuer: &VerifiedIssuer): address { - verified_issuer.owner - } +/// Possession of a VerifiedIssuer proves that the user's address was created using zklogin and with the given issuer +/// (identity provider). +public struct VerifiedIssuer has key { + /// The ID of this VerifiedIssuer + id: UID, + /// The address this VerifiedID is associated with + owner: address, + /// The issuer + issuer: String, +} - /// Returns the issuer associated with the given VerifiedIssuer - public fun issuer(verified_issuer: &VerifiedIssuer): &String { - &verified_issuer.issuer - } +/// Returns the address associated with the given VerifiedIssuer +public fun owner(verified_issuer: &VerifiedIssuer): address { + verified_issuer.owner +} - /// Delete a VerifiedIssuer - public fun delete(verified_issuer: VerifiedIssuer) { - let VerifiedIssuer { id, owner: _, issuer: _ } = verified_issuer; - id.delete(); - } +/// Returns the issuer associated with the given VerifiedIssuer +public fun issuer(verified_issuer: &VerifiedIssuer): &String { + &verified_issuer.issuer +} - /// Verify that the caller's address was created using zklogin with the given issuer. If so, a VerifiedIssuer object - /// with the issuers id transferred to the caller. - /// - /// Aborts with `EInvalidProof` if the verification fails. - public fun verify_zklogin_issuer( - address_seed: u256, - issuer: String, - ctx: &mut TxContext, - ) { - let sender = ctx.sender(); - assert!(check_zklogin_issuer(sender, address_seed, &issuer), EInvalidProof); - transfer::transfer( - VerifiedIssuer { - id: object::new(ctx), - owner: sender, - issuer - }, - sender - ) - } +/// Delete a VerifiedIssuer +public fun delete(verified_issuer: VerifiedIssuer) { + let VerifiedIssuer { id, owner: _, issuer: _ } = verified_issuer; + id.delete(); +} - /// Returns true if `address` was created using zklogin with the given issuer and address seed. - public fun check_zklogin_issuer( - address: address, - address_seed: u256, - issuer: &String, - ): bool { - check_zklogin_issuer_internal(address, address_seed, issuer.as_bytes()) - } +/// Verify that the caller's address was created using zklogin with the given issuer. If so, a VerifiedIssuer object +/// with the issuers id transferred to the caller. +/// +/// Aborts with `EInvalidProof` if the verification fails. +public fun verify_zklogin_issuer(address_seed: u256, issuer: String, ctx: &mut TxContext) { + let sender = ctx.sender(); + assert!(check_zklogin_issuer(sender, address_seed, &issuer), EInvalidProof); + transfer::transfer( + VerifiedIssuer { + id: object::new(ctx), + owner: sender, + issuer, + }, + sender, + ) +} - /// Returns true if `address` was created using zklogin with the given issuer and address seed. - /// - /// Aborts with `EInvalidInput` if the `iss` input is not a valid UTF-8 string. - native fun check_zklogin_issuer_internal( - address: address, - address_seed: u256, - issuer: &vector, - ): bool; +/// Returns true if `address` was created using zklogin with the given issuer and address seed. +public fun check_zklogin_issuer(address: address, address_seed: u256, issuer: &String): bool { + check_zklogin_issuer_internal(address, address_seed, issuer.as_bytes()) } + +/// Returns true if `address` was created using zklogin with the given issuer and address seed. +/// +/// Aborts with `EInvalidInput` if the `iss` input is not a valid UTF-8 string. +native fun check_zklogin_issuer_internal( + address: address, + address_seed: u256, + issuer: &vector, +): bool; diff --git a/crates/sui-framework/packages/sui-framework/sources/deny_list.move b/crates/sui-framework/packages/sui-framework/sources/deny_list.move index f2e74ee41b52e..51a1c91d8c9f3 100644 --- a/crates/sui-framework/packages/sui-framework/sources/deny_list.move +++ b/crates/sui-framework/packages/sui-framework/sources/deny_list.move @@ -4,445 +4,427 @@ /// Defines the `DenyList` type. The `DenyList` shared object is used to restrict access to /// instances of certain core types from being used as inputs by specified addresses in the deny /// list. -module sui::deny_list { - use sui::config::{Self, Config}; - use sui::dynamic_object_field as ofield; - use sui::table::{Self, Table}; - use sui::bag::{Self, Bag}; - use sui::vec_set::{Self, VecSet}; - - /// Trying to create a deny list object when not called by the system address. - const ENotSystemAddress: u64 = 0; - /// The specified address to be removed is not already in the deny list. - const ENotDenied: u64 = 1; - /// The specified address cannot be added to the deny list. - const EInvalidAddress: u64 = 1; - - /// The index into the deny list vector for the `sui::coin::Coin` type. - const COIN_INDEX: u64 = 0; - - /// These addresses are reserved and cannot be added to the deny list. - /// The addresses listed are well known package and object addresses. So it would be - /// meaningless to add them to the deny list. - const RESERVED: vector
= vector[ - @0x0, - @0x1, - @0x2, - @0x3, - @0x4, - @0x5, - @0x6, - @0x7, - @0x8, - @0x9, - @0xA, - @0xB, - @0xC, - @0xD, - @0xE, - @0xF, - @0x403, - @0xDEE9, - ]; - - /// A shared object that stores the addresses that are blocked for a given core type. - public struct DenyList has key { - id: UID, - /// The individual deny lists. - lists: Bag, - } +module sui::deny_list; + +use sui::bag::{Self, Bag}; +use sui::config::{Self, Config}; +use sui::dynamic_object_field as ofield; +use sui::table::{Self, Table}; +use sui::vec_set::{Self, VecSet}; + +/// Trying to create a deny list object when not called by the system address. +const ENotSystemAddress: u64 = 0; +/// The specified address to be removed is not already in the deny list. +const ENotDenied: u64 = 1; +/// The specified address cannot be added to the deny list. +const EInvalidAddress: u64 = 1; + +/// The index into the deny list vector for the `sui::coin::Coin` type. +const COIN_INDEX: u64 = 0; + +/// These addresses are reserved and cannot be added to the deny list. +/// The addresses listed are well known package and object addresses. So it would be +/// meaningless to add them to the deny list. +const RESERVED: vector
= vector[ + @0x0, + @0x1, + @0x2, + @0x3, + @0x4, + @0x5, + @0x6, + @0x7, + @0x8, + @0x9, + @0xA, + @0xB, + @0xC, + @0xD, + @0xE, + @0xF, + @0x403, + @0xDEE9, +]; + +/// A shared object that stores the addresses that are blocked for a given core type. +public struct DenyList has key { + id: UID, + /// The individual deny lists. + lists: Bag, +} +// === V2 === - // === V2 === +/// The capability used to write to the deny list config. Ensures that the Configs for the +/// DenyList are modified only by this module. +public struct ConfigWriteCap() has drop; - /// The capability used to write to the deny list config. Ensures that the Configs for the - /// DenyList are modified only by this module. - public struct ConfigWriteCap() has drop; +/// The dynamic object field key used to store the `Config` for a given type, essentially a +/// `(per_type_index, per_type_key)` pair. +public struct ConfigKey has copy, drop, store { + per_type_index: u64, + per_type_key: vector, +} - /// The dynamic object field key used to store the `Config` for a given type, essentially a - /// `(per_type_index, per_type_key)` pair. - public struct ConfigKey has copy, drop, store { - per_type_index: u64, - per_type_key: vector, - } +/// The setting key used to store the deny list for a given address in the `Config`. +public struct AddressKey(address) has copy, drop, store; - /// The setting key used to store the deny list for a given address in the `Config`. - public struct AddressKey(address) has copy, drop, store; +/// The setting key used to store the global pause setting in the `Config`. +public struct GlobalPauseKey() has copy, drop, store; - /// The setting key used to store the global pause setting in the `Config`. - public struct GlobalPauseKey() has copy, drop, store; +/// The event emitted when a new `Config` is created for a given type. This can be useful for +/// tracking the `ID` of a type's `Config` object. +public struct PerTypeConfigCreated has copy, drop, store { + key: ConfigKey, + config_id: ID, +} - /// The event emitted when a new `Config` is created for a given type. This can be useful for - /// tracking the `ID` of a type's `Config` object. - public struct PerTypeConfigCreated has copy, drop, store { - key: ConfigKey, - config_id: ID, - } +public(package) fun v2_add( + deny_list: &mut DenyList, + per_type_index: u64, + per_type_key: vector, + addr: address, + ctx: &mut TxContext, +) { + let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); + let setting_name = AddressKey(addr); + let next_epoch_entry = per_type_config.entry!<_, AddressKey, bool>( + &mut ConfigWriteCap(), + setting_name, + |_deny_list, _cap, _ctx| true, + ctx, + ); + *next_epoch_entry = true; +} - public(package) fun v2_add( - deny_list: &mut DenyList, - per_type_index: u64, - per_type_key: vector, - addr: address, - ctx: &mut TxContext, - ) { - let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); - let setting_name = AddressKey(addr); - let next_epoch_entry = per_type_config.entry!<_,AddressKey, bool>( - &mut ConfigWriteCap(), - setting_name, - |_deny_list, _cap, _ctx| true, - ctx, - ); - *next_epoch_entry = true; - } +public(package) fun v2_remove( + deny_list: &mut DenyList, + per_type_index: u64, + per_type_key: vector, + addr: address, + ctx: &mut TxContext, +) { + let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); + let setting_name = AddressKey(addr); + per_type_config.remove_for_next_epoch<_, AddressKey, bool>( + &mut ConfigWriteCap(), + setting_name, + ctx, + ); +} - public(package) fun v2_remove( - deny_list: &mut DenyList, - per_type_index: u64, - per_type_key: vector, - addr: address, - ctx: &mut TxContext, - ) { - let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); - let setting_name = AddressKey(addr); - per_type_config.remove_for_next_epoch<_, AddressKey, bool>( - &mut ConfigWriteCap(), - setting_name, - ctx, - ); - } +public(package) fun v2_contains_current_epoch( + deny_list: &DenyList, + per_type_index: u64, + per_type_key: vector, + addr: address, + ctx: &TxContext, +): bool { + if (!deny_list.per_type_exists(per_type_index, per_type_key)) return false; + let per_type_config = deny_list.borrow_per_type_config(per_type_index, per_type_key); + let setting_name = AddressKey(addr); + config::read_setting(object::id(per_type_config), setting_name, ctx).destroy_or!(false) +} - public(package) fun v2_contains_current_epoch( - deny_list: &DenyList, - per_type_index: u64, - per_type_key: vector, - addr: address, - ctx: &TxContext, - ): bool { - if (!deny_list.per_type_exists(per_type_index, per_type_key)) return false; - let per_type_config = deny_list.borrow_per_type_config(per_type_index, per_type_key); - let setting_name = AddressKey(addr); - config::read_setting(object::id(per_type_config), setting_name, ctx).destroy_or!(false) - } +public(package) fun v2_contains_next_epoch( + deny_list: &DenyList, + per_type_index: u64, + per_type_key: vector, + addr: address, +): bool { + if (!deny_list.per_type_exists(per_type_index, per_type_key)) return false; + let per_type_config = deny_list.borrow_per_type_config(per_type_index, per_type_key); + let setting_name = AddressKey(addr); + per_type_config.read_setting_for_next_epoch(setting_name).destroy_or!(false) +} - public(package) fun v2_contains_next_epoch( - deny_list: &DenyList, - per_type_index: u64, - per_type_key: vector, - addr: address, - ): bool { - if (!deny_list.per_type_exists(per_type_index, per_type_key)) return false; - let per_type_config = deny_list.borrow_per_type_config(per_type_index, per_type_key); - let setting_name = AddressKey(addr); - per_type_config.read_setting_for_next_epoch(setting_name).destroy_or!(false) - } +// public(package) fun v2_per_type_contains( +// per_type_config: ID, +// addr: address, +// ): bool { +// // TODO can read from the config directly once the ID is set +// } + +public(package) fun v2_enable_global_pause( + deny_list: &mut DenyList, + per_type_index: u64, + per_type_key: vector, + ctx: &mut TxContext, +) { + let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); + let setting_name = GlobalPauseKey(); + let next_epoch_entry = per_type_config.entry!<_, GlobalPauseKey, bool>( + &mut ConfigWriteCap(), + setting_name, + |_deny_list, _cap, _ctx| true, + ctx, + ); + *next_epoch_entry = true; +} + +public(package) fun v2_disable_global_pause( + deny_list: &mut DenyList, + per_type_index: u64, + per_type_key: vector, + ctx: &mut TxContext, +) { + let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); + let setting_name = GlobalPauseKey(); + per_type_config.remove_for_next_epoch<_, GlobalPauseKey, bool>( + &mut ConfigWriteCap(), + setting_name, + ctx, + ); +} + +public(package) fun v2_is_global_pause_enabled_current_epoch( + deny_list: &DenyList, + per_type_index: u64, + per_type_key: vector, + ctx: &TxContext, +): bool { + if (!deny_list.per_type_exists(per_type_index, per_type_key)) return false; + let per_type_config = deny_list.borrow_per_type_config(per_type_index, per_type_key); + let setting_name = GlobalPauseKey(); + config::read_setting(object::id(per_type_config), setting_name, ctx).destroy_or!(false) +} - // public(package) fun v2_per_type_contains( - // per_type_config: ID, - // addr: address, - // ): bool { - // // TODO can read from the config directly once the ID is set - // } - - public(package) fun v2_enable_global_pause( - deny_list: &mut DenyList, - per_type_index: u64, - per_type_key: vector, - ctx: &mut TxContext, - ) { - let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); - let setting_name = GlobalPauseKey(); - let next_epoch_entry = per_type_config.entry!<_, GlobalPauseKey, bool>( +public(package) fun v2_is_global_pause_enabled_next_epoch( + deny_list: &DenyList, + per_type_index: u64, + per_type_key: vector, +): bool { + if (!deny_list.per_type_exists(per_type_index, per_type_key)) return false; + let per_type_config = deny_list.borrow_per_type_config(per_type_index, per_type_key); + let setting_name = GlobalPauseKey(); + per_type_config.read_setting_for_next_epoch(setting_name).destroy_or!(false) +} + +// public(package) fun v2_per_type_is_global_pause_enabled( +// per_type_config: ID, +// ): bool { +// // TODO can read from the config directly once the ID is set +// } + +public(package) fun migrate_v1_to_v2( + deny_list: &mut DenyList, + per_type_index: u64, + per_type_key: vector, + ctx: &mut TxContext, +) { + let bag_entry: &mut PerTypeList = &mut deny_list.lists[per_type_index]; + let elements = if (!bag_entry.denied_addresses.contains(per_type_key)) vector[] + else bag_entry.denied_addresses.remove(per_type_key).into_keys(); + elements.do_ref!(|addr| { + let addr = *addr; + let denied_count = &mut bag_entry.denied_count[addr]; + *denied_count = *denied_count - 1; + if (*denied_count == 0) { + bag_entry.denied_count.remove(addr); + } + }); + let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); + elements.do!(|addr| { + let setting_name = AddressKey(addr); + let next_epoch_entry = per_type_config.entry!<_, AddressKey, bool>( &mut ConfigWriteCap(), setting_name, |_deny_list, _cap, _ctx| true, ctx, ); *next_epoch_entry = true; - } - - public(package) fun v2_disable_global_pause( - deny_list: &mut DenyList, - per_type_index: u64, - per_type_key: vector, - ctx: &mut TxContext, - ) { - let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); - let setting_name = GlobalPauseKey(); - per_type_config.remove_for_next_epoch<_, GlobalPauseKey, bool>( - &mut ConfigWriteCap(), - setting_name, - ctx, - ); - } - - public(package) fun v2_is_global_pause_enabled_current_epoch( - deny_list: &DenyList, - per_type_index: u64, - per_type_key: vector, - ctx: &TxContext, - ): bool { - if (!deny_list.per_type_exists(per_type_index, per_type_key)) return false; - let per_type_config = deny_list.borrow_per_type_config(per_type_index, per_type_key); - let setting_name = GlobalPauseKey(); - config::read_setting(object::id(per_type_config), setting_name, ctx).destroy_or!(false) - } - - public(package) fun v2_is_global_pause_enabled_next_epoch( - deny_list: &DenyList, - per_type_index: u64, - per_type_key: vector, - ): bool { - if (!deny_list.per_type_exists(per_type_index, per_type_key)) return false; - let per_type_config = deny_list.borrow_per_type_config(per_type_index, per_type_key); - let setting_name = GlobalPauseKey(); - per_type_config.read_setting_for_next_epoch(setting_name).destroy_or!(false) - } - - // public(package) fun v2_per_type_is_global_pause_enabled( - // per_type_config: ID, - // ): bool { - // // TODO can read from the config directly once the ID is set - // } - - public(package) fun migrate_v1_to_v2( - deny_list: &mut DenyList, - per_type_index: u64, - per_type_key: vector, - ctx: &mut TxContext, - ) { - let bag_entry: &mut PerTypeList = &mut deny_list.lists[per_type_index]; - let elements = - if (!bag_entry.denied_addresses.contains(per_type_key)) vector[] - else bag_entry.denied_addresses.remove(per_type_key).into_keys(); - elements.do_ref!(|addr| { - let addr = *addr; - let denied_count = &mut bag_entry.denied_count[addr]; - *denied_count = *denied_count - 1; - if (*denied_count == 0) { - bag_entry.denied_count.remove(addr); - } - }); - let per_type_config = deny_list.per_type_config_entry!(per_type_index, per_type_key, ctx); - elements.do!(|addr| { - let setting_name = AddressKey(addr); - let next_epoch_entry = per_type_config.entry!<_,AddressKey, bool>( - &mut ConfigWriteCap(), - setting_name, - |_deny_list, _cap, _ctx| true, - ctx, - ); - *next_epoch_entry = true; - }); - } + }); +} - fun add_per_type_config( - deny_list: &mut DenyList, - per_type_index: u64, - per_type_key: vector, - ctx: &mut TxContext, - ) { - let key = ConfigKey { per_type_index, per_type_key }; - let config = config::new(&mut ConfigWriteCap(), ctx); - let config_id = object::id(&config); - ofield::internal_add(&mut deny_list.id, key, config); - sui::event::emit(PerTypeConfigCreated { key, config_id }); - } +fun add_per_type_config( + deny_list: &mut DenyList, + per_type_index: u64, + per_type_key: vector, + ctx: &mut TxContext, +) { + let key = ConfigKey { per_type_index, per_type_key }; + let config = config::new(&mut ConfigWriteCap(), ctx); + let config_id = object::id(&config); + ofield::internal_add(&mut deny_list.id, key, config); + sui::event::emit(PerTypeConfigCreated { key, config_id }); +} - fun borrow_per_type_config_mut( - deny_list: &mut DenyList, - per_type_index: u64, - per_type_key: vector, - ): &mut Config { - let key = ConfigKey { per_type_index, per_type_key }; - ofield::internal_borrow_mut(&mut deny_list.id, key) - } +fun borrow_per_type_config_mut( + deny_list: &mut DenyList, + per_type_index: u64, + per_type_key: vector, +): &mut Config { + let key = ConfigKey { per_type_index, per_type_key }; + ofield::internal_borrow_mut(&mut deny_list.id, key) +} - fun borrow_per_type_config( - deny_list: &DenyList, - per_type_index: u64, - per_type_key: vector, - ): &Config { - let key = ConfigKey { per_type_index, per_type_key }; - ofield::internal_borrow(&deny_list.id, key) - } +fun borrow_per_type_config( + deny_list: &DenyList, + per_type_index: u64, + per_type_key: vector, +): &Config { + let key = ConfigKey { per_type_index, per_type_key }; + ofield::internal_borrow(&deny_list.id, key) +} - fun per_type_exists( - deny_list: &DenyList, - per_type_index: u64, - per_type_key: vector, - ): bool { - let key = ConfigKey { per_type_index, per_type_key }; - ofield::exists_(&deny_list.id, key) - } +fun per_type_exists(deny_list: &DenyList, per_type_index: u64, per_type_key: vector): bool { + let key = ConfigKey { per_type_index, per_type_key }; + ofield::exists_(&deny_list.id, key) +} - macro fun per_type_config_entry( - $deny_list: &mut DenyList, - $per_type_index: u64, - $per_type_key: vector, - $ctx: &mut TxContext, - ): &mut Config { - let deny_list = $deny_list; - let per_type_index = $per_type_index; - let per_type_key = $per_type_key; - let ctx = $ctx; - if (!deny_list.per_type_exists(per_type_index, per_type_key)) { - deny_list.add_per_type_config(per_type_index, per_type_key, ctx); - }; - deny_list.borrow_per_type_config_mut(per_type_index, per_type_key) - } +macro fun per_type_config_entry( + $deny_list: &mut DenyList, + $per_type_index: u64, + $per_type_key: vector, + $ctx: &mut TxContext, +): &mut Config { + let deny_list = $deny_list; + let per_type_index = $per_type_index; + let per_type_key = $per_type_key; + let ctx = $ctx; + if (!deny_list.per_type_exists(per_type_index, per_type_key)) { + deny_list.add_per_type_config(per_type_index, per_type_key, ctx); + }; + deny_list.borrow_per_type_config_mut(per_type_index, per_type_key) +} - // === V1 === - - /// Stores the addresses that are denied for a given core type. - public struct PerTypeList has key, store { - id: UID, - /// Number of object types that have been banned for a given address. - /// Used to quickly skip checks for most addresses. - denied_count: Table, - /// Set of addresses that are banned for a given type. - /// For example with `sui::coin::Coin`: If addresses A and B are banned from using - /// "0...0123::my_coin::MY_COIN", this will be "0...0123::my_coin::MY_COIN" -> {A, B}. - denied_addresses: Table, VecSet
>, - } +// === V1 === + +/// Stores the addresses that are denied for a given core type. +public struct PerTypeList has key, store { + id: UID, + /// Number of object types that have been banned for a given address. + /// Used to quickly skip checks for most addresses. + denied_count: Table, + /// Set of addresses that are banned for a given type. + /// For example with `sui::coin::Coin`: If addresses A and B are banned from using + /// "0...0123::my_coin::MY_COIN", this will be "0...0123::my_coin::MY_COIN" -> {A, B}. + denied_addresses: Table, VecSet
>, +} - /// Adds the given address to the deny list of the specified type, preventing it - /// from interacting with instances of that type as an input to a transaction. For coins, - /// the type specified is the type of the coin, not the coin type itself. For example, - /// "00...0123::my_coin::MY_COIN" would be the type, not "00...02::coin::Coin". - public(package) fun v1_add( - deny_list: &mut DenyList, - per_type_index: u64, - `type`: vector, - addr: address, - ) { - let reserved = RESERVED; - assert!(!reserved.contains(&addr), EInvalidAddress); - let bag_entry: &mut PerTypeList = &mut deny_list.lists[per_type_index]; - bag_entry.v1_per_type_list_add(`type`, addr) - } +/// Adds the given address to the deny list of the specified type, preventing it +/// from interacting with instances of that type as an input to a transaction. For coins, +/// the type specified is the type of the coin, not the coin type itself. For example, +/// "00...0123::my_coin::MY_COIN" would be the type, not "00...02::coin::Coin". +public(package) fun v1_add( + deny_list: &mut DenyList, + per_type_index: u64, + `type`: vector, + addr: address, +) { + let reserved = RESERVED; + assert!(!reserved.contains(&addr), EInvalidAddress); + let bag_entry: &mut PerTypeList = &mut deny_list.lists[per_type_index]; + bag_entry.v1_per_type_list_add(`type`, addr) +} - fun v1_per_type_list_add( - list: &mut PerTypeList, - `type`: vector, - addr: address, - ) { - if (!list.denied_addresses.contains(`type`)) { - list.denied_addresses.add(`type`, vec_set::empty()); - }; - let denied_addresses = &mut list.denied_addresses[`type`]; - let already_denied = denied_addresses.contains(&addr); - if (already_denied) return; - - denied_addresses.insert(addr); - if (!list.denied_count.contains(addr)) { - list.denied_count.add(addr, 0); - }; - let denied_count = &mut list.denied_count[addr]; - *denied_count = *denied_count + 1; - } +fun v1_per_type_list_add(list: &mut PerTypeList, `type`: vector, addr: address) { + if (!list.denied_addresses.contains(`type`)) { + list.denied_addresses.add(`type`, vec_set::empty()); + }; + let denied_addresses = &mut list.denied_addresses[`type`]; + let already_denied = denied_addresses.contains(&addr); + if (already_denied) return; + + denied_addresses.insert(addr); + if (!list.denied_count.contains(addr)) { + list.denied_count.add(addr, 0); + }; + let denied_count = &mut list.denied_count[addr]; + *denied_count = *denied_count + 1; +} - /// Removes a previously denied address from the list. - /// Aborts with `ENotDenied` if the address is not on the list. - public(package) fun v1_remove( - deny_list: &mut DenyList, - per_type_index: u64, - `type`: vector, - addr: address, - ) { - let reserved = RESERVED; - assert!(!reserved.contains(&addr), EInvalidAddress); - let bag_entry: &mut PerTypeList = &mut deny_list.lists[per_type_index]; - bag_entry.v1_per_type_list_remove(`type`, addr) - } +/// Removes a previously denied address from the list. +/// Aborts with `ENotDenied` if the address is not on the list. +public(package) fun v1_remove( + deny_list: &mut DenyList, + per_type_index: u64, + `type`: vector, + addr: address, +) { + let reserved = RESERVED; + assert!(!reserved.contains(&addr), EInvalidAddress); + let bag_entry: &mut PerTypeList = &mut deny_list.lists[per_type_index]; + bag_entry.v1_per_type_list_remove(`type`, addr) +} - fun v1_per_type_list_remove( - list: &mut PerTypeList, - `type`: vector, - addr: address, - ) { - let denied_addresses = &mut list.denied_addresses[`type`]; - assert!(denied_addresses.contains(&addr), ENotDenied); - denied_addresses.remove(&addr); - let denied_count = &mut list.denied_count[addr]; - *denied_count = *denied_count - 1; - if (*denied_count == 0) { - list.denied_count.remove(addr); - } +fun v1_per_type_list_remove(list: &mut PerTypeList, `type`: vector, addr: address) { + let denied_addresses = &mut list.denied_addresses[`type`]; + assert!(denied_addresses.contains(&addr), ENotDenied); + denied_addresses.remove(&addr); + let denied_count = &mut list.denied_count[addr]; + *denied_count = *denied_count - 1; + if (*denied_count == 0) { + list.denied_count.remove(addr); } +} - /// Returns true iff the given address is denied for the given type. - public(package) fun v1_contains( - deny_list: &DenyList, - per_type_index: u64, - `type`: vector, - addr: address, - ): bool { - let reserved = RESERVED; - if (reserved.contains(&addr)) return false; - let bag_entry: &PerTypeList = &deny_list.lists[per_type_index]; - bag_entry.v1_per_type_list_contains(`type`, addr) - } +/// Returns true iff the given address is denied for the given type. +public(package) fun v1_contains( + deny_list: &DenyList, + per_type_index: u64, + `type`: vector, + addr: address, +): bool { + let reserved = RESERVED; + if (reserved.contains(&addr)) return false; + let bag_entry: &PerTypeList = &deny_list.lists[per_type_index]; + bag_entry.v1_per_type_list_contains(`type`, addr) +} - fun v1_per_type_list_contains( - list: &PerTypeList, - `type`: vector, - addr: address, - ): bool { - if (!list.denied_count.contains(addr)) return false; +fun v1_per_type_list_contains(list: &PerTypeList, `type`: vector, addr: address): bool { + if (!list.denied_count.contains(addr)) return false; - let denied_count = &list.denied_count[addr]; - if (*denied_count == 0) return false; + let denied_count = &list.denied_count[addr]; + if (*denied_count == 0) return false; - if (!list.denied_addresses.contains(`type`)) return false; + if (!list.denied_addresses.contains(`type`)) return false; - let denied_addresses = &list.denied_addresses[`type`]; - denied_addresses.contains(&addr) - } + let denied_addresses = &list.denied_addresses[`type`]; + denied_addresses.contains(&addr) +} - #[allow(unused_function)] - /// Creation of the deny list object is restricted to the system address - /// via a system transaction. - fun create(ctx: &mut TxContext) { - assert!(ctx.sender() == @0x0, ENotSystemAddress); - - let mut lists = bag::new(ctx); - lists.add(COIN_INDEX, per_type_list(ctx)); - let deny_list_object = DenyList { - id: object::sui_deny_list_object_id(), - lists, - }; - transfer::share_object(deny_list_object); - } +#[allow(unused_function)] +/// Creation of the deny list object is restricted to the system address +/// via a system transaction. +fun create(ctx: &mut TxContext) { + assert!(ctx.sender() == @0x0, ENotSystemAddress); + + let mut lists = bag::new(ctx); + lists.add(COIN_INDEX, per_type_list(ctx)); + let deny_list_object = DenyList { + id: object::sui_deny_list_object_id(), + lists, + }; + transfer::share_object(deny_list_object); +} - fun per_type_list(ctx: &mut TxContext): PerTypeList { - PerTypeList { - id: object::new(ctx), - denied_count: table::new(ctx), - denied_addresses: table::new(ctx), - } +fun per_type_list(ctx: &mut TxContext): PerTypeList { + PerTypeList { + id: object::new(ctx), + denied_count: table::new(ctx), + denied_addresses: table::new(ctx), } +} - #[test_only] - public fun reserved_addresses(): vector
{ - RESERVED - } +#[test_only] +public fun reserved_addresses(): vector
{ + RESERVED +} - #[test_only] - public fun create_for_test(ctx: &mut TxContext) { - create(ctx); - } +#[test_only] +public fun create_for_test(ctx: &mut TxContext) { + create(ctx); +} - #[test_only] - /// Creates and returns a new DenyList object for testing purposes. It - /// doesn't matter which object ID the list has in this kind of test. - public fun new_for_testing(ctx: &mut TxContext): DenyList { - let mut lists = bag::new(ctx); - lists.add(COIN_INDEX, per_type_list(ctx)); - DenyList { - id: object::new(ctx), - lists, - } +#[test_only] +/// Creates and returns a new DenyList object for testing purposes. It +/// doesn't matter which object ID the list has in this kind of test. +public fun new_for_testing(ctx: &mut TxContext): DenyList { + let mut lists = bag::new(ctx); + lists.add(COIN_INDEX, per_type_list(ctx)); + DenyList { + id: object::new(ctx), + lists, } } diff --git a/crates/sui-framework/packages/sui-framework/sources/display.move b/crates/sui-framework/packages/sui-framework/sources/display.move index ef590aba27b40..b09e4802600d6 100644 --- a/crates/sui-framework/packages/sui-framework/sources/display.move +++ b/crates/sui-framework/packages/sui-framework/sources/display.move @@ -10,218 +10,184 @@ /// substitution and filling-in the pieces using the data from the object T. /// /// More entry functions might be added in the future depending on the use cases. -module sui::display { - use sui::package::Publisher; - use sui::vec_map::{Self, VecMap}; - use sui::event; - use std::string::String; - - /// For when T does not belong to the package `Publisher`. - const ENotOwner: u64 = 0; - - /// For when vectors passed into one of the multiple insert functions - /// don't match in their lengths. - const EVecLengthMismatch: u64 = 1; - - /// The Display object. Defines the way a T instance should be - /// displayed. Display object can only be created and modified with - /// a PublisherCap, making sure that the rules are set by the owner - /// of the type. - /// - /// Each of the display properties should support patterns outside - /// of the system, making it simpler to customize Display based - /// on the property values of an Object. - /// ``` - /// // Example of a display object - /// Display<0x...::capy::Capy> { - /// fields: - /// - /// - /// - /// - /// } - /// ``` - /// - /// Uses only String type due to external-facing nature of the object, - /// the property names have a priority over their types. - public struct Display has key, store { - id: UID, - /// Contains fields for display. Currently supported - /// fields are: name, link, image and description. - fields: VecMap, - /// Version that can only be updated manually by the Publisher. - version: u16 - } - - /// Event: emitted when a new Display object has been created for type T. - /// Type signature of the event corresponds to the type while id serves for - /// the discovery. - /// - /// Since Sui RPC supports querying events by type, finding a Display for the T - /// would be as simple as looking for the first event with `Display`. - public struct DisplayCreated has copy, drop { - id: ID - } - - /// Version of Display got updated - - public struct VersionUpdated has copy, drop { - id: ID, - version: u16, - fields: VecMap, - } +module sui::display; - // === Initializer Methods === +use std::string::String; +use sui::event; +use sui::package::Publisher; +use sui::vec_map::{Self, VecMap}; - /// Create an empty Display object. It can either be shared empty or filled - /// with data right away via cheaper `set_owned` method. - public fun new(pub: &Publisher, ctx: &mut TxContext): Display { - assert!(is_authorized(pub), ENotOwner); - create_internal(ctx) - } - - /// Create a new Display object with a set of fields. - public fun new_with_fields( - pub: &Publisher, fields: vector, values: vector, ctx: &mut TxContext - ): Display { - let len = fields.length(); - assert!(len == values.length(), EVecLengthMismatch); - - let mut i = 0; - let mut display = new(pub, ctx); - while (i < len) { - display.add_internal(fields[i], values[i]); - i = i + 1; - }; - - display - } +/// For when T does not belong to the package `Publisher`. +const ENotOwner: u64 = 0; - // === Entry functions: Create === +/// For when vectors passed into one of the multiple insert functions +/// don't match in their lengths. +const EVecLengthMismatch: u64 = 1; - #[allow(lint(self_transfer))] - /// Create a new empty Display object and keep it. - entry public fun create_and_keep(pub: &Publisher, ctx: &mut TxContext) { - transfer::public_transfer(new(pub, ctx), ctx.sender()) - } +/// The Display object. Defines the way a T instance should be +/// displayed. Display object can only be created and modified with +/// a PublisherCap, making sure that the rules are set by the owner +/// of the type. +/// +/// Each of the display properties should support patterns outside +/// of the system, making it simpler to customize Display based +/// on the property values of an Object. +/// ``` +/// // Example of a display object +/// Display<0x...::capy::Capy> { +/// fields: +/// +/// +/// +/// +/// } +/// ``` +/// +/// Uses only String type due to external-facing nature of the object, +/// the property names have a priority over their types. +public struct Display has key, store { + id: UID, + /// Contains fields for display. Currently supported + /// fields are: name, link, image and description. + fields: VecMap, + /// Version that can only be updated manually by the Publisher. + version: u16, +} - /// Manually bump the version and emit an event with the updated version's contents. - entry public fun update_version( - display: &mut Display - ) { - display.version = display.version + 1; - event::emit(VersionUpdated { - version: display.version, - fields: *&display.fields, - id: display.id.to_inner(), - }) - } +/// Event: emitted when a new Display object has been created for type T. +/// Type signature of the event corresponds to the type while id serves for +/// the discovery. +/// +/// Since Sui RPC supports querying events by type, finding a Display for the T +/// would be as simple as looking for the first event with `Display`. +public struct DisplayCreated has copy, drop { + id: ID, +} - // === Entry functions: Add/Modify fields === +/// Version of Display got updated - +public struct VersionUpdated has copy, drop { + id: ID, + version: u16, + fields: VecMap, +} - /// Sets a custom `name` field with the `value`. - entry public fun add(self: &mut Display, name: String, value: String) { - self.add_internal(name, value) - } +// === Initializer Methods === - /// Sets multiple `fields` with `values`. - entry public fun add_multiple( - self: &mut Display, fields: vector, values: vector - ) { - let len = fields.length(); - assert!(len == values.length(), EVecLengthMismatch); - - let mut i = 0; - while (i < len) { - self.add_internal(fields[i], values[i]); - i = i + 1; - }; - } +/// Create an empty Display object. It can either be shared empty or filled +/// with data right away via cheaper `set_owned` method. +public fun new(pub: &Publisher, ctx: &mut TxContext): Display { + assert!(is_authorized(pub), ENotOwner); + create_internal(ctx) +} - /// Change the value of the field. - /// TODO (long run): version changes; - entry public fun edit(self: &mut Display, name: String, value: String) { - let (_, _) = self.fields.remove(&name); - self.add_internal(name, value) - } +/// Create a new Display object with a set of fields. +public fun new_with_fields( + pub: &Publisher, + fields: vector, + values: vector, + ctx: &mut TxContext, +): Display { + let len = fields.length(); + assert!(len == values.length(), EVecLengthMismatch); + + let mut i = 0; + let mut display = new(pub, ctx); + while (i < len) { + display.add_internal(fields[i], values[i]); + i = i + 1; + }; + + display +} - /// Remove the key from the Display. - entry public fun remove(self: &mut Display, name: String) { - self.fields.remove(&name); - } +// === Entry functions: Create === - // === Access fields === +#[allow(lint(self_transfer))] +/// Create a new empty Display object and keep it. +public entry fun create_and_keep(pub: &Publisher, ctx: &mut TxContext) { + transfer::public_transfer(new(pub, ctx), ctx.sender()) +} - /// Authorization check; can be performed externally to implement protection rules for Display. - public fun is_authorized(pub: &Publisher): bool { - pub.from_package() - } +/// Manually bump the version and emit an event with the updated version's contents. +public entry fun update_version(display: &mut Display) { + display.version = display.version + 1; + event::emit(VersionUpdated { + version: display.version, + fields: *&display.fields, + id: display.id.to_inner(), + }) +} - /// Read the `version` field. - public fun version(d: &Display): u16 { - d.version - } +// === Entry functions: Add/Modify fields === - /// Read the `fields` field. - public fun fields(d: &Display): &VecMap { - &d.fields - } +/// Sets a custom `name` field with the `value`. +public entry fun add(self: &mut Display, name: String, value: String) { + self.add_internal(name, value) +} - // === Private functions === +/// Sets multiple `fields` with `values`. +public entry fun add_multiple( + self: &mut Display, + fields: vector, + values: vector, +) { + let len = fields.length(); + assert!(len == values.length(), EVecLengthMismatch); + + let mut i = 0; + while (i < len) { + self.add_internal(fields[i], values[i]); + i = i + 1; + }; +} - /// Internal function to create a new `Display`. - fun create_internal(ctx: &mut TxContext): Display { - let uid = object::new(ctx); +/// Change the value of the field. +/// TODO (long run): version changes; +public entry fun edit(self: &mut Display, name: String, value: String) { + let (_, _) = self.fields.remove(&name); + self.add_internal(name, value) +} - event::emit(DisplayCreated { - id: uid.to_inner() - }); +/// Remove the key from the Display. +public entry fun remove(self: &mut Display, name: String) { + self.fields.remove(&name); +} - Display { - id: uid, - fields: vec_map::empty(), - version: 0, - } - } +// === Access fields === - /// Private method for inserting fields without security checks. - fun add_internal(display: &mut Display, name: String, value: String) { - display.fields.insert(name, value) - } +/// Authorization check; can be performed externally to implement protection rules for Display. +public fun is_authorized(pub: &Publisher): bool { + pub.from_package() } -#[test_only] -module sui::display_tests { - use sui::test_scenario as test; - use std::string::String; - use sui::package; - use sui::display; - - #[allow(unused_field)] - /// An example object. - /// Purely for visibility. - public struct Capy has key { - id: UID, - name: String - } +/// Read the `version` field. +public fun version(d: &Display): u16 { + d.version +} - /// Test witness type to create a Publisher object. - public struct CAPY has drop {} +/// Read the `fields` field. +public fun fields(d: &Display): &VecMap { + &d.fields +} - #[test] - fun capy_init() { - let mut test = test::begin(@0x2); - let pub = package::test_claim(CAPY {}, test.ctx()); +// === Private functions === - // create a new display object - let mut display = display::new(&pub, test.ctx()); +/// Internal function to create a new `Display`. +fun create_internal(ctx: &mut TxContext): Display { + let uid = object::new(ctx); - display.add(b"name".to_string(), b"Capy {name}".to_string()); - display.add(b"link".to_string(), b"https://capy.art/capy/{id}".to_string()); - display.add(b"image".to_string(), b"https://api.capy.art/capy/{id}/svg".to_string()); - display.add(b"description".to_string(), b"A Lovely Capy".to_string()); + event::emit(DisplayCreated { + id: uid.to_inner(), + }); - pub.burn_publisher(); - transfer::public_transfer(display, @0x2); - test.end(); + Display { + id: uid, + fields: vec_map::empty(), + version: 0, } } + +/// Private method for inserting fields without security checks. +fun add_internal(display: &mut Display, name: String, value: String) { + display.fields.insert(name, value) +} diff --git a/crates/sui-framework/packages/sui-framework/sources/dynamic_field.move b/crates/sui-framework/packages/sui-framework/sources/dynamic_field.move index 712641e20b9a2..8b83d9b192aee 100644 --- a/crates/sui-framework/packages/sui-framework/sources/dynamic_field.move +++ b/crates/sui-framework/packages/sui-framework/sources/dynamic_field.move @@ -8,167 +8,163 @@ /// the `copy`, `drop`, and `store` abilities, e.g. an integer, a boolean, or a string. /// This gives Sui programmers the flexibility to extend objects on-the-fly, and it also serves as a /// building block for core collection types -module sui::dynamic_field { - - /// The object already has a dynamic field with this name (with the value and type specified) - const EFieldAlreadyExists: u64 = 0; - /// Cannot load dynamic field. - /// The object does not have a dynamic field with this name (with the value and type specified) - const EFieldDoesNotExist: u64 = 1; - /// The object has a field with that name, but the value type does not match - const EFieldTypeMismatch: u64 = 2; - /// Failed to serialize the field's name - const EBCSSerializationFailure: u64 = 3; - /// The object added as a dynamic field was previously a shared object - const ESharedObjectOperationNotSupported: u64 = 4; - - /// Internal object used for storing the field and value - public struct Field has key { - /// Determined by the hash of the object ID, the field name value and it's type, - /// i.e. hash(parent.id || name || Name) - id: UID, - /// The value for the name of this field - name: Name, - /// The value bound to this field - value: Value, - } +module sui::dynamic_field; + +/// The object already has a dynamic field with this name (with the value and type specified) +const EFieldAlreadyExists: u64 = 0; +/// Cannot load dynamic field. +/// The object does not have a dynamic field with this name (with the value and type specified) +const EFieldDoesNotExist: u64 = 1; +/// The object has a field with that name, but the value type does not match +const EFieldTypeMismatch: u64 = 2; +/// Failed to serialize the field's name +const EBCSSerializationFailure: u64 = 3; +/// The object added as a dynamic field was previously a shared object +const ESharedObjectOperationNotSupported: u64 = 4; + +/// Internal object used for storing the field and value +public struct Field has key { + /// Determined by the hash of the object ID, the field name value and it's type, + /// i.e. hash(parent.id || name || Name) + id: UID, + /// The value for the name of this field + name: Name, + /// The value bound to this field + value: Value, +} - /// Adds a dynamic field to the object `object: &mut UID` at field specified by `name: Name`. - /// Aborts with `EFieldAlreadyExists` if the object already has that field with that name. - public fun add( - // we use &mut UID in several spots for access control - object: &mut UID, - name: Name, - value: Value, - ) { - let object_addr = object.to_address(); - let hash = hash_type_and_key(object_addr, name); - assert!(!has_child_object(object_addr, hash), EFieldAlreadyExists); - let field = Field { - id: object::new_uid_from_hash(hash), - name, - value, - }; - add_child_object(object_addr, field) - } +/// Adds a dynamic field to the object `object: &mut UID` at field specified by `name: Name`. +/// Aborts with `EFieldAlreadyExists` if the object already has that field with that name. +public fun add( + // we use &mut UID in several spots for access control + object: &mut UID, + name: Name, + value: Value, +) { + let object_addr = object.to_address(); + let hash = hash_type_and_key(object_addr, name); + assert!(!has_child_object(object_addr, hash), EFieldAlreadyExists); + let field = Field { + id: object::new_uid_from_hash(hash), + name, + value, + }; + add_child_object(object_addr, field) +} - /// Immutably borrows the `object`s dynamic field with the name specified by `name: Name`. - /// Aborts with `EFieldDoesNotExist` if the object does not have a field with that name. - /// Aborts with `EFieldTypeMismatch` if the field exists, but the value does not have the specified - /// type. - public fun borrow( - object: &UID, - name: Name, - ): &Value { - let object_addr = object.to_address(); - let hash = hash_type_and_key(object_addr, name); - let field = borrow_child_object>(object, hash); - &field.value - } +/// Immutably borrows the `object`s dynamic field with the name specified by `name: Name`. +/// Aborts with `EFieldDoesNotExist` if the object does not have a field with that name. +/// Aborts with `EFieldTypeMismatch` if the field exists, but the value does not have the specified +/// type. +public fun borrow(object: &UID, name: Name): &Value { + let object_addr = object.to_address(); + let hash = hash_type_and_key(object_addr, name); + let field = borrow_child_object>(object, hash); + &field.value +} - /// Mutably borrows the `object`s dynamic field with the name specified by `name: Name`. - /// Aborts with `EFieldDoesNotExist` if the object does not have a field with that name. - /// Aborts with `EFieldTypeMismatch` if the field exists, but the value does not have the specified - /// type. - public fun borrow_mut( - object: &mut UID, - name: Name, - ): &mut Value { - let object_addr = object.to_address(); - let hash = hash_type_and_key(object_addr, name); - let field = borrow_child_object_mut>(object, hash); - &mut field.value - } +/// Mutably borrows the `object`s dynamic field with the name specified by `name: Name`. +/// Aborts with `EFieldDoesNotExist` if the object does not have a field with that name. +/// Aborts with `EFieldTypeMismatch` if the field exists, but the value does not have the specified +/// type. +public fun borrow_mut( + object: &mut UID, + name: Name, +): &mut Value { + let object_addr = object.to_address(); + let hash = hash_type_and_key(object_addr, name); + let field = borrow_child_object_mut>(object, hash); + &mut field.value +} - /// Removes the `object`s dynamic field with the name specified by `name: Name` and returns the - /// bound value. - /// Aborts with `EFieldDoesNotExist` if the object does not have a field with that name. - /// Aborts with `EFieldTypeMismatch` if the field exists, but the value does not have the specified - /// type. - public fun remove( - object: &mut UID, - name: Name, - ): Value { - let object_addr = object.to_address(); - let hash = hash_type_and_key(object_addr, name); - let Field { id, name: _, value } = remove_child_object>(object_addr, hash); - id.delete(); - value - } +/// Removes the `object`s dynamic field with the name specified by `name: Name` and returns the +/// bound value. +/// Aborts with `EFieldDoesNotExist` if the object does not have a field with that name. +/// Aborts with `EFieldTypeMismatch` if the field exists, but the value does not have the specified +/// type. +public fun remove(object: &mut UID, name: Name): Value { + let object_addr = object.to_address(); + let hash = hash_type_and_key(object_addr, name); + let Field { id, name: _, value } = remove_child_object>(object_addr, hash); + id.delete(); + value +} - /// Returns true if and only if the `object` has a dynamic field with the name specified by - /// `name: Name` but without specifying the `Value` type - public fun exists_( - object: &UID, - name: Name, - ): bool { - let object_addr = object.to_address(); - let hash = hash_type_and_key(object_addr, name); - has_child_object(object_addr, hash) - } +/// Returns true if and only if the `object` has a dynamic field with the name specified by +/// `name: Name` but without specifying the `Value` type +public fun exists_(object: &UID, name: Name): bool { + let object_addr = object.to_address(); + let hash = hash_type_and_key(object_addr, name); + has_child_object(object_addr, hash) +} - /// Removes the dynamic field if it exists. Returns the `some(Value)` if it exists or none otherwise. - public fun remove_if_exists( - object: &mut UID, - name: Name - ): Option { - if (exists_(object, name)) { - option::some(remove(object, name)) - } else { - option::none() - } +/// Removes the dynamic field if it exists. Returns the `some(Value)` if it exists or none otherwise. +public fun remove_if_exists( + object: &mut UID, + name: Name, +): Option { + if (exists_(object, name)) { + option::some(remove(object, name)) + } else { + option::none() } +} - /// Returns true if and only if the `object` has a dynamic field with the name specified by - /// `name: Name` with an assigned value of type `Value`. - public fun exists_with_type( - object: &UID, - name: Name, - ): bool { - let object_addr = object.to_address(); - let hash = hash_type_and_key(object_addr, name); - has_child_object_with_ty>(object_addr, hash) - } +/// Returns true if and only if the `object` has a dynamic field with the name specified by +/// `name: Name` with an assigned value of type `Value`. +public fun exists_with_type( + object: &UID, + name: Name, +): bool { + let object_addr = object.to_address(); + let hash = hash_type_and_key(object_addr, name); + has_child_object_with_ty>(object_addr, hash) +} - public(package) fun field_info( - object: &UID, - name: Name, - ): (&UID, address) { - let object_addr = object.to_address(); - let hash = hash_type_and_key(object_addr, name); - let Field { id, name: _, value } = borrow_child_object>(object, hash); - (id, value.to_address()) - } +public(package) fun field_info( + object: &UID, + name: Name, +): (&UID, address) { + let object_addr = object.to_address(); + let hash = hash_type_and_key(object_addr, name); + let Field { id, name: _, value } = borrow_child_object>(object, hash); + (id, value.to_address()) +} - public(package) fun field_info_mut( - object: &mut UID, - name: Name, - ): (&mut UID, address) { - let object_addr = object.to_address(); - let hash = hash_type_and_key(object_addr, name); - let Field { id, name: _, value } = borrow_child_object_mut>(object, hash); - (id, value.to_address()) - } +public(package) fun field_info_mut( + object: &mut UID, + name: Name, +): (&mut UID, address) { + let object_addr = object.to_address(); + let hash = hash_type_and_key(object_addr, name); + let Field { id, name: _, value } = borrow_child_object_mut>(object, hash); + (id, value.to_address()) +} - /// May abort with `EBCSSerializationFailure`. - public(package) native fun hash_type_and_key(parent: address, k: K): address; +/// May abort with `EBCSSerializationFailure`. +public(package) native fun hash_type_and_key( + parent: address, + k: K, +): address; - public(package) native fun add_child_object(parent: address, child: Child); +public(package) native fun add_child_object(parent: address, child: Child); - /// throws `EFieldDoesNotExist` if a child does not exist with that ID - /// or throws `EFieldTypeMismatch` if the type does not match, - /// and may also abort with `EBCSSerializationFailure` - /// we need two versions to return a reference or a mutable reference - public(package) native fun borrow_child_object(object: &UID, id: address): &Child; +/// throws `EFieldDoesNotExist` if a child does not exist with that ID +/// or throws `EFieldTypeMismatch` if the type does not match, +/// and may also abort with `EBCSSerializationFailure` +/// we need two versions to return a reference or a mutable reference +public(package) native fun borrow_child_object(object: &UID, id: address): &Child; - public(package) native fun borrow_child_object_mut(object: &mut UID, id: address): &mut Child; +public(package) native fun borrow_child_object_mut( + object: &mut UID, + id: address, +): &mut Child; - /// throws `EFieldDoesNotExist` if a child does not exist with that ID - /// or throws `EFieldTypeMismatch` if the type does not match, - /// and may also abort with `EBCSSerializationFailure`. - public(package) native fun remove_child_object(parent: address, id: address): Child; +/// throws `EFieldDoesNotExist` if a child does not exist with that ID +/// or throws `EFieldTypeMismatch` if the type does not match, +/// and may also abort with `EBCSSerializationFailure`. +public(package) native fun remove_child_object(parent: address, id: address): Child; - public(package) native fun has_child_object(parent: address, id: address): bool; +public(package) native fun has_child_object(parent: address, id: address): bool; - public(package) native fun has_child_object_with_ty(parent: address, id: address): bool; -} +public(package) native fun has_child_object_with_ty(parent: address, id: address): bool; diff --git a/crates/sui-framework/packages/sui-framework/sources/dynamic_object_field.move b/crates/sui-framework/packages/sui-framework/sources/dynamic_object_field.move index 56c2c44d25bd4..a23db974b3ef4 100644 --- a/crates/sui-framework/packages/sui-framework/sources/dynamic_object_field.move +++ b/crates/sui-framework/packages/sui-framework/sources/dynamic_object_field.move @@ -5,195 +5,185 @@ /// unlike, `sui::dynamic_field` the values bound to these dynamic fields _must_ be objects /// themselves. This allows for the objects to still exist within in storage, which may be important /// for external tools. The difference is otherwise not observable from within Move. -module sui::dynamic_object_field { - use sui::dynamic_field::{ - Self as field, - add_child_object, - borrow_child_object, - borrow_child_object_mut, - remove_child_object, - }; - - // Internal object used for storing the field and the name associated with the value - // The separate type is necessary to prevent key collision with direct usage of dynamic_field - public struct Wrapper has copy, drop, store { - name: Name, - } - - /// Adds a dynamic object field to the object `object: &mut UID` at field specified by `name: Name`. - /// Aborts with `EFieldAlreadyExists` if the object already has that field with that name. - public fun add( - // we use &mut UID in several spots for access control - object: &mut UID, - name: Name, - value: Value, - ) { - add_impl!(object, name, value) - } - - /// Immutably borrows the `object`s dynamic object field with the name specified by `name: Name`. - /// Aborts with `EFieldDoesNotExist` if the object does not have a field with that name. - /// Aborts with `EFieldTypeMismatch` if the field exists, but the value object does not have the - /// specified type. - public fun borrow( - object: &UID, - name: Name, - ): &Value { - borrow_impl!(object, name) - } - - /// Mutably borrows the `object`s dynamic object field with the name specified by `name: Name`. - /// Aborts with `EFieldDoesNotExist` if the object does not have a field with that name. - /// Aborts with `EFieldTypeMismatch` if the field exists, but the value object does not have the - /// specified type. - public fun borrow_mut( - object: &mut UID, - name: Name, - ): &mut Value { - borrow_mut_impl!(object, name) - } - - /// Removes the `object`s dynamic object field with the name specified by `name: Name` and returns - /// the bound object. - /// Aborts with `EFieldDoesNotExist` if the object does not have a field with that name. - /// Aborts with `EFieldTypeMismatch` if the field exists, but the value object does not have the - /// specified type. - public fun remove( - object: &mut UID, - name: Name, - ): Value { - remove_impl!(object, name) - } - - /// Returns true if and only if the `object` has a dynamic object field with the name specified by - /// `name: Name`. - public fun exists_( - object: &UID, - name: Name, - ): bool { - let key = Wrapper { name }; - field::exists_with_type, ID>(object, key) - } - - /// Returns true if and only if the `object` has a dynamic field with the name specified by - /// `name: Name` with an assigned value of type `Value`. - public fun exists_with_type( - object: &UID, - name: Name, - ): bool { - exists_with_type_impl!<_, Value>(object, name) - } - - /// Returns the ID of the object associated with the dynamic object field - /// Returns none otherwise - public fun id( - object: &UID, - name: Name, - ): Option { - let key = Wrapper { name }; - if (!field::exists_with_type, ID>(object, key)) return option::none(); - let (_field, value_addr) = field::field_info>(object, key); - option::some(value_addr.to_id()) - } - - public(package) fun internal_add( - // we use &mut UID in several spots for access control - object: &mut UID, - name: Name, - value: Value, - ) { - add_impl!(object, name, value) - } - - public(package) fun internal_borrow( - object: &UID, - name: Name, - ): &Value { - borrow_impl!(object, name) - } - - public(package) fun internal_borrow_mut( - object: &mut UID, - name: Name, - ): &mut Value { - borrow_mut_impl!(object, name) - } - - public(package) fun internal_remove( - object: &mut UID, - name: Name, - ): Value { - remove_impl!(object, name) - } - - public(package) fun internal_exists_with_type( - object: &UID, - name: Name, - ): bool { - exists_with_type_impl!<_, Value>(object, name) - } - - macro fun add_impl<$Name: copy + drop + store, $Value: key>( - // we use &mut UID in several spots for access control - $object: &mut UID, - $name: $Name, - $value: $Value, - ) { - let object = $object; - let name = $name; - let value = $value; - let key = Wrapper { name }; - let id = object::id(&value); - field::add(object, key, id); - let (field, _) = field::field_info>(object, key); - add_child_object(field.to_address(), value); - } - - macro fun borrow_impl<$Name: copy + drop + store, $Value: key>( - $object: &UID, - $name: $Name, - ): &$Value { - let object = $object; - let name = $name; - let key = Wrapper { name }; - let (field, value_id) = field::field_info>(object, key); - borrow_child_object<$Value>(field, value_id) - } - - macro fun borrow_mut_impl<$Name: copy + drop + store, $Value: key>( - $object: &mut UID, - $name: $Name, - ): &mut $Value { - let object = $object; - let name = $name; - let key = Wrapper { name }; - let (field, value_id) = field::field_info_mut>(object, key); - borrow_child_object_mut<$Value>(field, value_id) - } - - macro fun remove_impl<$Name: copy + drop + store, $Value: key>( - $object: &mut UID, - $name: $Name, - ): $Value { - let object = $object; - let name = $name; - let key = Wrapper { name }; - let (field, value_id) = field::field_info>(object, key); - let value = remove_child_object<$Value>(field.to_address(), value_id); - field::remove, ID>(object, key); - value - } - - macro fun exists_with_type_impl<$Name: copy + drop + store, $Value: key>( - $object: &UID, - $name: $Name, - ): bool { - let object = $object; - let name = $name; - let key = Wrapper { name }; - if (!field::exists_with_type, ID>(object, key)) return false; - let (field, value_id) = field::field_info>(object, key); - field::has_child_object_with_ty<$Value>(field.to_address(), value_id) - } +module sui::dynamic_object_field; + +use sui::dynamic_field::{ + Self as field, + add_child_object, + borrow_child_object, + borrow_child_object_mut, + remove_child_object +}; + +// Internal object used for storing the field and the name associated with the value +// The separate type is necessary to prevent key collision with direct usage of dynamic_field +public struct Wrapper has copy, drop, store { + name: Name, +} + +/// Adds a dynamic object field to the object `object: &mut UID` at field specified by `name: Name`. +/// Aborts with `EFieldAlreadyExists` if the object already has that field with that name. +public fun add( + // we use &mut UID in several spots for access control + object: &mut UID, + name: Name, + value: Value, +) { + add_impl!(object, name, value) +} + +/// Immutably borrows the `object`s dynamic object field with the name specified by `name: Name`. +/// Aborts with `EFieldDoesNotExist` if the object does not have a field with that name. +/// Aborts with `EFieldTypeMismatch` if the field exists, but the value object does not have the +/// specified type. +public fun borrow(object: &UID, name: Name): &Value { + borrow_impl!(object, name) +} + +/// Mutably borrows the `object`s dynamic object field with the name specified by `name: Name`. +/// Aborts with `EFieldDoesNotExist` if the object does not have a field with that name. +/// Aborts with `EFieldTypeMismatch` if the field exists, but the value object does not have the +/// specified type. +public fun borrow_mut( + object: &mut UID, + name: Name, +): &mut Value { + borrow_mut_impl!(object, name) +} + +/// Removes the `object`s dynamic object field with the name specified by `name: Name` and returns +/// the bound object. +/// Aborts with `EFieldDoesNotExist` if the object does not have a field with that name. +/// Aborts with `EFieldTypeMismatch` if the field exists, but the value object does not have the +/// specified type. +public fun remove( + object: &mut UID, + name: Name, +): Value { + remove_impl!(object, name) +} + +/// Returns true if and only if the `object` has a dynamic object field with the name specified by +/// `name: Name`. +public fun exists_(object: &UID, name: Name): bool { + let key = Wrapper { name }; + field::exists_with_type, ID>(object, key) +} + +/// Returns true if and only if the `object` has a dynamic field with the name specified by +/// `name: Name` with an assigned value of type `Value`. +public fun exists_with_type( + object: &UID, + name: Name, +): bool { + exists_with_type_impl!<_, Value>(object, name) +} + +/// Returns the ID of the object associated with the dynamic object field +/// Returns none otherwise +public fun id(object: &UID, name: Name): Option { + let key = Wrapper { name }; + if (!field::exists_with_type, ID>(object, key)) return option::none(); + let (_field, value_addr) = field::field_info>(object, key); + option::some(value_addr.to_id()) +} + +public(package) fun internal_add( + // we use &mut UID in several spots for access control + object: &mut UID, + name: Name, + value: Value, +) { + add_impl!(object, name, value) +} + +public(package) fun internal_borrow( + object: &UID, + name: Name, +): &Value { + borrow_impl!(object, name) +} + +public(package) fun internal_borrow_mut( + object: &mut UID, + name: Name, +): &mut Value { + borrow_mut_impl!(object, name) +} + +public(package) fun internal_remove( + object: &mut UID, + name: Name, +): Value { + remove_impl!(object, name) +} + +public(package) fun internal_exists_with_type( + object: &UID, + name: Name, +): bool { + exists_with_type_impl!<_, Value>(object, name) +} + +macro fun add_impl<$Name: copy + drop + store, $Value: key>( + // we use &mut UID in several spots for access control + $object: &mut UID, + $name: $Name, + $value: $Value, +) { + let object = $object; + let name = $name; + let value = $value; + let key = Wrapper { name }; + let id = object::id(&value); + field::add(object, key, id); + let (field, _) = field::field_info>(object, key); + add_child_object(field.to_address(), value); +} + +macro fun borrow_impl<$Name: copy + drop + store, $Value: key>( + $object: &UID, + $name: $Name, +): &$Value { + let object = $object; + let name = $name; + let key = Wrapper { name }; + let (field, value_id) = field::field_info>(object, key); + borrow_child_object<$Value>(field, value_id) +} + +macro fun borrow_mut_impl<$Name: copy + drop + store, $Value: key>( + $object: &mut UID, + $name: $Name, +): &mut $Value { + let object = $object; + let name = $name; + let key = Wrapper { name }; + let (field, value_id) = field::field_info_mut>(object, key); + borrow_child_object_mut<$Value>(field, value_id) +} + +macro fun remove_impl<$Name: copy + drop + store, $Value: key>( + $object: &mut UID, + $name: $Name, +): $Value { + let object = $object; + let name = $name; + let key = Wrapper { name }; + let (field, value_id) = field::field_info>(object, key); + let value = remove_child_object<$Value>(field.to_address(), value_id); + field::remove, ID>(object, key); + value +} +macro fun exists_with_type_impl<$Name: copy + drop + store, $Value: key>( + $object: &UID, + $name: $Name, +): bool { + let object = $object; + let name = $name; + let key = Wrapper { name }; + if (!field::exists_with_type, ID>(object, key)) return false; + let (field, value_id) = field::field_info>(object, key); + field::has_child_object_with_ty<$Value>(field.to_address(), value_id) } diff --git a/crates/sui-framework/packages/sui-framework/sources/event.move b/crates/sui-framework/packages/sui-framework/sources/event.move index f708ac46285c5..7aea21d5c2108 100644 --- a/crates/sui-framework/packages/sui-framework/sources/event.move +++ b/crates/sui-framework/packages/sui-framework/sources/event.move @@ -26,22 +26,22 @@ /// } /// } /// ``` -module sui::event { - /// Emit a custom Move event, sending the data offchain. - /// - /// Used for creating custom indexes and tracking onchain - /// activity in a way that suits a specific application the most. - /// - /// The type `T` is the main way to index the event, and can contain - /// phantom parameters, eg `emit(MyEvent)`. - public native fun emit(event: T); +module sui::event; - #[test_only] - /// Get the total number of events emitted during execution so far - public native fun num_events(): u32; +/// Emit a custom Move event, sending the data offchain. +/// +/// Used for creating custom indexes and tracking onchain +/// activity in a way that suits a specific application the most. +/// +/// The type `T` is the main way to index the event, and can contain +/// phantom parameters, eg `emit(MyEvent)`. +public native fun emit(event: T); + +#[test_only] +/// Get the total number of events emitted during execution so far +public native fun num_events(): u32; - #[test_only] - /// Get all events of type `T` emitted during execution. - /// Can only be used in testing, - public native fun events_by_type(): vector; -} +#[test_only] +/// Get all events of type `T` emitted during execution. +/// Can only be used in testing, +public native fun events_by_type(): vector; diff --git a/crates/sui-framework/packages/sui-framework/sources/hex.move b/crates/sui-framework/packages/sui-framework/sources/hex.move index 640e4338d9a62..5776d3e912076 100644 --- a/crates/sui-framework/packages/sui-framework/sources/hex.move +++ b/crates/sui-framework/packages/sui-framework/sources/hex.move @@ -2,104 +2,68 @@ // SPDX-License-Identifier: Apache-2.0 /// HEX (Base16) encoding utility. -module sui::hex { +module sui::hex; - const EInvalidHexLength: u64 = 0; - const ENotValidHexCharacter: u64 = 1; +const EInvalidHexLength: u64 = 0; +const ENotValidHexCharacter: u64 = 1; - /// Vector of Base16 values from `00` to `FF` - const HEX: vector> = vector[ - b"00",b"01",b"02",b"03",b"04",b"05",b"06",b"07",b"08",b"09",b"0a",b"0b",b"0c",b"0d",b"0e",b"0f",b"10",b"11",b"12",b"13",b"14",b"15",b"16",b"17",b"18",b"19",b"1a",b"1b",b"1c",b"1d",b"1e",b"1f",b"20",b"21",b"22",b"23",b"24",b"25",b"26",b"27",b"28",b"29",b"2a",b"2b",b"2c",b"2d",b"2e",b"2f",b"30",b"31",b"32",b"33",b"34",b"35",b"36",b"37",b"38",b"39",b"3a",b"3b",b"3c",b"3d",b"3e",b"3f",b"40",b"41",b"42",b"43",b"44",b"45",b"46",b"47",b"48",b"49",b"4a",b"4b",b"4c",b"4d",b"4e",b"4f",b"50",b"51",b"52",b"53",b"54",b"55",b"56",b"57",b"58",b"59",b"5a",b"5b",b"5c",b"5d",b"5e",b"5f",b"60",b"61",b"62",b"63",b"64",b"65",b"66",b"67",b"68",b"69",b"6a",b"6b",b"6c",b"6d",b"6e",b"6f",b"70",b"71",b"72",b"73",b"74",b"75",b"76",b"77",b"78",b"79",b"7a",b"7b",b"7c",b"7d",b"7e",b"7f",b"80",b"81",b"82",b"83",b"84",b"85",b"86",b"87",b"88",b"89",b"8a",b"8b",b"8c",b"8d",b"8e",b"8f",b"90",b"91",b"92",b"93",b"94",b"95",b"96",b"97",b"98",b"99",b"9a",b"9b",b"9c",b"9d",b"9e",b"9f",b"a0",b"a1",b"a2",b"a3",b"a4",b"a5",b"a6",b"a7",b"a8",b"a9",b"aa",b"ab",b"ac",b"ad",b"ae",b"af",b"b0",b"b1",b"b2",b"b3",b"b4",b"b5",b"b6",b"b7",b"b8",b"b9",b"ba",b"bb",b"bc",b"bd",b"be",b"bf",b"c0",b"c1",b"c2",b"c3",b"c4",b"c5",b"c6",b"c7",b"c8",b"c9",b"ca",b"cb",b"cc",b"cd",b"ce",b"cf",b"d0",b"d1",b"d2",b"d3",b"d4",b"d5",b"d6",b"d7",b"d8",b"d9",b"da",b"db",b"dc",b"dd",b"de",b"df",b"e0",b"e1",b"e2",b"e3",b"e4",b"e5",b"e6",b"e7",b"e8",b"e9",b"ea",b"eb",b"ec",b"ed",b"ee",b"ef",b"f0",b"f1",b"f2",b"f3",b"f4",b"f5",b"f6",b"f7",b"f8",b"f9",b"fa",b"fb",b"fc",b"fd",b"fe",b"ff" - ]; +// prettier-ignore +/// Vector of Base16 values from `00` to `FF` +const HEX: vector> = vector[ + b"00", b"01", b"02", b"03", b"04", b"05", b"06", b"07", b"08", b"09", b"0a", b"0b", b"0c", b"0d", b"0e", b"0f", + b"10", b"11", b"12", b"13", b"14", b"15", b"16", b"17", b"18", b"19", b"1a", b"1b", b"1c", b"1d", b"1e", b"1f", + b"20", b"21", b"22", b"23", b"24", b"25", b"26", b"27", b"28", b"29", b"2a", b"2b", b"2c", b"2d", b"2e", b"2f", + b"30", b"31", b"32", b"33", b"34", b"35", b"36", b"37", b"38", b"39", b"3a", b"3b", b"3c", b"3d", b"3e", b"3f", + b"40", b"41", b"42", b"43", b"44", b"45", b"46", b"47", b"48", b"49", b"4a", b"4b", b"4c", b"4d", b"4e", b"4f", + b"50", b"51", b"52", b"53", b"54", b"55", b"56", b"57", b"58", b"59", b"5a", b"5b", b"5c", b"5d", b"5e", b"5f", + b"60", b"61", b"62", b"63", b"64", b"65", b"66", b"67", b"68", b"69", b"6a", b"6b", b"6c", b"6d", b"6e", b"6f", + b"70", b"71", b"72", b"73", b"74", b"75", b"76", b"77", b"78", b"79", b"7a", b"7b", b"7c", b"7d", b"7e", b"7f", + b"80", b"81", b"82", b"83", b"84", b"85", b"86", b"87", b"88", b"89", b"8a", b"8b", b"8c", b"8d", b"8e", b"8f", + b"90", b"91", b"92", b"93", b"94", b"95", b"96", b"97", b"98", b"99", b"9a", b"9b", b"9c", b"9d", b"9e", b"9f", + b"a0", b"a1", b"a2", b"a3", b"a4", b"a5", b"a6", b"a7", b"a8", b"a9", b"aa", b"ab", b"ac", b"ad", b"ae", b"af", + b"b0", b"b1", b"b2", b"b3", b"b4", b"b5", b"b6", b"b7", b"b8", b"b9", b"ba", b"bb", b"bc", b"bd", b"be", b"bf", + b"c0", b"c1", b"c2", b"c3", b"c4", b"c5", b"c6", b"c7", b"c8", b"c9", b"ca", b"cb", b"cc", b"cd", b"ce", b"cf", + b"d0", b"d1", b"d2", b"d3", b"d4", b"d5", b"d6", b"d7", b"d8", b"d9", b"da", b"db", b"dc", b"dd", b"de", b"df", + b"e0", b"e1", b"e2", b"e3", b"e4", b"e5", b"e6", b"e7", b"e8", b"e9", b"ea", b"eb", b"ec", b"ed", b"ee", b"ef", + b"f0", b"f1", b"f2", b"f3", b"f4", b"f5", b"f6", b"f7", b"f8", b"f9", b"fa", b"fb", b"fc", b"fd", b"fe", b"ff", +]; - /// Encode `bytes` in lowercase hex - public fun encode(bytes: vector): vector { - let (mut i, mut r, l) = (0, vector[], bytes.length()); - let hex_vector = HEX; - while (i < l) { - r.append(hex_vector[bytes[i] as u64]); - i = i + 1; - }; - r - } - - /// Decode hex into `bytes` - /// Takes a hex string (no 0x prefix) (e.g. b"0f3a") - /// Returns vector of `bytes` that represents the hex string (e.g. x"0f3a") - /// Hex string can be case insensitive (e.g. b"0F3A" and b"0f3a" both return x"0f3a") - /// Aborts if the hex string does not have an even number of characters (as each hex character is 2 characters long) - /// Aborts if the hex string contains non-valid hex characters (valid characters are 0 - 9, a - f, A - F) - public fun decode(hex: vector): vector { - let (mut i, mut r, l) = (0, vector[], hex.length()); - assert!(l % 2 == 0, EInvalidHexLength); - while (i < l) { - let decimal = decode_byte(hex[i]) * 16 + decode_byte(hex[i + 1]); - r.push_back(decimal); - i = i + 2; - }; - r - } - - fun decode_byte(hex: u8): u8 { - if (/* 0 .. 9 */ 48 <= hex && hex < 58) { - hex - 48 - } else if (/* A .. F */ 65 <= hex && hex < 71) { - 10 + hex - 65 - } else if (/* a .. f */ 97 <= hex && hex < 103) { - 10 + hex - 97 - } else { - abort ENotValidHexCharacter - } - } - - #[test] - fun test_hex_encode_string_literal() { - assert!(b"30" == encode(b"0")); - assert!(b"61" == encode(b"a")); - assert!(b"666666" == encode(b"fff")); - } - - #[test] - fun test_hex_encode_hex_literal() { - assert!(b"ff" == encode(x"ff")); - assert!(b"fe" == encode(x"fe")); - assert!(b"00" == encode(x"00")); - } - - #[test] - fun test_hex_decode_string_literal() { - assert!(x"ff" == decode(b"ff")); - assert!(x"fe" == decode(b"fe")); - assert!(x"00" == decode(b"00")); - } - - #[test] - fun test_hex_decode_string_literal__lowercase_and_uppercase() { - assert!(x"ff" == decode(b"Ff")); - assert!(x"ff" == decode(b"fF")); - assert!(x"ff" == decode(b"FF")); - } - - #[test] - fun test_hex_decode_string_literal__long_hex() { - assert!(x"036d2416252ae1db8aedad59e14b007bee6ab94a3e77a3549a81137871604456f3" == decode(b"036d2416252ae1Db8aedAd59e14b007bee6aB94a3e77a3549a81137871604456f3")); - } - - #[test] - #[expected_failure(abort_code = EInvalidHexLength)] - fun test_hex_decode__invalid_length() { - decode(b"0"); - } +/// Encode `bytes` in lowercase hex +public fun encode(bytes: vector): vector { + let (mut i, mut r, l) = (0, vector[], bytes.length()); + let hex_vector = HEX; + while (i < l) { + r.append(hex_vector[bytes[i] as u64]); + i = i + 1; + }; + r +} - #[test] - #[expected_failure(abort_code = ENotValidHexCharacter)] - fun test_hex_decode__hex_literal() { - decode(x"ffff"); - } +/// Decode hex into `bytes` +/// Takes a hex string (no 0x prefix) (e.g. b"0f3a") +/// Returns vector of `bytes` that represents the hex string (e.g. x"0f3a") +/// Hex string can be case insensitive (e.g. b"0F3A" and b"0f3a" both return x"0f3a") +/// Aborts if the hex string does not have an even number of characters (as each hex character is 2 characters long) +/// Aborts if the hex string contains non-valid hex characters (valid characters are 0 - 9, a - f, A - F) +public fun decode(hex: vector): vector { + let (mut i, mut r, l) = (0, vector[], hex.length()); + assert!(l % 2 == 0, EInvalidHexLength); + while (i < l) { + let decimal = decode_byte(hex[i]) * 16 + decode_byte(hex[i + 1]); + r.push_back(decimal); + i = i + 2; + }; + r +} - #[test] - #[expected_failure(abort_code = ENotValidHexCharacter)] - fun test_hex_decode__invalid_string_literal() { - decode(b"0g"); +fun decode_byte(hex: u8): u8 { + if (48 <= hex && hex < 58) { + hex - 48 + } else if (65 <= hex && hex < 71) { + 10 + hex - 65 + } else if (97 <= hex && hex < 103) { + 10 + hex - 97 + } else { + abort ENotValidHexCharacter } } diff --git a/crates/sui-framework/packages/sui-framework/sources/kiosk/kiosk.move b/crates/sui-framework/packages/sui-framework/sources/kiosk/kiosk.move index 0da0136ce0537..6d26ffcb6e3e9 100644 --- a/crates/sui-framework/packages/sui-framework/sources/kiosk/kiosk.move +++ b/crates/sui-framework/packages/sui-framework/sources/kiosk/kiosk.move @@ -80,580 +80,562 @@ /// ``` /// /// See `transfer_policy` module for more details on how they function. -module sui::kiosk { - use sui::dynamic_object_field as dof; - use sui::dynamic_field as df; - use sui::transfer_policy::{ - Self, - TransferPolicy, - TransferRequest - }; - use sui::balance::{Self, Balance}; - use sui::coin::{Self, Coin}; - use sui::sui::SUI; - use sui::event; - - /// Allows calling `cap.kiosk()` to retrieve `for` field from `KioskOwnerCap`. - public use fun kiosk_owner_cap_for as KioskOwnerCap.kiosk; - - // Gets access to: - // - `place_internal` - // - `lock_internal` - // - `uid_mut_internal` - - /// Trying to withdraw profits and sender is not owner. - const ENotOwner: u64 = 0; - /// Coin paid does not match the offer price. - const EIncorrectAmount: u64 = 1; - /// Trying to withdraw higher amount than stored. - const ENotEnough: u64 = 2; - /// Trying to close a Kiosk and it has items in it. - const ENotEmpty: u64 = 3; - /// Attempt to take an item that has a `PurchaseCap` issued. - const EListedExclusively: u64 = 4; - /// `PurchaseCap` does not match the `Kiosk`. - const EWrongKiosk: u64 = 5; - /// Trying to exclusively list an already listed item. - const EAlreadyListed: u64 = 6; - /// Trying to call `uid_mut` when `allow_extensions` set to false. - const EUidAccessNotAllowed: u64 = 7; - /// Attempt to `take` an item that is locked. - const EItemLocked: u64 = 8; - /// Taking or mutably borrowing an item that is listed. - const EItemIsListed: u64 = 9; - /// Item does not match `Borrow` in `return_val`. - const EItemMismatch: u64 = 10; - /// An is not found while trying to borrow. - const EItemNotFound: u64 = 11; - /// Delisting an item that is not listed. - const ENotListed: u64 = 12; - - /// An object which allows selling collectibles within "kiosk" ecosystem. - /// By default gives the functionality to list an item openly - for anyone - /// to purchase providing the guarantees for creators that every transfer - /// needs to be approved via the `TransferPolicy`. - public struct Kiosk has key, store { - id: UID, - /// Balance of the Kiosk - all profits from sales go here. - profits: Balance, - /// Always point to `sender` of the transaction. - /// Can be changed by calling `set_owner` with Cap. - owner: address, - /// Number of items stored in a Kiosk. Used to allow unpacking - /// an empty Kiosk if it was wrapped or has a single owner. - item_count: u32, - /// [DEPRECATED] Please, don't use the `allow_extensions` and the matching - /// `set_allow_extensions` function - it is a legacy feature that is being - /// replaced by the `kiosk_extension` module and its Extensions API. - /// - /// Exposes `uid_mut` publicly when set to `true`, set to `false` by default. - allow_extensions: bool - } - - /// A Capability granting the bearer a right to `place` and `take` items - /// from the `Kiosk` as well as to `list` them and `list_with_purchase_cap`. - public struct KioskOwnerCap has key, store { - id: UID, - `for`: ID - } - - /// A capability which locks an item and gives a permission to - /// purchase it from a `Kiosk` for any price no less than `min_price`. - /// - /// Allows exclusive listing: only bearer of the `PurchaseCap` can - /// purchase the asset. However, the capability should be used - /// carefully as losing it would lock the asset in the `Kiosk`. +module sui::kiosk; + +use sui::balance::{Self, Balance}; +use sui::coin::{Self, Coin}; +use sui::dynamic_field as df; +use sui::dynamic_object_field as dof; +use sui::event; +use sui::sui::SUI; +use sui::transfer_policy::{Self, TransferPolicy, TransferRequest}; + +/// Allows calling `cap.kiosk()` to retrieve `for` field from `KioskOwnerCap`. +public use fun kiosk_owner_cap_for as KioskOwnerCap.kiosk; + +// Gets access to: +// - `place_internal` +// - `lock_internal` +// - `uid_mut_internal` + +/// Trying to withdraw profits and sender is not owner. +const ENotOwner: u64 = 0; +/// Coin paid does not match the offer price. +const EIncorrectAmount: u64 = 1; +/// Trying to withdraw higher amount than stored. +const ENotEnough: u64 = 2; +/// Trying to close a Kiosk and it has items in it. +const ENotEmpty: u64 = 3; +/// Attempt to take an item that has a `PurchaseCap` issued. +const EListedExclusively: u64 = 4; +/// `PurchaseCap` does not match the `Kiosk`. +const EWrongKiosk: u64 = 5; +/// Trying to exclusively list an already listed item. +const EAlreadyListed: u64 = 6; +/// Trying to call `uid_mut` when `allow_extensions` set to false. +const EUidAccessNotAllowed: u64 = 7; +/// Attempt to `take` an item that is locked. +const EItemLocked: u64 = 8; +/// Taking or mutably borrowing an item that is listed. +const EItemIsListed: u64 = 9; +/// Item does not match `Borrow` in `return_val`. +const EItemMismatch: u64 = 10; +/// An is not found while trying to borrow. +const EItemNotFound: u64 = 11; +/// Delisting an item that is not listed. +const ENotListed: u64 = 12; + +/// An object which allows selling collectibles within "kiosk" ecosystem. +/// By default gives the functionality to list an item openly - for anyone +/// to purchase providing the guarantees for creators that every transfer +/// needs to be approved via the `TransferPolicy`. +public struct Kiosk has key, store { + id: UID, + /// Balance of the Kiosk - all profits from sales go here. + profits: Balance, + /// Always point to `sender` of the transaction. + /// Can be changed by calling `set_owner` with Cap. + owner: address, + /// Number of items stored in a Kiosk. Used to allow unpacking + /// an empty Kiosk if it was wrapped or has a single owner. + item_count: u32, + /// [DEPRECATED] Please, don't use the `allow_extensions` and the matching + /// `set_allow_extensions` function - it is a legacy feature that is being + /// replaced by the `kiosk_extension` module and its Extensions API. /// - /// The main application for the `PurchaseCap` is building extensions - /// on top of the `Kiosk`. - public struct PurchaseCap has key, store { - id: UID, - /// ID of the `Kiosk` the cap belongs to. - kiosk_id: ID, - /// ID of the listed item. - item_id: ID, - /// Minimum price for which the item can be purchased. - min_price: u64 - } + /// Exposes `uid_mut` publicly when set to `true`, set to `false` by default. + allow_extensions: bool, +} - // === Utilities === +/// A Capability granting the bearer a right to `place` and `take` items +/// from the `Kiosk` as well as to `list` them and `list_with_purchase_cap`. +public struct KioskOwnerCap has key, store { + id: UID, + `for`: ID, +} - /// Hot potato to ensure an item was returned after being taken using - /// the `borrow_val` call. - public struct Borrow { kiosk_id: ID, item_id: ID } +/// A capability which locks an item and gives a permission to +/// purchase it from a `Kiosk` for any price no less than `min_price`. +/// +/// Allows exclusive listing: only bearer of the `PurchaseCap` can +/// purchase the asset. However, the capability should be used +/// carefully as losing it would lock the asset in the `Kiosk`. +/// +/// The main application for the `PurchaseCap` is building extensions +/// on top of the `Kiosk`. +public struct PurchaseCap has key, store { + id: UID, + /// ID of the `Kiosk` the cap belongs to. + kiosk_id: ID, + /// ID of the listed item. + item_id: ID, + /// Minimum price for which the item can be purchased. + min_price: u64, +} - // === Dynamic Field keys === +// === Utilities === - /// Dynamic field key for an item placed into the kiosk. - public struct Item has store, copy, drop { id: ID } +/// Hot potato to ensure an item was returned after being taken using +/// the `borrow_val` call. +public struct Borrow { kiosk_id: ID, item_id: ID } - /// Dynamic field key for an active offer to purchase the T. If an - /// item is listed without a `PurchaseCap`, exclusive is set to `false`. - public struct Listing has store, copy, drop { id: ID, is_exclusive: bool } +// === Dynamic Field keys === - /// Dynamic field key which marks that an item is locked in the `Kiosk` and - /// can't be `take`n. The item then can only be listed / sold via the PurchaseCap. - /// Lock is released on `purchase`. - public struct Lock has store, copy, drop { id: ID } +/// Dynamic field key for an item placed into the kiosk. +public struct Item has store, copy, drop { id: ID } - // === Events === +/// Dynamic field key for an active offer to purchase the T. If an +/// item is listed without a `PurchaseCap`, exclusive is set to `false`. +public struct Listing has store, copy, drop { id: ID, is_exclusive: bool } - /// Emitted when an item was listed by the safe owner. Can be used - /// to track available offers anywhere on the network; the event is - /// type-indexed which allows for searching for offers of a specific `T` - public struct ItemListed has copy, drop { - kiosk: ID, - id: ID, - price: u64 - } +/// Dynamic field key which marks that an item is locked in the `Kiosk` and +/// can't be `take`n. The item then can only be listed / sold via the PurchaseCap. +/// Lock is released on `purchase`. +public struct Lock has store, copy, drop { id: ID } - /// Emitted when an item was purchased from the `Kiosk`. Can be used - /// to track finalized sales across the network. The event is emitted - /// in both cases: when an item is purchased via the `PurchaseCap` or - /// when it's purchased directly (via `list` + `purchase`). - /// - /// The `price` is also emitted and might differ from the `price` set - /// in the `ItemListed` event. This is because the `PurchaseCap` only - /// sets a minimum price for the item, and the actual price is defined - /// by the trading module / extension. - public struct ItemPurchased has copy, drop { - kiosk: ID, - id: ID, - price: u64 - } +// === Events === - /// Emitted when an item was delisted by the safe owner. Can be used - /// to close tracked offers. - public struct ItemDelisted has copy, drop { - kiosk: ID, - id: ID - } +/// Emitted when an item was listed by the safe owner. Can be used +/// to track available offers anywhere on the network; the event is +/// type-indexed which allows for searching for offers of a specific `T` +public struct ItemListed has copy, drop { + kiosk: ID, + id: ID, + price: u64, +} - // === Kiosk packing and unpacking === +/// Emitted when an item was purchased from the `Kiosk`. Can be used +/// to track finalized sales across the network. The event is emitted +/// in both cases: when an item is purchased via the `PurchaseCap` or +/// when it's purchased directly (via `list` + `purchase`). +/// +/// The `price` is also emitted and might differ from the `price` set +/// in the `ItemListed` event. This is because the `PurchaseCap` only +/// sets a minimum price for the item, and the actual price is defined +/// by the trading module / extension. +public struct ItemPurchased has copy, drop { + kiosk: ID, + id: ID, + price: u64, +} - #[allow(lint(self_transfer))] - /// Creates a new Kiosk in a default configuration: sender receives the - /// `KioskOwnerCap` and becomes the Owner, the `Kiosk` is shared. - entry fun default(ctx: &mut TxContext) { - let (kiosk, cap) = new(ctx); - sui::transfer::transfer(cap, ctx.sender()); - sui::transfer::share_object(kiosk); - } +/// Emitted when an item was delisted by the safe owner. Can be used +/// to close tracked offers. +public struct ItemDelisted has copy, drop { + kiosk: ID, + id: ID, +} - /// Creates a new `Kiosk` with a matching `KioskOwnerCap`. - public fun new(ctx: &mut TxContext): (Kiosk, KioskOwnerCap) { - let kiosk = Kiosk { - id: object::new(ctx), - profits: balance::zero(), - owner: ctx.sender(), - item_count: 0, - allow_extensions: false - }; - - let cap = KioskOwnerCap { - id: object::new(ctx), - `for`: object::id(&kiosk) - }; - - (kiosk, cap) - } +// === Kiosk packing and unpacking === - /// Unpacks and destroys a Kiosk returning the profits (even if "0"). - /// Can only be performed by the bearer of the `KioskOwnerCap` in the - /// case where there's no items inside and a `Kiosk` is not shared. - public fun close_and_withdraw( - self: Kiosk, cap: KioskOwnerCap, ctx: &mut TxContext - ): Coin { - let Kiosk { id, profits, owner: _, item_count, allow_extensions: _ } = self; - let KioskOwnerCap { id: cap_id, `for` } = cap; +#[allow(lint(self_transfer))] +/// Creates a new Kiosk in a default configuration: sender receives the +/// `KioskOwnerCap` and becomes the Owner, the `Kiosk` is shared. +entry fun default(ctx: &mut TxContext) { + let (kiosk, cap) = new(ctx); + sui::transfer::transfer(cap, ctx.sender()); + sui::transfer::share_object(kiosk); +} - assert!(id.to_inner() == `for`, ENotOwner); - assert!(item_count == 0, ENotEmpty); +/// Creates a new `Kiosk` with a matching `KioskOwnerCap`. +public fun new(ctx: &mut TxContext): (Kiosk, KioskOwnerCap) { + let kiosk = Kiosk { + id: object::new(ctx), + profits: balance::zero(), + owner: ctx.sender(), + item_count: 0, + allow_extensions: false, + }; - cap_id.delete(); - id.delete(); + let cap = KioskOwnerCap { + id: object::new(ctx), + `for`: object::id(&kiosk), + }; - profits.into_coin(ctx) - } + (kiosk, cap) +} - /// Change the `owner` field to the transaction sender. - /// The change is purely cosmetical and does not affect any of the - /// basic kiosk functions unless some logic for this is implemented - /// in a third party module. - public fun set_owner( - self: &mut Kiosk, cap: &KioskOwnerCap, ctx: &TxContext - ) { - assert!(self.has_access(cap), ENotOwner); - self.owner = ctx.sender(); - } +/// Unpacks and destroys a Kiosk returning the profits (even if "0"). +/// Can only be performed by the bearer of the `KioskOwnerCap` in the +/// case where there's no items inside and a `Kiosk` is not shared. +public fun close_and_withdraw(self: Kiosk, cap: KioskOwnerCap, ctx: &mut TxContext): Coin { + let Kiosk { id, profits, owner: _, item_count, allow_extensions: _ } = self; + let KioskOwnerCap { id: cap_id, `for` } = cap; - /// Update the `owner` field with a custom address. Can be used for - /// implementing a custom logic that relies on the `Kiosk` owner. - public fun set_owner_custom( - self: &mut Kiosk, cap: &KioskOwnerCap, owner: address - ) { - assert!(self.has_access(cap), ENotOwner); - self.owner = owner - } + assert!(id.to_inner() == `for`, ENotOwner); + assert!(item_count == 0, ENotEmpty); - // === Place, Lock and Take from the Kiosk === + cap_id.delete(); + id.delete(); - /// Place any object into a Kiosk. - /// Performs an authorization check to make sure only owner can do that. - public fun place( - self: &mut Kiosk, cap: &KioskOwnerCap, item: T - ) { - assert!(self.has_access(cap), ENotOwner); - self.place_internal(item) - } + profits.into_coin(ctx) +} - /// Place an item to the `Kiosk` and issue a `Lock` for it. Once placed this - /// way, an item can only be listed either with a `list` function or with a - /// `list_with_purchase_cap`. - /// - /// Requires policy for `T` to make sure that there's an issued `TransferPolicy` - /// and the item can be sold, otherwise the asset might be locked forever. - public fun lock( - self: &mut Kiosk, cap: &KioskOwnerCap, _policy: &TransferPolicy, item: T - ) { - assert!(self.has_access(cap), ENotOwner); - self.lock_internal(item) - } +/// Change the `owner` field to the transaction sender. +/// The change is purely cosmetical and does not affect any of the +/// basic kiosk functions unless some logic for this is implemented +/// in a third party module. +public fun set_owner(self: &mut Kiosk, cap: &KioskOwnerCap, ctx: &TxContext) { + assert!(self.has_access(cap), ENotOwner); + self.owner = ctx.sender(); +} - /// Take any object from the Kiosk. - /// Performs an authorization check to make sure only owner can do that. - public fun take( - self: &mut Kiosk, cap: &KioskOwnerCap, id: ID - ): T { - assert!(self.has_access(cap), ENotOwner); - assert!(!self.is_locked(id), EItemLocked); - assert!(!self.is_listed_exclusively(id), EListedExclusively); - assert!(self.has_item(id), EItemNotFound); - - self.item_count = self.item_count - 1; - df::remove_if_exists(&mut self.id, Listing { id, is_exclusive: false }); - dof::remove(&mut self.id, Item { id }) - } +/// Update the `owner` field with a custom address. Can be used for +/// implementing a custom logic that relies on the `Kiosk` owner. +public fun set_owner_custom(self: &mut Kiosk, cap: &KioskOwnerCap, owner: address) { + assert!(self.has_access(cap), ENotOwner); + self.owner = owner +} - // === Trading functionality: List and Purchase === +// === Place, Lock and Take from the Kiosk === - /// List the item by setting a price and making it available for purchase. - /// Performs an authorization check to make sure only owner can sell. - public fun list( - self: &mut Kiosk, cap: &KioskOwnerCap, id: ID, price: u64 - ) { - assert!(self.has_access(cap), ENotOwner); - assert!(self.has_item_with_type(id), EItemNotFound); - assert!(!self.is_listed_exclusively(id), EListedExclusively); +/// Place any object into a Kiosk. +/// Performs an authorization check to make sure only owner can do that. +public fun place(self: &mut Kiosk, cap: &KioskOwnerCap, item: T) { + assert!(self.has_access(cap), ENotOwner); + self.place_internal(item) +} - df::add(&mut self.id, Listing { id, is_exclusive: false }, price); - event::emit(ItemListed { kiosk: object::id(self), id, price }) - } +/// Place an item to the `Kiosk` and issue a `Lock` for it. Once placed this +/// way, an item can only be listed either with a `list` function or with a +/// `list_with_purchase_cap`. +/// +/// Requires policy for `T` to make sure that there's an issued `TransferPolicy` +/// and the item can be sold, otherwise the asset might be locked forever. +public fun lock( + self: &mut Kiosk, + cap: &KioskOwnerCap, + _policy: &TransferPolicy, + item: T, +) { + assert!(self.has_access(cap), ENotOwner); + self.lock_internal(item) +} - /// Calls `place` and `list` together - simplifies the flow. - public fun place_and_list( - self: &mut Kiosk, cap: &KioskOwnerCap, item: T, price: u64 - ) { - let id = object::id(&item); - self.place(cap, item); - self.list(cap, id, price) - } +/// Take any object from the Kiosk. +/// Performs an authorization check to make sure only owner can do that. +public fun take(self: &mut Kiosk, cap: &KioskOwnerCap, id: ID): T { + assert!(self.has_access(cap), ENotOwner); + assert!(!self.is_locked(id), EItemLocked); + assert!(!self.is_listed_exclusively(id), EListedExclusively); + assert!(self.has_item(id), EItemNotFound); + + self.item_count = self.item_count - 1; + df::remove_if_exists(&mut self.id, Listing { id, is_exclusive: false }); + dof::remove(&mut self.id, Item { id }) +} - /// Remove an existing listing from the `Kiosk` and keep the item in the - /// user Kiosk. Can only be performed by the owner of the `Kiosk`. - public fun delist( - self: &mut Kiosk, cap: &KioskOwnerCap, id: ID - ) { - assert!(self.has_access(cap), ENotOwner); - assert!(self.has_item_with_type(id), EItemNotFound); - assert!(!self.is_listed_exclusively(id), EListedExclusively); - assert!(self.is_listed(id), ENotListed); - - df::remove(&mut self.id, Listing { id, is_exclusive: false }); - event::emit(ItemDelisted { kiosk: object::id(self), id }) - } +// === Trading functionality: List and Purchase === - /// Make a trade: pay the owner of the item and request a Transfer to the `target` - /// kiosk (to prevent item being taken by the approving party). - /// - /// Received `TransferRequest` needs to be handled by the publisher of the T, - /// if they have a method implemented that allows a trade, it is possible to - /// request their approval (by calling some function) so that the trade can be - /// finalized. - public fun purchase( - self: &mut Kiosk, id: ID, payment: Coin - ): (T, TransferRequest) { - let price = df::remove(&mut self.id, Listing { id, is_exclusive: false }); - let inner = dof::remove(&mut self.id, Item { id }); - - self.item_count = self.item_count - 1; - assert!(price == payment.value(), EIncorrectAmount); - df::remove_if_exists(&mut self.id, Lock { id }); - coin::put(&mut self.profits, payment); - - event::emit(ItemPurchased { kiosk: object::id(self), id, price }); - - (inner, transfer_policy::new_request(id, price, object::id(self))) - } +/// List the item by setting a price and making it available for purchase. +/// Performs an authorization check to make sure only owner can sell. +public fun list(self: &mut Kiosk, cap: &KioskOwnerCap, id: ID, price: u64) { + assert!(self.has_access(cap), ENotOwner); + assert!(self.has_item_with_type(id), EItemNotFound); + assert!(!self.is_listed_exclusively(id), EListedExclusively); - // === Trading Functionality: Exclusive listing with `PurchaseCap` === - - /// Creates a `PurchaseCap` which gives the right to purchase an item - /// for any price equal or higher than the `min_price`. - public fun list_with_purchase_cap( - self: &mut Kiosk, cap: &KioskOwnerCap, id: ID, min_price: u64, ctx: &mut TxContext - ): PurchaseCap { - assert!(self.has_access(cap), ENotOwner); - assert!(self.has_item_with_type(id), EItemNotFound); - assert!(!self.is_listed(id), EAlreadyListed); - - df::add(&mut self.id, Listing { id, is_exclusive: true }, min_price); - - PurchaseCap { - min_price, - item_id: id, - id: object::new(ctx), - kiosk_id: object::id(self), - } - } + df::add(&mut self.id, Listing { id, is_exclusive: false }, price); + event::emit(ItemListed { kiosk: object::id(self), id, price }) +} - /// Unpack the `PurchaseCap` and call `purchase`. Sets the payment amount - /// as the price for the listing making sure it's no less than `min_amount`. - public fun purchase_with_cap( - self: &mut Kiosk, purchase_cap: PurchaseCap, payment: Coin - ): (T, TransferRequest) { - let PurchaseCap { id, item_id, kiosk_id, min_price } = purchase_cap; - id.delete(); +/// Calls `place` and `list` together - simplifies the flow. +public fun place_and_list( + self: &mut Kiosk, + cap: &KioskOwnerCap, + item: T, + price: u64, +) { + let id = object::id(&item); + self.place(cap, item); + self.list(cap, id, price) +} - let id = item_id; - let paid = payment.value(); - assert!(paid >= min_price, EIncorrectAmount); - assert!(object::id(self) == kiosk_id, EWrongKiosk); +/// Remove an existing listing from the `Kiosk` and keep the item in the +/// user Kiosk. Can only be performed by the owner of the `Kiosk`. +public fun delist(self: &mut Kiosk, cap: &KioskOwnerCap, id: ID) { + assert!(self.has_access(cap), ENotOwner); + assert!(self.has_item_with_type(id), EItemNotFound); + assert!(!self.is_listed_exclusively(id), EListedExclusively); + assert!(self.is_listed(id), ENotListed); - df::remove(&mut self.id, Listing { id, is_exclusive: true }); + df::remove(&mut self.id, Listing { id, is_exclusive: false }); + event::emit(ItemDelisted { kiosk: object::id(self), id }) +} - coin::put(&mut self.profits, payment); - self.item_count = self.item_count - 1; - df::remove_if_exists(&mut self.id, Lock { id }); - let item = dof::remove(&mut self.id, Item { id }); +/// Make a trade: pay the owner of the item and request a Transfer to the `target` +/// kiosk (to prevent item being taken by the approving party). +/// +/// Received `TransferRequest` needs to be handled by the publisher of the T, +/// if they have a method implemented that allows a trade, it is possible to +/// request their approval (by calling some function) so that the trade can be +/// finalized. +public fun purchase( + self: &mut Kiosk, + id: ID, + payment: Coin, +): (T, TransferRequest) { + let price = df::remove(&mut self.id, Listing { id, is_exclusive: false }); + let inner = dof::remove(&mut self.id, Item { id }); + + self.item_count = self.item_count - 1; + assert!(price == payment.value(), EIncorrectAmount); + df::remove_if_exists(&mut self.id, Lock { id }); + coin::put(&mut self.profits, payment); + + event::emit(ItemPurchased { kiosk: object::id(self), id, price }); + + (inner, transfer_policy::new_request(id, price, object::id(self))) +} - (item, transfer_policy::new_request(id, paid, object::id(self))) +// === Trading Functionality: Exclusive listing with `PurchaseCap` === + +/// Creates a `PurchaseCap` which gives the right to purchase an item +/// for any price equal or higher than the `min_price`. +public fun list_with_purchase_cap( + self: &mut Kiosk, + cap: &KioskOwnerCap, + id: ID, + min_price: u64, + ctx: &mut TxContext, +): PurchaseCap { + assert!(self.has_access(cap), ENotOwner); + assert!(self.has_item_with_type(id), EItemNotFound); + assert!(!self.is_listed(id), EAlreadyListed); + + df::add(&mut self.id, Listing { id, is_exclusive: true }, min_price); + + PurchaseCap { + min_price, + item_id: id, + id: object::new(ctx), + kiosk_id: object::id(self), } +} - /// Return the `PurchaseCap` without making a purchase; remove an active offer and - /// allow the item for taking. Can only be returned to its `Kiosk`, aborts otherwise. - public fun return_purchase_cap( - self: &mut Kiosk, purchase_cap: PurchaseCap - ) { - let PurchaseCap { id, item_id, kiosk_id, min_price: _ } = purchase_cap; +/// Unpack the `PurchaseCap` and call `purchase`. Sets the payment amount +/// as the price for the listing making sure it's no less than `min_amount`. +public fun purchase_with_cap( + self: &mut Kiosk, + purchase_cap: PurchaseCap, + payment: Coin, +): (T, TransferRequest) { + let PurchaseCap { id, item_id, kiosk_id, min_price } = purchase_cap; + id.delete(); + + let id = item_id; + let paid = payment.value(); + assert!(paid >= min_price, EIncorrectAmount); + assert!(object::id(self) == kiosk_id, EWrongKiosk); + + df::remove(&mut self.id, Listing { id, is_exclusive: true }); + + coin::put(&mut self.profits, payment); + self.item_count = self.item_count - 1; + df::remove_if_exists(&mut self.id, Lock { id }); + let item = dof::remove(&mut self.id, Item { id }); + + (item, transfer_policy::new_request(id, paid, object::id(self))) +} - assert!(object::id(self) == kiosk_id, EWrongKiosk); - df::remove(&mut self.id, Listing { id: item_id, is_exclusive: true }); - id.delete() - } +/// Return the `PurchaseCap` without making a purchase; remove an active offer and +/// allow the item for taking. Can only be returned to its `Kiosk`, aborts otherwise. +public fun return_purchase_cap(self: &mut Kiosk, purchase_cap: PurchaseCap) { + let PurchaseCap { id, item_id, kiosk_id, min_price: _ } = purchase_cap; - /// Withdraw profits from the Kiosk. - public fun withdraw( - self: &mut Kiosk, cap: &KioskOwnerCap, amount: Option, ctx: &mut TxContext - ): Coin { - assert!(self.has_access(cap), ENotOwner); - - let amount = if (amount.is_some()) { - let amt = amount.destroy_some(); - assert!(amt <= self.profits.value(), ENotEnough); - amt - } else { - self.profits.value() - }; - - coin::take(&mut self.profits, amount, ctx) - } + assert!(object::id(self) == kiosk_id, EWrongKiosk); + df::remove(&mut self.id, Listing { id: item_id, is_exclusive: true }); + id.delete() +} - // === Internal Core === +/// Withdraw profits from the Kiosk. +public fun withdraw( + self: &mut Kiosk, + cap: &KioskOwnerCap, + amount: Option, + ctx: &mut TxContext, +): Coin { + assert!(self.has_access(cap), ENotOwner); + + let amount = if (amount.is_some()) { + let amt = amount.destroy_some(); + assert!(amt <= self.profits.value(), ENotEnough); + amt + } else { + self.profits.value() + }; - /// Internal: "lock" an item disabling the `take` action. - public(package) fun lock_internal(self: &mut Kiosk, item: T) { - df::add(&mut self.id, Lock { id: object::id(&item) }, true); - self.place_internal(item) - } + coin::take(&mut self.profits, amount, ctx) +} - /// Internal: "place" an item to the Kiosk and increment the item count. - public(package) fun place_internal(self: &mut Kiosk, item: T) { - self.item_count = self.item_count + 1; - dof::add(&mut self.id, Item { id: object::id(&item) }, item) - } +// === Internal Core === - /// Internal: get a mutable access to the UID. - public(package) fun uid_mut_internal(self: &mut Kiosk): &mut UID { - &mut self.id - } +/// Internal: "lock" an item disabling the `take` action. +public(package) fun lock_internal(self: &mut Kiosk, item: T) { + df::add(&mut self.id, Lock { id: object::id(&item) }, true); + self.place_internal(item) +} - // === Kiosk fields access === +/// Internal: "place" an item to the Kiosk and increment the item count. +public(package) fun place_internal(self: &mut Kiosk, item: T) { + self.item_count = self.item_count + 1; + dof::add(&mut self.id, Item { id: object::id(&item) }, item) +} - /// Check whether the `item` is present in the `Kiosk`. - public fun has_item(self: &Kiosk, id: ID): bool { - dof::exists_(&self.id, Item { id }) - } +/// Internal: get a mutable access to the UID. +public(package) fun uid_mut_internal(self: &mut Kiosk): &mut UID { + &mut self.id +} - /// Check whether the `item` is present in the `Kiosk` and has type T. - public fun has_item_with_type(self: &Kiosk, id: ID): bool { - dof::exists_with_type(&self.id, Item { id }) - } +// === Kiosk fields access === - /// Check whether an item with the `id` is locked in the `Kiosk`. Meaning - /// that the only two actions that can be performed on it are `list` and - /// `list_with_purchase_cap`, it cannot be `take`n out of the `Kiosk`. - public fun is_locked(self: &Kiosk, id: ID): bool { - df::exists_(&self.id, Lock { id }) - } +/// Check whether the `item` is present in the `Kiosk`. +public fun has_item(self: &Kiosk, id: ID): bool { + dof::exists_(&self.id, Item { id }) +} + +/// Check whether the `item` is present in the `Kiosk` and has type T. +public fun has_item_with_type(self: &Kiosk, id: ID): bool { + dof::exists_with_type(&self.id, Item { id }) +} + +/// Check whether an item with the `id` is locked in the `Kiosk`. Meaning +/// that the only two actions that can be performed on it are `list` and +/// `list_with_purchase_cap`, it cannot be `take`n out of the `Kiosk`. +public fun is_locked(self: &Kiosk, id: ID): bool { + df::exists_(&self.id, Lock { id }) +} - /// Check whether an `item` is listed (exclusively or non exclusively). - public fun is_listed(self: &Kiosk, id: ID): bool { - df::exists_(&self.id, Listing { id, is_exclusive: false }) +/// Check whether an `item` is listed (exclusively or non exclusively). +public fun is_listed(self: &Kiosk, id: ID): bool { + df::exists_(&self.id, Listing { id, is_exclusive: false }) || self.is_listed_exclusively(id) - } +} - /// Check whether there's a `PurchaseCap` issued for an item. - public fun is_listed_exclusively(self: &Kiosk, id: ID): bool { - df::exists_(&self.id, Listing { id, is_exclusive: true }) - } +/// Check whether there's a `PurchaseCap` issued for an item. +public fun is_listed_exclusively(self: &Kiosk, id: ID): bool { + df::exists_(&self.id, Listing { id, is_exclusive: true }) +} - /// Check whether the `KioskOwnerCap` matches the `Kiosk`. - public fun has_access(self: &mut Kiosk, cap: &KioskOwnerCap): bool { - object::id(self) == cap.`for` - } +/// Check whether the `KioskOwnerCap` matches the `Kiosk`. +public fun has_access(self: &mut Kiosk, cap: &KioskOwnerCap): bool { + object::id(self) == cap.`for` +} - /// Access the `UID` using the `KioskOwnerCap`. - public fun uid_mut_as_owner( - self: &mut Kiosk, cap: &KioskOwnerCap - ): &mut UID { - assert!(self.has_access(cap), ENotOwner); - &mut self.id - } +/// Access the `UID` using the `KioskOwnerCap`. +public fun uid_mut_as_owner(self: &mut Kiosk, cap: &KioskOwnerCap): &mut UID { + assert!(self.has_access(cap), ENotOwner); + &mut self.id +} - /// [DEPRECATED] - /// Allow or disallow `uid` and `uid_mut` access via the `allow_extensions` - /// setting. - public fun set_allow_extensions( - self: &mut Kiosk, cap: &KioskOwnerCap, allow_extensions: bool - ) { - assert!(self.has_access(cap), ENotOwner); - self.allow_extensions = allow_extensions; - } +/// [DEPRECATED] +/// Allow or disallow `uid` and `uid_mut` access via the `allow_extensions` +/// setting. +public fun set_allow_extensions(self: &mut Kiosk, cap: &KioskOwnerCap, allow_extensions: bool) { + assert!(self.has_access(cap), ENotOwner); + self.allow_extensions = allow_extensions; +} - /// Get the immutable `UID` for dynamic field access. - /// Always enabled. - /// - /// Given the &UID can be used for reading keys and authorization, - /// its access - public fun uid(self: &Kiosk): &UID { - &self.id - } +/// Get the immutable `UID` for dynamic field access. +/// Always enabled. +/// +/// Given the &UID can be used for reading keys and authorization, +/// its access +public fun uid(self: &Kiosk): &UID { + &self.id +} - /// Get the mutable `UID` for dynamic field access and extensions. - /// Aborts if `allow_extensions` set to `false`. - public fun uid_mut(self: &mut Kiosk): &mut UID { - assert!(self.allow_extensions, EUidAccessNotAllowed); - &mut self.id - } +/// Get the mutable `UID` for dynamic field access and extensions. +/// Aborts if `allow_extensions` set to `false`. +public fun uid_mut(self: &mut Kiosk): &mut UID { + assert!(self.allow_extensions, EUidAccessNotAllowed); + &mut self.id +} - /// Get the owner of the Kiosk. - public fun owner(self: &Kiosk): address { - self.owner - } +/// Get the owner of the Kiosk. +public fun owner(self: &Kiosk): address { + self.owner +} - /// Get the number of items stored in a Kiosk. - public fun item_count(self: &Kiosk): u32 { - self.item_count - } +/// Get the number of items stored in a Kiosk. +public fun item_count(self: &Kiosk): u32 { + self.item_count +} - /// Get the amount of profits collected by selling items. - public fun profits_amount(self: &Kiosk): u64 { - self.profits.value() - } +/// Get the amount of profits collected by selling items. +public fun profits_amount(self: &Kiosk): u64 { + self.profits.value() +} - /// Get mutable access to `profits` - owner only action. - public fun profits_mut(self: &mut Kiosk, cap: &KioskOwnerCap): &mut Balance { - assert!(self.has_access(cap), ENotOwner); - &mut self.profits - } +/// Get mutable access to `profits` - owner only action. +public fun profits_mut(self: &mut Kiosk, cap: &KioskOwnerCap): &mut Balance { + assert!(self.has_access(cap), ENotOwner); + &mut self.profits +} - // === Item borrowing === +// === Item borrowing === - #[syntax(index)] - /// Immutably borrow an item from the `Kiosk`. Any item can be `borrow`ed - /// at any time. - public fun borrow( - self: &Kiosk, cap: &KioskOwnerCap, id: ID - ): &T { - assert!(object::id(self) == cap.`for`, ENotOwner); - assert!(self.has_item(id), EItemNotFound); +#[syntax(index)] +/// Immutably borrow an item from the `Kiosk`. Any item can be `borrow`ed +/// at any time. +public fun borrow(self: &Kiosk, cap: &KioskOwnerCap, id: ID): &T { + assert!(object::id(self) == cap.`for`, ENotOwner); + assert!(self.has_item(id), EItemNotFound); - dof::borrow(&self.id, Item { id }) - } + dof::borrow(&self.id, Item { id }) +} - #[syntax(index)] - /// Mutably borrow an item from the `Kiosk`. - /// Item can be `borrow_mut`ed only if it's not `is_listed`. - public fun borrow_mut( - self: &mut Kiosk, cap: &KioskOwnerCap, id: ID - ): &mut T { - assert!(self.has_access(cap), ENotOwner); - assert!(self.has_item(id), EItemNotFound); - assert!(!self.is_listed(id), EItemIsListed); - - dof::borrow_mut(&mut self.id, Item { id }) - } +#[syntax(index)] +/// Mutably borrow an item from the `Kiosk`. +/// Item can be `borrow_mut`ed only if it's not `is_listed`. +public fun borrow_mut(self: &mut Kiosk, cap: &KioskOwnerCap, id: ID): &mut T { + assert!(self.has_access(cap), ENotOwner); + assert!(self.has_item(id), EItemNotFound); + assert!(!self.is_listed(id), EItemIsListed); - /// Take the item from the `Kiosk` with a guarantee that it will be returned. - /// Item can be `borrow_val`-ed only if it's not `is_listed`. - public fun borrow_val( - self: &mut Kiosk, cap: &KioskOwnerCap, id: ID - ): (T, Borrow) { - assert!(self.has_access(cap), ENotOwner); - assert!(self.has_item(id), EItemNotFound); - assert!(!self.is_listed(id), EItemIsListed); - - ( - dof::remove(&mut self.id, Item { id }), - Borrow { kiosk_id: object::id(self), item_id: id } - ) - } + dof::borrow_mut(&mut self.id, Item { id }) +} - /// Return the borrowed item to the `Kiosk`. This method cannot be avoided - /// if `borrow_val` is used. - public fun return_val( - self: &mut Kiosk, item: T, borrow: Borrow - ) { - let Borrow { kiosk_id, item_id } = borrow; +/// Take the item from the `Kiosk` with a guarantee that it will be returned. +/// Item can be `borrow_val`-ed only if it's not `is_listed`. +public fun borrow_val(self: &mut Kiosk, cap: &KioskOwnerCap, id: ID): (T, Borrow) { + assert!(self.has_access(cap), ENotOwner); + assert!(self.has_item(id), EItemNotFound); + assert!(!self.is_listed(id), EItemIsListed); - assert!(object::id(self) == kiosk_id, EWrongKiosk); - assert!(object::id(&item) == item_id, EItemMismatch); + (dof::remove(&mut self.id, Item { id }), Borrow { kiosk_id: object::id(self), item_id: id }) +} - dof::add(&mut self.id, Item { id: item_id }, item); - } +/// Return the borrowed item to the `Kiosk`. This method cannot be avoided +/// if `borrow_val` is used. +public fun return_val(self: &mut Kiosk, item: T, borrow: Borrow) { + let Borrow { kiosk_id, item_id } = borrow; - // === KioskOwnerCap fields access === + assert!(object::id(self) == kiosk_id, EWrongKiosk); + assert!(object::id(&item) == item_id, EItemMismatch); - /// Get the `for` field of the `KioskOwnerCap`. - public fun kiosk_owner_cap_for(cap: &KioskOwnerCap): ID { - cap.`for` - } + dof::add(&mut self.id, Item { id: item_id }, item); +} - // === PurchaseCap fields access === +// === KioskOwnerCap fields access === - /// Get the `kiosk_id` from the `PurchaseCap`. - public fun purchase_cap_kiosk(self: &PurchaseCap): ID { - self.kiosk_id - } +/// Get the `for` field of the `KioskOwnerCap`. +public fun kiosk_owner_cap_for(cap: &KioskOwnerCap): ID { + cap.`for` +} - /// Get the `Item_id` from the `PurchaseCap`. - public fun purchase_cap_item(self: &PurchaseCap): ID { - self.item_id - } +// === PurchaseCap fields access === - /// Get the `min_price` from the `PurchaseCap`. - public fun purchase_cap_min_price(self: &PurchaseCap): u64 { - self.min_price - } +/// Get the `kiosk_id` from the `PurchaseCap`. +public fun purchase_cap_kiosk(self: &PurchaseCap): ID { + self.kiosk_id +} + +/// Get the `Item_id` from the `PurchaseCap`. +public fun purchase_cap_item(self: &PurchaseCap): ID { + self.item_id +} + +/// Get the `min_price` from the `PurchaseCap`. +public fun purchase_cap_min_price(self: &PurchaseCap): u64 { + self.min_price } diff --git a/crates/sui-framework/packages/sui-framework/sources/kiosk/kiosk_extension.move b/crates/sui-framework/packages/sui-framework/sources/kiosk/kiosk_extension.move index dd3ec9e2fe34c..fceb338a02e48 100644 --- a/crates/sui-framework/packages/sui-framework/sources/kiosk/kiosk_extension.move +++ b/crates/sui-framework/packages/sui-framework/sources/kiosk/kiosk_extension.move @@ -38,217 +38,211 @@ /// - `kiosk_extension` is a friend module to `kiosk` and has access to its /// internal functions (such as `place_internal` and `lock_internal` to /// implement custom authorization scheme for `place` and `lock` respectively). -module sui::kiosk_extension { - use sui::bag::{Self, Bag}; - use sui::dynamic_field as df; - use sui::transfer_policy::TransferPolicy; - use sui::kiosk::{Kiosk, KioskOwnerCap}; - - /// Trying to add an extension while not being the owner of the Kiosk. - const ENotOwner: u64 = 0; - /// Extension is trying to access a permissioned action while not having - /// the required permission. - const EExtensionNotAllowed: u64 = 2; - /// Extension is not installed in the Kiosk. - const EExtensionNotInstalled: u64 = 3; - - /// Value that represents the `place` permission in the permissions bitmap. - const PLACE: u128 = 1; - - /// Value that represents the `lock` and `place` permission in the - /// permissions bitmap. - const LOCK: u128 = 2; - - /// The Extension struct contains the data used by the extension and the - /// configuration for this extension. Stored under the `ExtensionKey` - /// dynamic field. - public struct Extension has store { - /// Storage for the extension, an isolated Bag. By putting the extension - /// into a single dynamic field, we reduce the amount of fields on the - /// top level (eg items / listings) while giving extension developers - /// the ability to store any data they want. - storage: Bag, - /// Bitmap of permissions that the extension has (can be revoked any - /// moment). It's all or nothing policy - either the extension has the - /// required permissions or no permissions at all. - /// - /// 1st bit - `place` - allows to place items for sale - /// 2nd bit - `lock` and `place` - allows to lock items (and place) - /// - /// For example: - /// - `10` - allows to place items and lock them. - /// - `11` - allows to place items and lock them (`lock` includes `place`). - /// - `01` - allows to place items, but not lock them. - /// - `00` - no permissions. - permissions: u128, - /// Whether the extension can call protected actions. By default, all - /// extensions are enabled (on `add` call), however the Kiosk - /// owner can disable them at any time. - /// - /// Disabling the extension does not limit its access to the storage. - is_enabled: bool, - } - - /// The `ExtensionKey` is a typed dynamic field key used to store the - /// extension configuration and data. `Ext` is a phantom type that is used - /// to identify the extension witness. - public struct ExtensionKey has store, copy, drop {} - - // === Management === - - /// Add an extension to the Kiosk. Can only be performed by the owner. The - /// extension witness is required to allow extensions define their set of - /// permissions in the custom `add` call. - public fun add( - _ext: Ext, - self: &mut Kiosk, - cap: &KioskOwnerCap, - permissions: u128, - ctx: &mut TxContext - ) { - assert!(self.has_access(cap), ENotOwner); - df::add( - self.uid_mut_as_owner(cap), - ExtensionKey {}, - Extension { - storage: bag::new(ctx), - permissions, - is_enabled: true, - } - ) - } - - /// Revoke permissions from the extension. While it does not remove the - /// extension completely, it keeps it from performing any protected actions. - /// The storage is still available to the extension (until it's removed). - public fun disable( - self: &mut Kiosk, - cap: &KioskOwnerCap, - ) { - assert!(self.has_access(cap), ENotOwner); - assert!(is_installed(self), EExtensionNotInstalled); - extension_mut(self).is_enabled = false; - } - - /// Re-enable the extension allowing it to call protected actions (eg - /// `place`, `lock`). By default, all added extensions are enabled. Kiosk - /// owner can disable them via `disable` call. - public fun enable( - self: &mut Kiosk, - cap: &KioskOwnerCap, - ) { - assert!(self.has_access(cap), ENotOwner); - assert!(is_installed(self), EExtensionNotInstalled); - extension_mut(self).is_enabled = true; - } - - /// Remove an extension from the Kiosk. Can only be performed by the owner, - /// the extension storage must be empty for the transaction to succeed. - public fun remove( - self: &mut Kiosk, cap: &KioskOwnerCap - ) { - assert!(self.has_access(cap), ENotOwner); - assert!(is_installed(self), EExtensionNotInstalled); - - let Extension { - storage, - permissions: _, - is_enabled: _, - } = df::remove(self.uid_mut_as_owner(cap), ExtensionKey {}); - - storage.destroy_empty(); - } - - // === Storage === - - /// Get immutable access to the extension storage. Can only be performed by - /// the extension as long as the extension is installed. - public fun storage( - _ext: Ext, self: &Kiosk - ): &Bag { - assert!(is_installed(self), EExtensionNotInstalled); - &extension(self).storage - } - - /// Get mutable access to the extension storage. Can only be performed by - /// the extension as long as the extension is installed. Disabling the - /// extension does not prevent it from accessing the storage. +module sui::kiosk_extension; + +use sui::bag::{Self, Bag}; +use sui::dynamic_field as df; +use sui::kiosk::{Kiosk, KioskOwnerCap}; +use sui::transfer_policy::TransferPolicy; + +/// Trying to add an extension while not being the owner of the Kiosk. +const ENotOwner: u64 = 0; +/// Extension is trying to access a permissioned action while not having +/// the required permission. +const EExtensionNotAllowed: u64 = 2; +/// Extension is not installed in the Kiosk. +const EExtensionNotInstalled: u64 = 3; + +/// Value that represents the `place` permission in the permissions bitmap. +const PLACE: u128 = 1; + +/// Value that represents the `lock` and `place` permission in the +/// permissions bitmap. +const LOCK: u128 = 2; + +/// The Extension struct contains the data used by the extension and the +/// configuration for this extension. Stored under the `ExtensionKey` +/// dynamic field. +public struct Extension has store { + /// Storage for the extension, an isolated Bag. By putting the extension + /// into a single dynamic field, we reduce the amount of fields on the + /// top level (eg items / listings) while giving extension developers + /// the ability to store any data they want. + storage: Bag, + /// Bitmap of permissions that the extension has (can be revoked any + /// moment). It's all or nothing policy - either the extension has the + /// required permissions or no permissions at all. /// - /// Potentially dangerous: extension developer can keep data in a Bag - /// therefore never really allowing the KioskOwner to remove the extension. - /// However, it is the case with any other solution (1) and this way we - /// prevent intentional extension freeze when the owner wants to ruin a - /// trade (2) - eg locking extension while an auction is in progress. + /// 1st bit - `place` - allows to place items for sale + /// 2nd bit - `lock` and `place` - allows to lock items (and place) /// - /// Extensions should be crafted carefully, and the KioskOwner should be - /// aware of the risks. - public fun storage_mut( - _ext: Ext, self: &mut Kiosk - ): &mut Bag { - assert!(is_installed(self), EExtensionNotInstalled); - &mut extension_mut(self).storage - } - - // === Protected Actions === - - /// Protected action: place an item into the Kiosk. Can be performed by an - /// authorized extension. The extension must have the `place` permission or - /// a `lock` permission. + /// For example: + /// - `10` - allows to place items and lock them. + /// - `11` - allows to place items and lock them (`lock` includes `place`). + /// - `01` - allows to place items, but not lock them. + /// - `00` - no permissions. + permissions: u128, + /// Whether the extension can call protected actions. By default, all + /// extensions are enabled (on `add` call), however the Kiosk + /// owner can disable them at any time. /// - /// To prevent non-tradable items from being placed into `Kiosk` the method - /// requires a `TransferPolicy` for the placed type to exist. - public fun place( - _ext: Ext, self: &mut Kiosk, item: T, _policy: &TransferPolicy - ) { - assert!(is_installed(self), EExtensionNotInstalled); - assert!(can_place(self) || can_lock(self), EExtensionNotAllowed); - - self.place_internal(item) - } - - /// Protected action: lock an item in the Kiosk. Can be performed by an - /// authorized extension. The extension must have the `lock` permission. - public fun lock( - _ext: Ext, self: &mut Kiosk, item: T, _policy: &TransferPolicy - ) { - assert!(is_installed(self), EExtensionNotInstalled); - assert!(can_lock(self), EExtensionNotAllowed); - - self.lock_internal(item) - } - - // === Field Access === - - /// Check whether an extension of type `Ext` is installed. - public fun is_installed(self: &Kiosk): bool { - df::exists_(self.uid(), ExtensionKey {}) - } - - /// Check whether an extension of type `Ext` is enabled. - public fun is_enabled(self: &Kiosk): bool { - extension(self).is_enabled - } - - /// Check whether an extension of type `Ext` can `place` into Kiosk. - public fun can_place(self: &Kiosk): bool { - is_enabled(self) && extension(self).permissions & PLACE != 0 - } - - /// Check whether an extension of type `Ext` can `lock` items in Kiosk. - /// Locking also enables `place`. - public fun can_lock(self: &Kiosk): bool { - is_enabled(self) && extension(self).permissions & LOCK != 0 - } - - // === Internal === - - /// Internal: get a read-only access to the Extension. - fun extension(self: &Kiosk): &Extension { - df::borrow(self.uid(), ExtensionKey {}) - } - - /// Internal: get a mutable access to the Extension. - fun extension_mut(self: &mut Kiosk): &mut Extension { - df::borrow_mut(self.uid_mut_internal(), ExtensionKey {}) - } + /// Disabling the extension does not limit its access to the storage. + is_enabled: bool, +} + +/// The `ExtensionKey` is a typed dynamic field key used to store the +/// extension configuration and data. `Ext` is a phantom type that is used +/// to identify the extension witness. +public struct ExtensionKey has store, copy, drop {} + +// === Management === + +/// Add an extension to the Kiosk. Can only be performed by the owner. The +/// extension witness is required to allow extensions define their set of +/// permissions in the custom `add` call. +public fun add( + _ext: Ext, + self: &mut Kiosk, + cap: &KioskOwnerCap, + permissions: u128, + ctx: &mut TxContext, +) { + assert!(self.has_access(cap), ENotOwner); + df::add( + self.uid_mut_as_owner(cap), + ExtensionKey {}, + Extension { + storage: bag::new(ctx), + permissions, + is_enabled: true, + }, + ) +} + +/// Revoke permissions from the extension. While it does not remove the +/// extension completely, it keeps it from performing any protected actions. +/// The storage is still available to the extension (until it's removed). +public fun disable(self: &mut Kiosk, cap: &KioskOwnerCap) { + assert!(self.has_access(cap), ENotOwner); + assert!(is_installed(self), EExtensionNotInstalled); + extension_mut(self).is_enabled = false; +} + +/// Re-enable the extension allowing it to call protected actions (eg +/// `place`, `lock`). By default, all added extensions are enabled. Kiosk +/// owner can disable them via `disable` call. +public fun enable(self: &mut Kiosk, cap: &KioskOwnerCap) { + assert!(self.has_access(cap), ENotOwner); + assert!(is_installed(self), EExtensionNotInstalled); + extension_mut(self).is_enabled = true; +} + +/// Remove an extension from the Kiosk. Can only be performed by the owner, +/// the extension storage must be empty for the transaction to succeed. +public fun remove(self: &mut Kiosk, cap: &KioskOwnerCap) { + assert!(self.has_access(cap), ENotOwner); + assert!(is_installed(self), EExtensionNotInstalled); + + let Extension { + storage, + permissions: _, + is_enabled: _, + } = df::remove(self.uid_mut_as_owner(cap), ExtensionKey {}); + + storage.destroy_empty(); +} + +// === Storage === + +/// Get immutable access to the extension storage. Can only be performed by +/// the extension as long as the extension is installed. +public fun storage(_ext: Ext, self: &Kiosk): &Bag { + assert!(is_installed(self), EExtensionNotInstalled); + &extension(self).storage +} + +/// Get mutable access to the extension storage. Can only be performed by +/// the extension as long as the extension is installed. Disabling the +/// extension does not prevent it from accessing the storage. +/// +/// Potentially dangerous: extension developer can keep data in a Bag +/// therefore never really allowing the KioskOwner to remove the extension. +/// However, it is the case with any other solution (1) and this way we +/// prevent intentional extension freeze when the owner wants to ruin a +/// trade (2) - eg locking extension while an auction is in progress. +/// +/// Extensions should be crafted carefully, and the KioskOwner should be +/// aware of the risks. +public fun storage_mut(_ext: Ext, self: &mut Kiosk): &mut Bag { + assert!(is_installed(self), EExtensionNotInstalled); + &mut extension_mut(self).storage +} + +// === Protected Actions === + +/// Protected action: place an item into the Kiosk. Can be performed by an +/// authorized extension. The extension must have the `place` permission or +/// a `lock` permission. +/// +/// To prevent non-tradable items from being placed into `Kiosk` the method +/// requires a `TransferPolicy` for the placed type to exist. +public fun place( + _ext: Ext, + self: &mut Kiosk, + item: T, + _policy: &TransferPolicy, +) { + assert!(is_installed(self), EExtensionNotInstalled); + assert!(can_place(self) || can_lock(self), EExtensionNotAllowed); + + self.place_internal(item) +} + +/// Protected action: lock an item in the Kiosk. Can be performed by an +/// authorized extension. The extension must have the `lock` permission. +public fun lock( + _ext: Ext, + self: &mut Kiosk, + item: T, + _policy: &TransferPolicy, +) { + assert!(is_installed(self), EExtensionNotInstalled); + assert!(can_lock(self), EExtensionNotAllowed); + + self.lock_internal(item) +} + +// === Field Access === + +/// Check whether an extension of type `Ext` is installed. +public fun is_installed(self: &Kiosk): bool { + df::exists_(self.uid(), ExtensionKey {}) +} + +/// Check whether an extension of type `Ext` is enabled. +public fun is_enabled(self: &Kiosk): bool { + extension(self).is_enabled +} + +/// Check whether an extension of type `Ext` can `place` into Kiosk. +public fun can_place(self: &Kiosk): bool { + is_enabled(self) && extension(self).permissions & PLACE != 0 +} + +/// Check whether an extension of type `Ext` can `lock` items in Kiosk. +/// Locking also enables `place`. +public fun can_lock(self: &Kiosk): bool { + is_enabled(self) && extension(self).permissions & LOCK != 0 +} + +// === Internal === + +/// Internal: get a read-only access to the Extension. +fun extension(self: &Kiosk): &Extension { + df::borrow(self.uid(), ExtensionKey {}) +} + +/// Internal: get a mutable access to the Extension. +fun extension_mut(self: &mut Kiosk): &mut Extension { + df::borrow_mut(self.uid_mut_internal(), ExtensionKey {}) } diff --git a/crates/sui-framework/packages/sui-framework/sources/kiosk/transfer_policy.move b/crates/sui-framework/packages/sui-framework/sources/kiosk/transfer_policy.move index 290e4b0d7282f..effd6c9a6d8ad 100644 --- a/crates/sui-framework/packages/sui-framework/sources/kiosk/transfer_policy.move +++ b/crates/sui-framework/packages/sui-framework/sources/kiosk/transfer_policy.move @@ -21,282 +21,280 @@ /// of their types and collect profits if a fee is required on sales. Custom /// policies can be removed at any moment, and the change will affect all instances /// of the type at once. -module sui::transfer_policy { - use std::type_name::{Self, TypeName}; - use sui::package::{Self, Publisher}; - use sui::vec_set::{Self, VecSet}; - use sui::dynamic_field as df; - use sui::balance::{Self, Balance}; - use sui::sui::SUI; - use sui::coin::{Self, Coin}; - use sui::event; - - /// The number of receipts does not match the `TransferPolicy` requirement. - const EPolicyNotSatisfied: u64 = 0; - /// A completed rule is not set in the `TransferPolicy`. - const EIllegalRule: u64 = 1; - /// A Rule is not set. - const EUnknownRequirement: u64 = 2; - /// Attempting to create a Rule that is already set. - const ERuleAlreadySet: u64 = 3; - /// Trying to `withdraw` or `close_and_withdraw` with a wrong Cap. - const ENotOwner: u64 = 4; - /// Trying to `withdraw` more than there is. - const ENotEnough: u64 = 5; - - /// A "Hot Potato" forcing the buyer to get a transfer permission - /// from the item type (`T`) owner on purchase attempt. - public struct TransferRequest { - /// The ID of the transferred item. Although the `T` has no - /// constraints, the main use case for this module is to work - /// with Objects. - item: ID, - /// Amount of SUI paid for the item. Can be used to - /// calculate the fee / transfer policy enforcement. - paid: u64, - /// The ID of the Kiosk / Safe the object is being sold from. - /// Can be used by the TransferPolicy implementors. - from: ID, - /// Collected Receipts. Used to verify that all of the rules - /// were followed and `TransferRequest` can be confirmed. - receipts: VecSet - } - - /// A unique capability that allows the owner of the `T` to authorize - /// transfers. Can only be created with the `Publisher` object. Although - /// there's no limitation to how many policies can be created, for most - /// of the cases there's no need to create more than one since any of the - /// policies can be used to confirm the `TransferRequest`. - public struct TransferPolicy has key, store { - id: UID, - /// The Balance of the `TransferPolicy` which collects `SUI`. - /// By default, transfer policy does not collect anything , and it's - /// a matter of an implementation of a specific rule - whether to add - /// to balance and how much. - balance: Balance, - /// Set of types of attached rules - used to verify `receipts` when - /// a `TransferRequest` is received in `confirm_request` function. - /// - /// Additionally provides a way to look up currently attached Rules. - rules: VecSet - } - - /// A Capability granting the owner permission to add/remove rules as well - /// as to `withdraw` and `destroy_and_withdraw` the `TransferPolicy`. - public struct TransferPolicyCap has key, store { - id: UID, - policy_id: ID - } - - /// Event that is emitted when a publisher creates a new `TransferPolicyCap` - /// making the discoverability and tracking the supported types easier. - public struct TransferPolicyCreated has copy, drop { id: ID } - - /// Event that is emitted when a publisher destroys a `TransferPolicyCap`. - /// Allows for tracking supported policies. - public struct TransferPolicyDestroyed has copy, drop { id: ID } - - /// Key to store "Rule" configuration for a specific `TransferPolicy`. - public struct RuleKey has copy, store, drop {} - - /// Construct a new `TransferRequest` hot potato which requires an - /// approving action from the creator to be destroyed / resolved. Once - /// created, it must be confirmed in the `confirm_request` call otherwise - /// the transaction will fail. - public fun new_request( - item: ID, paid: u64, from: ID - ): TransferRequest { - TransferRequest { item, paid, from, receipts: vec_set::empty() } - } - - /// Register a type in the Kiosk system and receive a `TransferPolicy` and - /// a `TransferPolicyCap` for the type. The `TransferPolicy` is required to - /// confirm kiosk deals for the `T`. If there's no `TransferPolicy` - /// available for use, the type can not be traded in kiosks. - public fun new( - pub: &Publisher, ctx: &mut TxContext - ): (TransferPolicy, TransferPolicyCap) { - assert!(package::from_package(pub), 0); - let id = object::new(ctx); - let policy_id = id.to_inner(); - - event::emit(TransferPolicyCreated { id: policy_id }); - - ( - TransferPolicy { id, rules: vec_set::empty(), balance: balance::zero() }, - TransferPolicyCap { id: object::new(ctx), policy_id } - ) - } - - #[allow(lint(self_transfer, share_owned))] - /// Initialize the Transfer Policy in the default scenario: Create and share - /// the `TransferPolicy`, transfer `TransferPolicyCap` to the transaction - /// sender. - entry fun default(pub: &Publisher, ctx: &mut TxContext) { - let (policy, cap) = new(pub, ctx); - sui::transfer::share_object(policy); - sui::transfer::transfer(cap, ctx.sender()); - } - - /// Withdraw some amount of profits from the `TransferPolicy`. If amount - /// is not specified, all profits are withdrawn. - public fun withdraw( - self: &mut TransferPolicy, - cap: &TransferPolicyCap, - amount: Option, - ctx: &mut TxContext - ): Coin { - assert!(object::id(self) == cap.policy_id, ENotOwner); - - let amount = if (amount.is_some()) { - let amt = amount.destroy_some(); - assert!(amt <= self.balance.value(), ENotEnough); - amt - } else { - self.balance.value() - }; - - coin::take(&mut self.balance, amount, ctx) - } - - /// Destroy a TransferPolicyCap. - /// Can be performed by any party as long as they own it. - public fun destroy_and_withdraw( - self: TransferPolicy, cap: TransferPolicyCap, ctx: &mut TxContext - ): Coin { - assert!(object::id(&self) == cap.policy_id, ENotOwner); - - let TransferPolicyCap { id: cap_id, policy_id } = cap; - let TransferPolicy { id, rules: _, balance } = self; - - id.delete(); - cap_id.delete(); - event::emit(TransferPolicyDestroyed { id: policy_id }); - balance.into_coin(ctx) - } - - /// Allow a `TransferRequest` for the type `T`. The call is protected - /// by the type constraint, as only the publisher of the `T` can get - /// `TransferPolicy`. - /// - /// Note: unless there's a policy for `T` to allow transfers, - /// Kiosk trades will not be possible. - public fun confirm_request( - self: &TransferPolicy, request: TransferRequest - ): (ID, u64, ID) { - let TransferRequest { item, paid, from, receipts } = request; - let mut completed = receipts.into_keys(); - let mut total = completed.length(); - - assert!(total == self.rules.size(), EPolicyNotSatisfied); - - while (total > 0) { - let rule_type = completed.pop_back(); - assert!(self.rules.contains(&rule_type), EIllegalRule); - total = total - 1; - }; - - (item, paid, from) - } - - // === Rules Logic === - - /// Add a custom Rule to the `TransferPolicy`. Once set, `TransferRequest` must - /// receive a confirmation of the rule executed so the hot potato can be unpacked. - /// - /// - T: the type to which TransferPolicy is applied. - /// - Rule: the witness type for the Custom rule - /// - Config: a custom configuration for the rule +module sui::transfer_policy; + +use std::type_name::{Self, TypeName}; +use sui::balance::{Self, Balance}; +use sui::coin::{Self, Coin}; +use sui::dynamic_field as df; +use sui::event; +use sui::package::{Self, Publisher}; +use sui::sui::SUI; +use sui::vec_set::{Self, VecSet}; + +/// The number of receipts does not match the `TransferPolicy` requirement. +const EPolicyNotSatisfied: u64 = 0; +/// A completed rule is not set in the `TransferPolicy`. +const EIllegalRule: u64 = 1; +/// A Rule is not set. +const EUnknownRequirement: u64 = 2; +/// Attempting to create a Rule that is already set. +const ERuleAlreadySet: u64 = 3; +/// Trying to `withdraw` or `close_and_withdraw` with a wrong Cap. +const ENotOwner: u64 = 4; +/// Trying to `withdraw` more than there is. +const ENotEnough: u64 = 5; + +/// A "Hot Potato" forcing the buyer to get a transfer permission +/// from the item type (`T`) owner on purchase attempt. +public struct TransferRequest { + /// The ID of the transferred item. Although the `T` has no + /// constraints, the main use case for this module is to work + /// with Objects. + item: ID, + /// Amount of SUI paid for the item. Can be used to + /// calculate the fee / transfer policy enforcement. + paid: u64, + /// The ID of the Kiosk / Safe the object is being sold from. + /// Can be used by the TransferPolicy implementors. + from: ID, + /// Collected Receipts. Used to verify that all of the rules + /// were followed and `TransferRequest` can be confirmed. + receipts: VecSet, +} + +/// A unique capability that allows the owner of the `T` to authorize +/// transfers. Can only be created with the `Publisher` object. Although +/// there's no limitation to how many policies can be created, for most +/// of the cases there's no need to create more than one since any of the +/// policies can be used to confirm the `TransferRequest`. +public struct TransferPolicy has key, store { + id: UID, + /// The Balance of the `TransferPolicy` which collects `SUI`. + /// By default, transfer policy does not collect anything , and it's + /// a matter of an implementation of a specific rule - whether to add + /// to balance and how much. + balance: Balance, + /// Set of types of attached rules - used to verify `receipts` when + /// a `TransferRequest` is received in `confirm_request` function. /// - /// Config requires `drop` to allow creators to remove any policy at any moment, - /// even if graceful unpacking has not been implemented in a "rule module". - public fun add_rule( - _: Rule, policy: &mut TransferPolicy, cap: &TransferPolicyCap, cfg: Config - ) { - assert!(object::id(policy) == cap.policy_id, ENotOwner); - assert!(!has_rule(policy), ERuleAlreadySet); - df::add(&mut policy.id, RuleKey {}, cfg); - policy.rules.insert(type_name::get()) - } - - /// Get the custom Config for the Rule (can be only one per "Rule" type). - public fun get_rule( - _: Rule, policy: &TransferPolicy) - : &Config { - df::borrow(&policy.id, RuleKey {}) - } - - /// Add some `SUI` to the balance of a `TransferPolicy`. - public fun add_to_balance( - _: Rule, policy: &mut TransferPolicy, coin: Coin - ) { - assert!(has_rule(policy), EUnknownRequirement); - coin::put(&mut policy.balance, coin) - } - - /// Adds a `Receipt` to the `TransferRequest`, unblocking the request and - /// confirming that the policy requirements are satisfied. - public fun add_receipt( - _: Rule, request: &mut TransferRequest - ) { - request.receipts.insert(type_name::get()) - } - - /// Check whether a custom rule has been added to the `TransferPolicy`. - public fun has_rule(policy: &TransferPolicy): bool { - df::exists_(&policy.id, RuleKey {}) - } - - /// Remove the Rule from the `TransferPolicy`. - public fun remove_rule( - policy: &mut TransferPolicy, cap: &TransferPolicyCap - ) { - assert!(object::id(policy) == cap.policy_id, ENotOwner); - let _: Config = df::remove(&mut policy.id, RuleKey {}); - policy.rules.remove(&type_name::get()); - } - - // === Fields access: TransferPolicy === - - /// Allows reading custom attachments to the `TransferPolicy` if there are any. - public fun uid(self: &TransferPolicy): &UID { &self.id } - - /// Get a mutable reference to the `self.id` to enable custom attachments - /// to the `TransferPolicy`. - public fun uid_mut_as_owner( - self: &mut TransferPolicy, cap: &TransferPolicyCap, - ): &mut UID { - assert!(object::id(self) == cap.policy_id, ENotOwner); - &mut self.id - } - - /// Read the `rules` field from the `TransferPolicy`. - public fun rules(self: &TransferPolicy): &VecSet { - &self.rules - } - - // === Fields access: TransferRequest === - - /// Get the `item` field of the `TransferRequest`. - public fun item(self: &TransferRequest): ID { self.item } - - /// Get the `paid` field of the `TransferRequest`. - public fun paid(self: &TransferRequest): u64 { self.paid } - - /// Get the `from` field of the `TransferRequest`. - public fun from(self: &TransferRequest): ID { self.from } - - // === Tests === - - #[test_only] - /// Create a new TransferPolicy for testing purposes. - public fun new_for_testing(ctx: &mut TxContext): (TransferPolicy, TransferPolicyCap) { - let id = object::new(ctx); - let policy_id = id.to_inner(); - - ( - TransferPolicy { id, rules: vec_set::empty(), balance: balance::zero() }, - TransferPolicyCap { id: object::new(ctx), policy_id } - ) - } + /// Additionally provides a way to look up currently attached Rules. + rules: VecSet, +} + +/// A Capability granting the owner permission to add/remove rules as well +/// as to `withdraw` and `destroy_and_withdraw` the `TransferPolicy`. +public struct TransferPolicyCap has key, store { + id: UID, + policy_id: ID, +} + +/// Event that is emitted when a publisher creates a new `TransferPolicyCap` +/// making the discoverability and tracking the supported types easier. +public struct TransferPolicyCreated has copy, drop { id: ID } + +/// Event that is emitted when a publisher destroys a `TransferPolicyCap`. +/// Allows for tracking supported policies. +public struct TransferPolicyDestroyed has copy, drop { id: ID } + +/// Key to store "Rule" configuration for a specific `TransferPolicy`. +public struct RuleKey has copy, store, drop {} + +/// Construct a new `TransferRequest` hot potato which requires an +/// approving action from the creator to be destroyed / resolved. Once +/// created, it must be confirmed in the `confirm_request` call otherwise +/// the transaction will fail. +public fun new_request(item: ID, paid: u64, from: ID): TransferRequest { + TransferRequest { item, paid, from, receipts: vec_set::empty() } +} + +/// Register a type in the Kiosk system and receive a `TransferPolicy` and +/// a `TransferPolicyCap` for the type. The `TransferPolicy` is required to +/// confirm kiosk deals for the `T`. If there's no `TransferPolicy` +/// available for use, the type can not be traded in kiosks. +public fun new(pub: &Publisher, ctx: &mut TxContext): (TransferPolicy, TransferPolicyCap) { + assert!(package::from_package(pub), 0); + let id = object::new(ctx); + let policy_id = id.to_inner(); + + event::emit(TransferPolicyCreated { id: policy_id }); + + ( + TransferPolicy { id, rules: vec_set::empty(), balance: balance::zero() }, + TransferPolicyCap { id: object::new(ctx), policy_id }, + ) +} + +#[allow(lint(self_transfer, share_owned))] +/// Initialize the Transfer Policy in the default scenario: Create and share +/// the `TransferPolicy`, transfer `TransferPolicyCap` to the transaction +/// sender. +entry fun default(pub: &Publisher, ctx: &mut TxContext) { + let (policy, cap) = new(pub, ctx); + sui::transfer::share_object(policy); + sui::transfer::transfer(cap, ctx.sender()); +} + +/// Withdraw some amount of profits from the `TransferPolicy`. If amount +/// is not specified, all profits are withdrawn. +public fun withdraw( + self: &mut TransferPolicy, + cap: &TransferPolicyCap, + amount: Option, + ctx: &mut TxContext, +): Coin { + assert!(object::id(self) == cap.policy_id, ENotOwner); + + let amount = if (amount.is_some()) { + let amt = amount.destroy_some(); + assert!(amt <= self.balance.value(), ENotEnough); + amt + } else { + self.balance.value() + }; + + coin::take(&mut self.balance, amount, ctx) +} + +/// Destroy a TransferPolicyCap. +/// Can be performed by any party as long as they own it. +public fun destroy_and_withdraw( + self: TransferPolicy, + cap: TransferPolicyCap, + ctx: &mut TxContext, +): Coin { + assert!(object::id(&self) == cap.policy_id, ENotOwner); + + let TransferPolicyCap { id: cap_id, policy_id } = cap; + let TransferPolicy { id, rules: _, balance } = self; + + id.delete(); + cap_id.delete(); + event::emit(TransferPolicyDestroyed { id: policy_id }); + balance.into_coin(ctx) +} + +/// Allow a `TransferRequest` for the type `T`. The call is protected +/// by the type constraint, as only the publisher of the `T` can get +/// `TransferPolicy`. +/// +/// Note: unless there's a policy for `T` to allow transfers, +/// Kiosk trades will not be possible. +public fun confirm_request( + self: &TransferPolicy, + request: TransferRequest, +): (ID, u64, ID) { + let TransferRequest { item, paid, from, receipts } = request; + let mut completed = receipts.into_keys(); + let mut total = completed.length(); + + assert!(total == self.rules.size(), EPolicyNotSatisfied); + + while (total > 0) { + let rule_type = completed.pop_back(); + assert!(self.rules.contains(&rule_type), EIllegalRule); + total = total - 1; + }; + + (item, paid, from) +} + +// === Rules Logic === + +/// Add a custom Rule to the `TransferPolicy`. Once set, `TransferRequest` must +/// receive a confirmation of the rule executed so the hot potato can be unpacked. +/// +/// - T: the type to which TransferPolicy is applied. +/// - Rule: the witness type for the Custom rule +/// - Config: a custom configuration for the rule +/// +/// Config requires `drop` to allow creators to remove any policy at any moment, +/// even if graceful unpacking has not been implemented in a "rule module". +public fun add_rule( + _: Rule, + policy: &mut TransferPolicy, + cap: &TransferPolicyCap, + cfg: Config, +) { + assert!(object::id(policy) == cap.policy_id, ENotOwner); + assert!(!has_rule(policy), ERuleAlreadySet); + df::add(&mut policy.id, RuleKey {}, cfg); + policy.rules.insert(type_name::get()) +} + +/// Get the custom Config for the Rule (can be only one per "Rule" type). +public fun get_rule( + _: Rule, + policy: &TransferPolicy, +): &Config { + df::borrow(&policy.id, RuleKey {}) +} + +/// Add some `SUI` to the balance of a `TransferPolicy`. +public fun add_to_balance(_: Rule, policy: &mut TransferPolicy, coin: Coin) { + assert!(has_rule(policy), EUnknownRequirement); + coin::put(&mut policy.balance, coin) +} + +/// Adds a `Receipt` to the `TransferRequest`, unblocking the request and +/// confirming that the policy requirements are satisfied. +public fun add_receipt(_: Rule, request: &mut TransferRequest) { + request.receipts.insert(type_name::get()) +} + +/// Check whether a custom rule has been added to the `TransferPolicy`. +public fun has_rule(policy: &TransferPolicy): bool { + df::exists_(&policy.id, RuleKey {}) +} + +/// Remove the Rule from the `TransferPolicy`. +public fun remove_rule( + policy: &mut TransferPolicy, + cap: &TransferPolicyCap, +) { + assert!(object::id(policy) == cap.policy_id, ENotOwner); + let _: Config = df::remove(&mut policy.id, RuleKey {}); + policy.rules.remove(&type_name::get()); +} + +// === Fields access: TransferPolicy === + +/// Allows reading custom attachments to the `TransferPolicy` if there are any. +public fun uid(self: &TransferPolicy): &UID { &self.id } + +/// Get a mutable reference to the `self.id` to enable custom attachments +/// to the `TransferPolicy`. +public fun uid_mut_as_owner(self: &mut TransferPolicy, cap: &TransferPolicyCap): &mut UID { + assert!(object::id(self) == cap.policy_id, ENotOwner); + &mut self.id +} + +/// Read the `rules` field from the `TransferPolicy`. +public fun rules(self: &TransferPolicy): &VecSet { + &self.rules +} + +// === Fields access: TransferRequest === + +/// Get the `item` field of the `TransferRequest`. +public fun item(self: &TransferRequest): ID { self.item } + +/// Get the `paid` field of the `TransferRequest`. +public fun paid(self: &TransferRequest): u64 { self.paid } + +/// Get the `from` field of the `TransferRequest`. +public fun from(self: &TransferRequest): ID { self.from } + +// === Tests === + +#[test_only] +/// Create a new TransferPolicy for testing purposes. +public fun new_for_testing(ctx: &mut TxContext): (TransferPolicy, TransferPolicyCap) { + let id = object::new(ctx); + let policy_id = id.to_inner(); + + ( + TransferPolicy { id, rules: vec_set::empty(), balance: balance::zero() }, + TransferPolicyCap { id: object::new(ctx), policy_id }, + ) } diff --git a/crates/sui-framework/packages/sui-framework/sources/linked_table.move b/crates/sui-framework/packages/sui-framework/sources/linked_table.move index d7e91b1c78e1f..31bb50f0e4e69 100644 --- a/crates/sui-framework/packages/sui-framework/sources/linked_table.move +++ b/crates/sui-framework/packages/sui-framework/sources/linked_table.move @@ -3,197 +3,197 @@ /// Similar to `sui::table` but the values are linked together, allowing for ordered insertion and /// removal -module sui::linked_table { - use sui::dynamic_field as field; - - // Attempted to destroy a non-empty table - const ETableNotEmpty: u64 = 0; - // Attempted to remove the front or back of an empty table - const ETableIsEmpty: u64 = 1; - - public struct LinkedTable has key, store { - /// the ID of this table - id: UID, - /// the number of key-value pairs in the table - size: u64, - /// the front of the table, i.e. the key of the first entry - head: Option, - /// the back of the table, i.e. the key of the last entry - tail: Option, - } +module sui::linked_table; + +use sui::dynamic_field as field; + +// Attempted to destroy a non-empty table +const ETableNotEmpty: u64 = 0; +// Attempted to remove the front or back of an empty table +const ETableIsEmpty: u64 = 1; + +public struct LinkedTable has key, store { + /// the ID of this table + id: UID, + /// the number of key-value pairs in the table + size: u64, + /// the front of the table, i.e. the key of the first entry + head: Option, + /// the back of the table, i.e. the key of the last entry + tail: Option, +} - public struct Node has store { - /// the previous key - prev: Option, - /// the next key - next: Option, - /// the value being stored - value: V - } +public struct Node has store { + /// the previous key + prev: Option, + /// the next key + next: Option, + /// the value being stored + value: V, +} - /// Creates a new, empty table - public fun new(ctx: &mut TxContext): LinkedTable { - LinkedTable { - id: object::new(ctx), - size: 0, - head: option::none(), - tail: option::none(), - } +/// Creates a new, empty table +public fun new(ctx: &mut TxContext): LinkedTable { + LinkedTable { + id: object::new(ctx), + size: 0, + head: option::none(), + tail: option::none(), } +} - /// Returns the key for the first element in the table, or None if the table is empty - public fun front(table: &LinkedTable): &Option { - &table.head - } +/// Returns the key for the first element in the table, or None if the table is empty +public fun front(table: &LinkedTable): &Option { + &table.head +} - /// Returns the key for the last element in the table, or None if the table is empty - public fun back(table: &LinkedTable): &Option { - &table.tail - } +/// Returns the key for the last element in the table, or None if the table is empty +public fun back(table: &LinkedTable): &Option { + &table.tail +} - /// Inserts a key-value pair at the front of the table, i.e. the newly inserted pair will be - /// the first element in the table - /// Aborts with `sui::dynamic_field::EFieldAlreadyExists` if the table already has an entry with - /// that key `k: K`. - public fun push_front( - table: &mut LinkedTable, - k: K, - value: V, - ) { - let old_head = table.head.swap_or_fill(k); - if (table.tail.is_none()) table.tail.fill(k); - let prev = option::none(); - let next = if (old_head.is_some()) { - let old_head_k = old_head.destroy_some(); - field::borrow_mut>(&mut table.id, old_head_k).prev = option::some(k); - option::some(old_head_k) - } else { - option::none() - }; - field::add(&mut table.id, k, Node { prev, next, value }); - table.size = table.size + 1; - } +/// Inserts a key-value pair at the front of the table, i.e. the newly inserted pair will be +/// the first element in the table +/// Aborts with `sui::dynamic_field::EFieldAlreadyExists` if the table already has an entry with +/// that key `k: K`. +public fun push_front( + table: &mut LinkedTable, + k: K, + value: V, +) { + let old_head = table.head.swap_or_fill(k); + if (table.tail.is_none()) table.tail.fill(k); + let prev = option::none(); + let next = if (old_head.is_some()) { + let old_head_k = old_head.destroy_some(); + field::borrow_mut>(&mut table.id, old_head_k).prev = option::some(k); + option::some(old_head_k) + } else { + option::none() + }; + field::add(&mut table.id, k, Node { prev, next, value }); + table.size = table.size + 1; +} - /// Inserts a key-value pair at the back of the table, i.e. the newly inserted pair will be - /// the last element in the table - /// Aborts with `sui::dynamic_field::EFieldAlreadyExists` if the table already has an entry with - /// that key `k: K`. - public fun push_back( - table: &mut LinkedTable, - k: K, - value: V, - ) { - if (table.head.is_none()) table.head.fill(k); - let old_tail = table.tail.swap_or_fill(k); - let prev = if (old_tail.is_some()) { - let old_tail_k = old_tail.destroy_some(); - field::borrow_mut>(&mut table.id, old_tail_k).next = option::some(k); - option::some(old_tail_k) - } else { - option::none() - }; - let next = option::none(); - field::add(&mut table.id, k, Node { prev, next, value }); - table.size = table.size + 1; - } +/// Inserts a key-value pair at the back of the table, i.e. the newly inserted pair will be +/// the last element in the table +/// Aborts with `sui::dynamic_field::EFieldAlreadyExists` if the table already has an entry with +/// that key `k: K`. +public fun push_back( + table: &mut LinkedTable, + k: K, + value: V, +) { + if (table.head.is_none()) table.head.fill(k); + let old_tail = table.tail.swap_or_fill(k); + let prev = if (old_tail.is_some()) { + let old_tail_k = old_tail.destroy_some(); + field::borrow_mut>(&mut table.id, old_tail_k).next = option::some(k); + option::some(old_tail_k) + } else { + option::none() + }; + let next = option::none(); + field::add(&mut table.id, k, Node { prev, next, value }); + table.size = table.size + 1; +} - #[syntax(index)] - /// Immutable borrows the value associated with the key in the table `table: &LinkedTable`. - /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with - /// that key `k: K`. - public fun borrow(table: &LinkedTable, k: K): &V { - &field::borrow>(&table.id, k).value - } +#[syntax(index)] +/// Immutable borrows the value associated with the key in the table `table: &LinkedTable`. +/// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with +/// that key `k: K`. +public fun borrow(table: &LinkedTable, k: K): &V { + &field::borrow>(&table.id, k).value +} - #[syntax(index)] - /// Mutably borrows the value associated with the key in the table `table: &mut LinkedTable`. - /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with - /// that key `k: K`. - public fun borrow_mut( - table: &mut LinkedTable, - k: K, - ): &mut V { - &mut field::borrow_mut>(&mut table.id, k).value - } +#[syntax(index)] +/// Mutably borrows the value associated with the key in the table `table: &mut LinkedTable`. +/// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with +/// that key `k: K`. +public fun borrow_mut( + table: &mut LinkedTable, + k: K, +): &mut V { + &mut field::borrow_mut>(&mut table.id, k).value +} - /// Borrows the key for the previous entry of the specified key `k: K` in the table - /// `table: &LinkedTable`. Returns None if the entry does not have a predecessor. - /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with - /// that key `k: K` - public fun prev(table: &LinkedTable, k: K): &Option { - &field::borrow>(&table.id, k).prev - } +/// Borrows the key for the previous entry of the specified key `k: K` in the table +/// `table: &LinkedTable`. Returns None if the entry does not have a predecessor. +/// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with +/// that key `k: K` +public fun prev(table: &LinkedTable, k: K): &Option { + &field::borrow>(&table.id, k).prev +} - /// Borrows the key for the next entry of the specified key `k: K` in the table - /// `table: &LinkedTable`. Returns None if the entry does not have a predecessor. - /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with - /// that key `k: K` - public fun next(table: &LinkedTable, k: K): &Option { - &field::borrow>(&table.id, k).next - } +/// Borrows the key for the next entry of the specified key `k: K` in the table +/// `table: &LinkedTable`. Returns None if the entry does not have a predecessor. +/// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with +/// that key `k: K` +public fun next(table: &LinkedTable, k: K): &Option { + &field::borrow>(&table.id, k).next +} - /// Removes the key-value pair in the table `table: &mut LinkedTable` and returns the value. - /// This splices the element out of the ordering. - /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with - /// that key `k: K`. Note: this is also what happens when the table is empty. - public fun remove(table: &mut LinkedTable, k: K): V { - let Node { prev, next, value } = field::remove(&mut table.id, k); - table.size = table.size - 1; - if (prev.is_some()) { - field::borrow_mut>(&mut table.id, *prev.borrow()).next = next - }; - if (next.is_some()) { - field::borrow_mut>(&mut table.id, *next.borrow()).prev = prev - }; - if (table.head.borrow() == &k) table.head = next; - if (table.tail.borrow() == &k) table.tail = prev; - value - } +/// Removes the key-value pair in the table `table: &mut LinkedTable` and returns the value. +/// This splices the element out of the ordering. +/// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with +/// that key `k: K`. Note: this is also what happens when the table is empty. +public fun remove(table: &mut LinkedTable, k: K): V { + let Node { prev, next, value } = field::remove(&mut table.id, k); + table.size = table.size - 1; + if (prev.is_some()) { + field::borrow_mut>(&mut table.id, *prev.borrow()).next = next + }; + if (next.is_some()) { + field::borrow_mut>(&mut table.id, *next.borrow()).prev = prev + }; + if (table.head.borrow() == &k) table.head = next; + if (table.tail.borrow() == &k) table.tail = prev; + value +} - /// Removes the front of the table `table: &mut LinkedTable` and returns the value. - /// Aborts with `ETableIsEmpty` if the table is empty - public fun pop_front(table: &mut LinkedTable): (K, V) { - assert!(table.head.is_some(), ETableIsEmpty); - let head = *table.head.borrow(); - (head, table.remove(head)) - } +/// Removes the front of the table `table: &mut LinkedTable` and returns the value. +/// Aborts with `ETableIsEmpty` if the table is empty +public fun pop_front(table: &mut LinkedTable): (K, V) { + assert!(table.head.is_some(), ETableIsEmpty); + let head = *table.head.borrow(); + (head, table.remove(head)) +} - /// Removes the back of the table `table: &mut LinkedTable` and returns the value. - /// Aborts with `ETableIsEmpty` if the table is empty - public fun pop_back(table: &mut LinkedTable): (K, V) { - assert!(table.tail.is_some(), ETableIsEmpty); - let tail = *table.tail.borrow(); - (tail, table.remove(tail)) - } +/// Removes the back of the table `table: &mut LinkedTable` and returns the value. +/// Aborts with `ETableIsEmpty` if the table is empty +public fun pop_back(table: &mut LinkedTable): (K, V) { + assert!(table.tail.is_some(), ETableIsEmpty); + let tail = *table.tail.borrow(); + (tail, table.remove(tail)) +} - /// Returns true iff there is a value associated with the key `k: K` in table - /// `table: &LinkedTable` - public fun contains(table: &LinkedTable, k: K): bool { - field::exists_with_type>(&table.id, k) - } +/// Returns true iff there is a value associated with the key `k: K` in table +/// `table: &LinkedTable` +public fun contains(table: &LinkedTable, k: K): bool { + field::exists_with_type>(&table.id, k) +} - /// Returns the size of the table, the number of key-value pairs - public fun length(table: &LinkedTable): u64 { - table.size - } +/// Returns the size of the table, the number of key-value pairs +public fun length(table: &LinkedTable): u64 { + table.size +} - /// Returns true iff the table is empty (if `length` returns `0`) - public fun is_empty(table: &LinkedTable): bool { - table.size == 0 - } +/// Returns true iff the table is empty (if `length` returns `0`) +public fun is_empty(table: &LinkedTable): bool { + table.size == 0 +} - /// Destroys an empty table - /// Aborts with `ETableNotEmpty` if the table still contains values - public fun destroy_empty(table: LinkedTable) { - let LinkedTable { id, size, head: _, tail: _ } = table; - assert!(size == 0, ETableNotEmpty); - id.delete() - } +/// Destroys an empty table +/// Aborts with `ETableNotEmpty` if the table still contains values +public fun destroy_empty(table: LinkedTable) { + let LinkedTable { id, size, head: _, tail: _ } = table; + assert!(size == 0, ETableNotEmpty); + id.delete() +} - /// Drop a possibly non-empty table. - /// Usable only if the value type `V` has the `drop` ability - public fun drop(table: LinkedTable) { - let LinkedTable { id, size: _, head: _, tail: _ } = table; - id.delete() - } +/// Drop a possibly non-empty table. +/// Usable only if the value type `V` has the `drop` ability +public fun drop(table: LinkedTable) { + let LinkedTable { id, size: _, head: _, tail: _ } = table; + id.delete() } diff --git a/crates/sui-framework/packages/sui-framework/sources/math.move b/crates/sui-framework/packages/sui-framework/sources/math.move index f98a7d7c5eb72..2ad382cc8304d 100644 --- a/crates/sui-framework/packages/sui-framework/sources/math.move +++ b/crates/sui-framework/packages/sui-framework/sources/math.move @@ -3,40 +3,39 @@ /// DEPRECATED, use the each integer type's individual module instead, e.g. `std::u64` #[deprecated(note = b"Use the each integer type's individual module instead, e.g. `std::u64`")] -module sui::math { - - /// DEPRECATED, use `std::u64::max` instead - public fun max(x: u64, y: u64): u64 { - x.max(y) - } - - /// DEPRECATED, use `std::u64::min` instead - public fun min(x: u64, y: u64): u64 { - x.min(y) - } - - /// DEPRECATED, use `std::u64::diff` instead - public fun diff(x: u64, y: u64): u64 { - x.diff(y) - } - - /// DEPRECATED, use `std::u64::pow` instead - public fun pow(base: u64, exponent: u8): u64 { - base.pow(exponent) - } - - /// DEPRECATED, use `std::u64::sqrt` instead - public fun sqrt(x: u64): u64 { - x.sqrt() - } - - /// DEPRECATED, use `std::u128::sqrt` instead - public fun sqrt_u128(x: u128): u128 { - x.sqrt() - } - - /// DEPRECATED, use `std::u64::divide_and_round_up` instead - public fun divide_and_round_up(x: u64, y: u64): u64 { - x.divide_and_round_up(y) - } +module sui::math; + +/// DEPRECATED, use `std::u64::max` instead +public fun max(x: u64, y: u64): u64 { + x.max(y) +} + +/// DEPRECATED, use `std::u64::min` instead +public fun min(x: u64, y: u64): u64 { + x.min(y) +} + +/// DEPRECATED, use `std::u64::diff` instead +public fun diff(x: u64, y: u64): u64 { + x.diff(y) +} + +/// DEPRECATED, use `std::u64::pow` instead +public fun pow(base: u64, exponent: u8): u64 { + base.pow(exponent) +} + +/// DEPRECATED, use `std::u64::sqrt` instead +public fun sqrt(x: u64): u64 { + x.sqrt() +} + +/// DEPRECATED, use `std::u128::sqrt` instead +public fun sqrt_u128(x: u128): u128 { + x.sqrt() +} + +/// DEPRECATED, use `std::u64::divide_and_round_up` instead +public fun divide_and_round_up(x: u64, y: u64): u64 { + x.divide_and_round_up(y) } diff --git a/crates/sui-framework/packages/sui-framework/sources/object.move b/crates/sui-framework/packages/sui-framework/sources/object.move index 5b1a388bb2450..8bc0c67c38fc8 100644 --- a/crates/sui-framework/packages/sui-framework/sources/object.move +++ b/crates/sui-framework/packages/sui-framework/sources/object.move @@ -2,233 +2,232 @@ // SPDX-License-Identifier: Apache-2.0 /// Sui object identifiers -module sui::object { - use std::bcs; - use sui::address; +module sui::object; - /// Allows calling `.to_address` on an `ID` to get an `address`. - public use fun id_to_address as ID.to_address; +use std::bcs; +use sui::address; - /// Allows calling `.to_bytes` on an `ID` to get a `vector`. - public use fun id_to_bytes as ID.to_bytes; +/// Allows calling `.to_address` on an `ID` to get an `address`. +public use fun id_to_address as ID.to_address; - /// Allows calling `.as_inner` on a `UID` to get an `&ID`. - public use fun uid_as_inner as UID.as_inner; +/// Allows calling `.to_bytes` on an `ID` to get a `vector`. +public use fun id_to_bytes as ID.to_bytes; - /// Allows calling `.to_inner` on a `UID` to get an `ID`. - public use fun uid_to_inner as UID.to_inner; +/// Allows calling `.as_inner` on a `UID` to get an `&ID`. +public use fun uid_as_inner as UID.as_inner; - /// Allows calling `.to_address` on a `UID` to get an `address`. - public use fun uid_to_address as UID.to_address; +/// Allows calling `.to_inner` on a `UID` to get an `ID`. +public use fun uid_to_inner as UID.to_inner; - /// Allows calling `.to_bytes` on a `UID` to get a `vector`. - public use fun uid_to_bytes as UID.to_bytes; +/// Allows calling `.to_address` on a `UID` to get an `address`. +public use fun uid_to_address as UID.to_address; - /// The hardcoded ID for the singleton Sui System State Object. - const SUI_SYSTEM_STATE_OBJECT_ID: address = @0x5; +/// Allows calling `.to_bytes` on a `UID` to get a `vector`. +public use fun uid_to_bytes as UID.to_bytes; - /// The hardcoded ID for the singleton Clock Object. - const SUI_CLOCK_OBJECT_ID: address = @0x6; +/// The hardcoded ID for the singleton Sui System State Object. +const SUI_SYSTEM_STATE_OBJECT_ID: address = @0x5; - /// The hardcoded ID for the singleton AuthenticatorState Object. - const SUI_AUTHENTICATOR_STATE_ID: address = @0x7; +/// The hardcoded ID for the singleton Clock Object. +const SUI_CLOCK_OBJECT_ID: address = @0x6; - /// The hardcoded ID for the singleton Random Object. - const SUI_RANDOM_ID: address = @0x8; +/// The hardcoded ID for the singleton AuthenticatorState Object. +const SUI_AUTHENTICATOR_STATE_ID: address = @0x7; - /// The hardcoded ID for the singleton DenyList. - const SUI_DENY_LIST_OBJECT_ID: address = @0x403; +/// The hardcoded ID for the singleton Random Object. +const SUI_RANDOM_ID: address = @0x8; - /// The hardcoded ID for the Bridge Object. - const SUI_BRIDGE_ID: address = @0x9; +/// The hardcoded ID for the singleton DenyList. +const SUI_DENY_LIST_OBJECT_ID: address = @0x403; - /// Sender is not @0x0 the system address. - const ENotSystemAddress: u64 = 0; +/// The hardcoded ID for the Bridge Object. +const SUI_BRIDGE_ID: address = @0x9; - /// An object ID. This is used to reference Sui Objects. - /// This is *not* guaranteed to be globally unique--anyone can create an `ID` from a `UID` or - /// from an object, and ID's can be freely copied and dropped. - /// Here, the values are not globally unique because there can be multiple values of type `ID` - /// with the same underlying bytes. For example, `object::id(&obj)` can be called as many times - /// as you want for a given `obj`, and each `ID` value will be identical. - public struct ID has copy, drop, store { - // We use `address` instead of `vector` here because `address` has a more - // compact serialization. `address` is serialized as a BCS fixed-length sequence, - // which saves us the length prefix we would pay for if this were `vector`. - // See https://github.com/diem/bcs#fixed-and-variable-length-sequences. - bytes: address - } +/// Sender is not @0x0 the system address. +const ENotSystemAddress: u64 = 0; - /// Globally unique IDs that define an object's ID in storage. Any Sui Object, that is a struct - /// with the `key` ability, must have `id: UID` as its first field. - /// These are globally unique in the sense that no two values of type `UID` are ever equal, in - /// other words for any two values `id1: UID` and `id2: UID`, `id1` != `id2`. - /// This is a privileged type that can only be derived from a `TxContext`. - /// `UID` doesn't have the `drop` ability, so deleting a `UID` requires a call to `delete`. - public struct UID has store { - id: ID, - } +/// An object ID. This is used to reference Sui Objects. +/// This is *not* guaranteed to be globally unique--anyone can create an `ID` from a `UID` or +/// from an object, and ID's can be freely copied and dropped. +/// Here, the values are not globally unique because there can be multiple values of type `ID` +/// with the same underlying bytes. For example, `object::id(&obj)` can be called as many times +/// as you want for a given `obj`, and each `ID` value will be identical. +public struct ID has copy, drop, store { + // We use `address` instead of `vector` here because `address` has a more + // compact serialization. `address` is serialized as a BCS fixed-length sequence, + // which saves us the length prefix we would pay for if this were `vector`. + // See https://github.com/diem/bcs#fixed-and-variable-length-sequences. + bytes: address, +} - // === id === +/// Globally unique IDs that define an object's ID in storage. Any Sui Object, that is a struct +/// with the `key` ability, must have `id: UID` as its first field. +/// These are globally unique in the sense that no two values of type `UID` are ever equal, in +/// other words for any two values `id1: UID` and `id2: UID`, `id1` != `id2`. +/// This is a privileged type that can only be derived from a `TxContext`. +/// `UID` doesn't have the `drop` ability, so deleting a `UID` requires a call to `delete`. +public struct UID has store { + id: ID, +} - /// Get the raw bytes of a `ID` - public fun id_to_bytes(id: &ID): vector { - bcs::to_bytes(&id.bytes) - } +// === id === - /// Get the inner bytes of `id` as an address. - public fun id_to_address(id: &ID): address { - id.bytes - } +/// Get the raw bytes of a `ID` +public fun id_to_bytes(id: &ID): vector { + bcs::to_bytes(&id.bytes) +} - /// Make an `ID` from raw bytes. - public fun id_from_bytes(bytes: vector): ID { - address::from_bytes(bytes).to_id() - } +/// Get the inner bytes of `id` as an address. +public fun id_to_address(id: &ID): address { + id.bytes +} - /// Make an `ID` from an address. - public fun id_from_address(bytes: address): ID { - ID { bytes } - } +/// Make an `ID` from raw bytes. +public fun id_from_bytes(bytes: vector): ID { + address::from_bytes(bytes).to_id() +} - // === uid === +/// Make an `ID` from an address. +public fun id_from_address(bytes: address): ID { + ID { bytes } +} - #[allow(unused_function)] - /// Create the `UID` for the singleton `SuiSystemState` object. - /// This should only be called once from `sui_system`. - fun sui_system_state(ctx: &TxContext): UID { - assert!(ctx.sender() == @0x0, ENotSystemAddress); - UID { - id: ID { bytes: SUI_SYSTEM_STATE_OBJECT_ID }, - } - } +// === uid === - /// Create the `UID` for the singleton `Clock` object. - /// This should only be called once from `clock`. - public(package) fun clock(): UID { - UID { - id: ID { bytes: SUI_CLOCK_OBJECT_ID } - } +#[allow(unused_function)] +/// Create the `UID` for the singleton `SuiSystemState` object. +/// This should only be called once from `sui_system`. +fun sui_system_state(ctx: &TxContext): UID { + assert!(ctx.sender() == @0x0, ENotSystemAddress); + UID { + id: ID { bytes: SUI_SYSTEM_STATE_OBJECT_ID }, } +} - /// Create the `UID` for the singleton `AuthenticatorState` object. - /// This should only be called once from `authenticator_state`. - public(package) fun authenticator_state(): UID { - UID { - id: ID { bytes: SUI_AUTHENTICATOR_STATE_ID } - } +/// Create the `UID` for the singleton `Clock` object. +/// This should only be called once from `clock`. +public(package) fun clock(): UID { + UID { + id: ID { bytes: SUI_CLOCK_OBJECT_ID }, } +} - /// Create the `UID` for the singleton `Random` object. - /// This should only be called once from `random`. - public(package) fun randomness_state(): UID { - UID { - id: ID { bytes: SUI_RANDOM_ID } - } +/// Create the `UID` for the singleton `AuthenticatorState` object. +/// This should only be called once from `authenticator_state`. +public(package) fun authenticator_state(): UID { + UID { + id: ID { bytes: SUI_AUTHENTICATOR_STATE_ID }, } +} - /// Create the `UID` for the singleton `DenyList` object. - /// This should only be called once from `deny_list`. - public(package) fun sui_deny_list_object_id(): UID { - UID { - id: ID { bytes: SUI_DENY_LIST_OBJECT_ID } - } +/// Create the `UID` for the singleton `Random` object. +/// This should only be called once from `random`. +public(package) fun randomness_state(): UID { + UID { + id: ID { bytes: SUI_RANDOM_ID }, } +} - #[allow(unused_function)] - /// Create the `UID` for the singleton `Bridge` object. - /// This should only be called once from `bridge`. - fun bridge(): UID { - UID { - id: ID { bytes: SUI_BRIDGE_ID } - } +/// Create the `UID` for the singleton `DenyList` object. +/// This should only be called once from `deny_list`. +public(package) fun sui_deny_list_object_id(): UID { + UID { + id: ID { bytes: SUI_DENY_LIST_OBJECT_ID }, } +} - /// Get the inner `ID` of `uid` - public fun uid_as_inner(uid: &UID): &ID { - &uid.id +#[allow(unused_function)] +/// Create the `UID` for the singleton `Bridge` object. +/// This should only be called once from `bridge`. +fun bridge(): UID { + UID { + id: ID { bytes: SUI_BRIDGE_ID }, } +} - /// Get the raw bytes of a `uid`'s inner `ID` - public fun uid_to_inner(uid: &UID): ID { - uid.id - } +/// Get the inner `ID` of `uid` +public fun uid_as_inner(uid: &UID): &ID { + &uid.id +} - /// Get the raw bytes of a `UID` - public fun uid_to_bytes(uid: &UID): vector { - bcs::to_bytes(&uid.id.bytes) - } +/// Get the raw bytes of a `uid`'s inner `ID` +public fun uid_to_inner(uid: &UID): ID { + uid.id +} - /// Get the inner bytes of `id` as an address. - public fun uid_to_address(uid: &UID): address { - uid.id.bytes - } +/// Get the raw bytes of a `UID` +public fun uid_to_bytes(uid: &UID): vector { + bcs::to_bytes(&uid.id.bytes) +} - // === any object === +/// Get the inner bytes of `id` as an address. +public fun uid_to_address(uid: &UID): address { + uid.id.bytes +} - /// Create a new object. Returns the `UID` that must be stored in a Sui object. - /// This is the only way to create `UID`s. - public fun new(ctx: &mut TxContext): UID { - UID { - id: ID { bytes: ctx.fresh_object_address() }, - } - } +// === any object === - /// Delete the object and it's `UID`. This is the only way to eliminate a `UID`. - // This exists to inform Sui of object deletions. When an object - // gets unpacked, the programmer will have to do something with its - // `UID`. The implementation of this function emits a deleted - // system event so Sui knows to process the object deletion - public fun delete(id: UID) { - let UID { id: ID { bytes } } = id; - delete_impl(bytes) +/// Create a new object. Returns the `UID` that must be stored in a Sui object. +/// This is the only way to create `UID`s. +public fun new(ctx: &mut TxContext): UID { + UID { + id: ID { bytes: ctx.fresh_object_address() }, } +} - /// Get the underlying `ID` of `obj` - public fun id(obj: &T): ID { - borrow_uid(obj).id - } +/// Delete the object and it's `UID`. This is the only way to eliminate a `UID`. +// This exists to inform Sui of object deletions. When an object +// gets unpacked, the programmer will have to do something with its +// `UID`. The implementation of this function emits a deleted +// system event so Sui knows to process the object deletion +public fun delete(id: UID) { + let UID { id: ID { bytes } } = id; + delete_impl(bytes) +} - /// Borrow the underlying `ID` of `obj` - public fun borrow_id(obj: &T): &ID { - &borrow_uid(obj).id - } +/// Get the underlying `ID` of `obj` +public fun id(obj: &T): ID { + borrow_uid(obj).id +} - /// Get the raw bytes for the underlying `ID` of `obj` - public fun id_bytes(obj: &T): vector { - bcs::to_bytes(&borrow_uid(obj).id) - } +/// Borrow the underlying `ID` of `obj` +public fun borrow_id(obj: &T): &ID { + &borrow_uid(obj).id +} - /// Get the inner bytes for the underlying `ID` of `obj` - public fun id_address(obj: &T): address { - borrow_uid(obj).id.bytes - } +/// Get the raw bytes for the underlying `ID` of `obj` +public fun id_bytes(obj: &T): vector { + bcs::to_bytes(&borrow_uid(obj).id) +} - /// Get the `UID` for `obj`. - /// Safe because Sui has an extra bytecode verifier pass that forces every struct with - /// the `key` ability to have a distinguished `UID` field. - /// Cannot be made public as the access to `UID` for a given object must be privileged, and - /// restrictable in the object's module. - native fun borrow_uid(obj: &T): &UID; - - /// Generate a new UID specifically used for creating a UID from a hash - public(package) fun new_uid_from_hash(bytes: address): UID { - record_new_uid(bytes); - UID { id: ID { bytes } } - } +/// Get the inner bytes for the underlying `ID` of `obj` +public fun id_address(obj: &T): address { + borrow_uid(obj).id.bytes +} - // === internal functions === +/// Get the `UID` for `obj`. +/// Safe because Sui has an extra bytecode verifier pass that forces every struct with +/// the `key` ability to have a distinguished `UID` field. +/// Cannot be made public as the access to `UID` for a given object must be privileged, and +/// restrictable in the object's module. +native fun borrow_uid(obj: &T): &UID; + +/// Generate a new UID specifically used for creating a UID from a hash +public(package) fun new_uid_from_hash(bytes: address): UID { + record_new_uid(bytes); + UID { id: ID { bytes } } +} - // helper for delete - native fun delete_impl(id: address); +// === internal functions === - // marks newly created UIDs from hash - native fun record_new_uid(id: address); +// helper for delete +native fun delete_impl(id: address); - #[test_only] - /// Return the most recent created object ID. - public fun last_created(ctx: &TxContext): ID { - ID { bytes: ctx.last_created_object_id() } - } +// marks newly created UIDs from hash +native fun record_new_uid(id: address); +#[test_only] +/// Return the most recent created object ID. +public fun last_created(ctx: &TxContext): ID { + ID { bytes: ctx.last_created_object_id() } } diff --git a/crates/sui-framework/packages/sui-framework/sources/object_bag.move b/crates/sui-framework/packages/sui-framework/sources/object_bag.move index 2f01d8a4fc1a8..51ebbe3b586ed 100644 --- a/crates/sui-framework/packages/sui-framework/sources/object_bag.move +++ b/crates/sui-framework/packages/sui-framework/sources/object_bag.move @@ -5,98 +5,98 @@ /// `sui::bag`, the values bound to these dynamic fields _must_ be objects themselves. This allows /// for the objects to still exist in storage, which may be important for external tools. /// The difference is otherwise not observable from within Move. -module sui::object_bag { - use sui::dynamic_object_field as ofield; +module sui::object_bag; - // Attempted to destroy a non-empty bag - const EBagNotEmpty: u64 = 0; +use sui::dynamic_object_field as ofield; - public struct ObjectBag has key, store { - /// the ID of this bag - id: UID, - /// the number of key-value pairs in the bag - size: u64, - } +// Attempted to destroy a non-empty bag +const EBagNotEmpty: u64 = 0; - /// Creates a new, empty bag - public fun new(ctx: &mut TxContext): ObjectBag { - ObjectBag { - id: object::new(ctx), - size: 0, - } - } +public struct ObjectBag has key, store { + /// the ID of this bag + id: UID, + /// the number of key-value pairs in the bag + size: u64, +} - /// Adds a key-value pair to the bag `bag: &mut ObjectBag` - /// Aborts with `sui::dynamic_field::EFieldAlreadyExists` if the bag already has an entry with - /// that key `k: K`. - public fun add(bag: &mut ObjectBag, k: K, v: V) { - ofield::add(&mut bag.id, k, v); - bag.size = bag.size + 1; +/// Creates a new, empty bag +public fun new(ctx: &mut TxContext): ObjectBag { + ObjectBag { + id: object::new(ctx), + size: 0, } +} - #[syntax(index)] - /// Immutably borrows the value associated with the key in the bag `bag: &ObjectBag`. - /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the bag does not have an entry with - /// that key `k: K`. - /// Aborts with `sui::dynamic_field::EFieldTypeMismatch` if the bag has an entry for the key, but - /// the value does not have the specified type. - public fun borrow(bag: &ObjectBag, k: K): &V { - ofield::borrow(&bag.id, k) - } +/// Adds a key-value pair to the bag `bag: &mut ObjectBag` +/// Aborts with `sui::dynamic_field::EFieldAlreadyExists` if the bag already has an entry with +/// that key `k: K`. +public fun add(bag: &mut ObjectBag, k: K, v: V) { + ofield::add(&mut bag.id, k, v); + bag.size = bag.size + 1; +} - #[syntax(index)] - /// Mutably borrows the value associated with the key in the bag `bag: &mut ObjectBag`. - /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the bag does not have an entry with - /// that key `k: K`. - /// Aborts with `sui::dynamic_field::EFieldTypeMismatch` if the bag has an entry for the key, but - /// the value does not have the specified type. - public fun borrow_mut(bag: &mut ObjectBag, k: K): &mut V { - ofield::borrow_mut(&mut bag.id, k) - } +#[syntax(index)] +/// Immutably borrows the value associated with the key in the bag `bag: &ObjectBag`. +/// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the bag does not have an entry with +/// that key `k: K`. +/// Aborts with `sui::dynamic_field::EFieldTypeMismatch` if the bag has an entry for the key, but +/// the value does not have the specified type. +public fun borrow(bag: &ObjectBag, k: K): &V { + ofield::borrow(&bag.id, k) +} - /// Mutably borrows the key-value pair in the bag `bag: &mut ObjectBag` and returns the value. - /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the bag does not have an entry with - /// that key `k: K`. - /// Aborts with `sui::dynamic_field::EFieldTypeMismatch` if the bag has an entry for the key, but - /// the value does not have the specified type. - public fun remove(bag: &mut ObjectBag, k: K): V { - let v = ofield::remove(&mut bag.id, k); - bag.size = bag.size - 1; - v - } +#[syntax(index)] +/// Mutably borrows the value associated with the key in the bag `bag: &mut ObjectBag`. +/// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the bag does not have an entry with +/// that key `k: K`. +/// Aborts with `sui::dynamic_field::EFieldTypeMismatch` if the bag has an entry for the key, but +/// the value does not have the specified type. +public fun borrow_mut(bag: &mut ObjectBag, k: K): &mut V { + ofield::borrow_mut(&mut bag.id, k) +} - /// Returns true iff there is an value associated with the key `k: K` in the bag `bag: &ObjectBag` - public fun contains(bag: &ObjectBag, k: K): bool { - ofield::exists_(&bag.id, k) - } +/// Mutably borrows the key-value pair in the bag `bag: &mut ObjectBag` and returns the value. +/// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the bag does not have an entry with +/// that key `k: K`. +/// Aborts with `sui::dynamic_field::EFieldTypeMismatch` if the bag has an entry for the key, but +/// the value does not have the specified type. +public fun remove(bag: &mut ObjectBag, k: K): V { + let v = ofield::remove(&mut bag.id, k); + bag.size = bag.size - 1; + v +} - /// Returns true iff there is an value associated with the key `k: K` in the bag `bag: &ObjectBag` - /// with an assigned value of type `V` - public fun contains_with_type(bag: &ObjectBag, k: K): bool { - ofield::exists_with_type(&bag.id, k) - } +/// Returns true iff there is an value associated with the key `k: K` in the bag `bag: &ObjectBag` +public fun contains(bag: &ObjectBag, k: K): bool { + ofield::exists_(&bag.id, k) +} - /// Returns the size of the bag, the number of key-value pairs - public fun length(bag: &ObjectBag): u64 { - bag.size - } +/// Returns true iff there is an value associated with the key `k: K` in the bag `bag: &ObjectBag` +/// with an assigned value of type `V` +public fun contains_with_type(bag: &ObjectBag, k: K): bool { + ofield::exists_with_type(&bag.id, k) +} - /// Returns true iff the bag is empty (if `length` returns `0`) - public fun is_empty(bag: &ObjectBag): bool { - bag.size == 0 - } +/// Returns the size of the bag, the number of key-value pairs +public fun length(bag: &ObjectBag): u64 { + bag.size +} - /// Destroys an empty bag - /// Aborts with `EBagNotEmpty` if the bag still contains values - public fun destroy_empty(bag: ObjectBag) { - let ObjectBag { id, size } = bag; - assert!(size == 0, EBagNotEmpty); - id.delete() - } +/// Returns true iff the bag is empty (if `length` returns `0`) +public fun is_empty(bag: &ObjectBag): bool { + bag.size == 0 +} - /// Returns the ID of the object associated with the key if the bag has an entry with key `k: K` - /// Returns none otherwise - public fun value_id(bag: &ObjectBag, k: K): Option { - ofield::id(&bag.id, k) - } +/// Destroys an empty bag +/// Aborts with `EBagNotEmpty` if the bag still contains values +public fun destroy_empty(bag: ObjectBag) { + let ObjectBag { id, size } = bag; + assert!(size == 0, EBagNotEmpty); + id.delete() +} + +/// Returns the ID of the object associated with the key if the bag has an entry with key `k: K` +/// Returns none otherwise +public fun value_id(bag: &ObjectBag, k: K): Option { + ofield::id(&bag.id, k) } diff --git a/crates/sui-framework/packages/sui-framework/sources/object_table.move b/crates/sui-framework/packages/sui-framework/sources/object_table.move index a6dc52aafb563..f6cd7a05cd464 100644 --- a/crates/sui-framework/packages/sui-framework/sources/object_table.move +++ b/crates/sui-framework/packages/sui-framework/sources/object_table.move @@ -5,93 +5,93 @@ /// `sui::table`, the values bound to these dynamic fields _must_ be objects themselves. This allows /// for the objects to still exist within in storage, which may be important for external tools. /// The difference is otherwise not observable from within Move. -module sui::object_table { - use sui::dynamic_object_field as ofield; +module sui::object_table; - // Attempted to destroy a non-empty table - const ETableNotEmpty: u64 = 0; +use sui::dynamic_object_field as ofield; - public struct ObjectTable has key, store { - /// the ID of this table - id: UID, - /// the number of key-value pairs in the table - size: u64, - } +// Attempted to destroy a non-empty table +const ETableNotEmpty: u64 = 0; - /// Creates a new, empty table - public fun new(ctx: &mut TxContext): ObjectTable { - ObjectTable { - id: object::new(ctx), - size: 0, - } - } +public struct ObjectTable has key, store { + /// the ID of this table + id: UID, + /// the number of key-value pairs in the table + size: u64, +} - /// Adds a key-value pair to the table `table: &mut ObjectTable` - /// Aborts with `sui::dynamic_field::EFieldAlreadyExists` if the table already has an entry with - /// that key `k: K`. - public fun add(table: &mut ObjectTable, k: K, v: V) { - ofield::add(&mut table.id, k, v); - table.size = table.size + 1; +/// Creates a new, empty table +public fun new(ctx: &mut TxContext): ObjectTable { + ObjectTable { + id: object::new(ctx), + size: 0, } +} - #[syntax(index)] - /// Immutable borrows the value associated with the key in the table `table: &ObjectTable`. - /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with - /// that key `k: K`. - public fun borrow(table: &ObjectTable, k: K): &V { - ofield::borrow(&table.id, k) - } +/// Adds a key-value pair to the table `table: &mut ObjectTable` +/// Aborts with `sui::dynamic_field::EFieldAlreadyExists` if the table already has an entry with +/// that key `k: K`. +public fun add(table: &mut ObjectTable, k: K, v: V) { + ofield::add(&mut table.id, k, v); + table.size = table.size + 1; +} - #[syntax(index)] - /// Mutably borrows the value associated with the key in the table `table: &mut ObjectTable`. - /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with - /// that key `k: K`. - public fun borrow_mut( - table: &mut ObjectTable, - k: K, - ): &mut V { - ofield::borrow_mut(&mut table.id, k) - } +#[syntax(index)] +/// Immutable borrows the value associated with the key in the table `table: &ObjectTable`. +/// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with +/// that key `k: K`. +public fun borrow(table: &ObjectTable, k: K): &V { + ofield::borrow(&table.id, k) +} - /// Removes the key-value pair in the table `table: &mut ObjectTable` and returns the value. - /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with - /// that key `k: K`. - public fun remove(table: &mut ObjectTable, k: K): V { - let v = ofield::remove(&mut table.id, k); - table.size = table.size - 1; - v - } +#[syntax(index)] +/// Mutably borrows the value associated with the key in the table `table: &mut ObjectTable`. +/// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with +/// that key `k: K`. +public fun borrow_mut( + table: &mut ObjectTable, + k: K, +): &mut V { + ofield::borrow_mut(&mut table.id, k) +} - /// Returns true iff there is a value associated with the key `k: K` in table - /// `table: &ObjectTable` - public fun contains(table: &ObjectTable, k: K): bool { - ofield::exists_(&table.id, k) - } +/// Removes the key-value pair in the table `table: &mut ObjectTable` and returns the value. +/// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with +/// that key `k: K`. +public fun remove(table: &mut ObjectTable, k: K): V { + let v = ofield::remove(&mut table.id, k); + table.size = table.size - 1; + v +} - /// Returns the size of the table, the number of key-value pairs - public fun length(table: &ObjectTable): u64 { - table.size - } +/// Returns true iff there is a value associated with the key `k: K` in table +/// `table: &ObjectTable` +public fun contains(table: &ObjectTable, k: K): bool { + ofield::exists_(&table.id, k) +} - /// Returns true iff the table is empty (if `length` returns `0`) - public fun is_empty(table: &ObjectTable): bool { - table.size == 0 - } +/// Returns the size of the table, the number of key-value pairs +public fun length(table: &ObjectTable): u64 { + table.size +} - /// Destroys an empty table - /// Aborts with `ETableNotEmpty` if the table still contains values - public fun destroy_empty(table: ObjectTable) { - let ObjectTable { id, size } = table; - assert!(size == 0, ETableNotEmpty); - id.delete() - } +/// Returns true iff the table is empty (if `length` returns `0`) +public fun is_empty(table: &ObjectTable): bool { + table.size == 0 +} - /// Returns the ID of the object associated with the key if the table has an entry with key `k: K` - /// Returns none otherwise - public fun value_id( - table: &ObjectTable, - k: K, - ): Option { - ofield::id(&table.id, k) - } +/// Destroys an empty table +/// Aborts with `ETableNotEmpty` if the table still contains values +public fun destroy_empty(table: ObjectTable) { + let ObjectTable { id, size } = table; + assert!(size == 0, ETableNotEmpty); + id.delete() +} + +/// Returns the ID of the object associated with the key if the table has an entry with key `k: K` +/// Returns none otherwise +public fun value_id( + table: &ObjectTable, + k: K, +): Option { + ofield::id(&table.id, k) } diff --git a/crates/sui-framework/packages/sui-framework/sources/package.move b/crates/sui-framework/packages/sui-framework/sources/package.move index 85917a647c9d4..4ef8990662b7c 100644 --- a/crates/sui-framework/packages/sui-framework/sources/package.move +++ b/crates/sui-framework/packages/sui-framework/sources/package.move @@ -4,355 +4,351 @@ /// Functions for operating on Move packages from within Move: /// - Creating proof-of-publish objects from one-time witnesses /// - Administering package upgrades through upgrade policies. -module sui::package { - use std::ascii::String; - use std::type_name; - use sui::types; - - /// Allows calling `.burn` to destroy a `Publisher`. - public use fun burn_publisher as Publisher.burn; - - /// Allows calling `.module_` to access the name of the module a - /// `Publisher` was derived from. - public use fun published_module as Publisher.module_; - - /// Allows calling `.package` to access the address of the package - /// a `Publisher` was derived from. - public use fun published_package as Publisher.package; - - /// Allows calling `.package` to access the package this cap - /// authorizes upgrades for. - public use fun upgrade_package as UpgradeCap.package; - - /// Allows calling `.policy` to access the most permissive kind of - /// upgrade this cap will authorize. - public use fun upgrade_policy as UpgradeCap.policy; - - /// Allows calling `.authorize` to initiate an upgrade. - public use fun authorize_upgrade as UpgradeCap.authorize; - - /// Allows calling `.commit` to finalize an upgrade. - public use fun commit_upgrade as UpgradeCap.commit; - - /// Allows calling `.package` to access the package this ticket - /// authorizes an upgrade for. - public use fun ticket_package as UpgradeTicket.package; - - /// Allows calling `.policy` to access the kind of upgrade this - /// ticket authorizes. - public use fun ticket_policy as UpgradeTicket.policy; - - /// Allows calling `.digest` to access the digest of the bytecode - /// used for this upgrade. - public use fun ticket_digest as UpgradeTicket.digest; - - /// Allows calling `.cap` to fetch the ID of the cap this receipt - /// should be applied to. - public use fun receipt_cap as UpgradeReceipt.cap; - - /// Allows calling `.package` to fetch the ID of the package after - /// upgrade. - public use fun receipt_package as UpgradeReceipt.package; - - /// Tried to create a `Publisher` using a type that isn't a - /// one-time witness. - const ENotOneTimeWitness: u64 = 0; - /// Tried to set a less restrictive policy than currently in place. - const ETooPermissive: u64 = 1; - /// This `UpgradeCap` has already authorized a pending upgrade. - const EAlreadyAuthorized: u64 = 2; - /// This `UpgradeCap` has not authorized an upgrade. - const ENotAuthorized: u64 = 3; - /// Trying to commit an upgrade to the wrong `UpgradeCap`. - const EWrongUpgradeCap: u64 = 4; - - /// Update any part of the package (function implementations, add new - /// functions or types, change dependencies) - const COMPATIBLE: u8 = 0; - /// Add new functions or types, or change dependencies, existing - /// functions can't change. - const ADDITIVE: u8 = 128; - /// Only be able to change dependencies. - const DEP_ONLY: u8 = 192; - - /// This type can only be created in the transaction that - /// generates a module, by consuming its one-time witness, so it - /// can be used to identify the address that published the package - /// a type originated from. - public struct Publisher has key, store { - id: UID, - package: String, - module_name: String, - } +module sui::package; + +use std::ascii::String; +use std::type_name; +use sui::types; + +/// Allows calling `.burn` to destroy a `Publisher`. +public use fun burn_publisher as Publisher.burn; + +/// Allows calling `.module_` to access the name of the module a +/// `Publisher` was derived from. +public use fun published_module as Publisher.module_; + +/// Allows calling `.package` to access the address of the package +/// a `Publisher` was derived from. +public use fun published_package as Publisher.package; + +/// Allows calling `.package` to access the package this cap +/// authorizes upgrades for. +public use fun upgrade_package as UpgradeCap.package; + +/// Allows calling `.policy` to access the most permissive kind of +/// upgrade this cap will authorize. +public use fun upgrade_policy as UpgradeCap.policy; + +/// Allows calling `.authorize` to initiate an upgrade. +public use fun authorize_upgrade as UpgradeCap.authorize; + +/// Allows calling `.commit` to finalize an upgrade. +public use fun commit_upgrade as UpgradeCap.commit; + +/// Allows calling `.package` to access the package this ticket +/// authorizes an upgrade for. +public use fun ticket_package as UpgradeTicket.package; + +/// Allows calling `.policy` to access the kind of upgrade this +/// ticket authorizes. +public use fun ticket_policy as UpgradeTicket.policy; + +/// Allows calling `.digest` to access the digest of the bytecode +/// used for this upgrade. +public use fun ticket_digest as UpgradeTicket.digest; + +/// Allows calling `.cap` to fetch the ID of the cap this receipt +/// should be applied to. +public use fun receipt_cap as UpgradeReceipt.cap; + +/// Allows calling `.package` to fetch the ID of the package after +/// upgrade. +public use fun receipt_package as UpgradeReceipt.package; + +/// Tried to create a `Publisher` using a type that isn't a +/// one-time witness. +const ENotOneTimeWitness: u64 = 0; +/// Tried to set a less restrictive policy than currently in place. +const ETooPermissive: u64 = 1; +/// This `UpgradeCap` has already authorized a pending upgrade. +const EAlreadyAuthorized: u64 = 2; +/// This `UpgradeCap` has not authorized an upgrade. +const ENotAuthorized: u64 = 3; +/// Trying to commit an upgrade to the wrong `UpgradeCap`. +const EWrongUpgradeCap: u64 = 4; + +/// Update any part of the package (function implementations, add new +/// functions or types, change dependencies) +const COMPATIBLE: u8 = 0; +/// Add new functions or types, or change dependencies, existing +/// functions can't change. +const ADDITIVE: u8 = 128; +/// Only be able to change dependencies. +const DEP_ONLY: u8 = 192; + +/// This type can only be created in the transaction that +/// generates a module, by consuming its one-time witness, so it +/// can be used to identify the address that published the package +/// a type originated from. +public struct Publisher has key, store { + id: UID, + package: String, + module_name: String, +} - /// Capability controlling the ability to upgrade a package. - public struct UpgradeCap has key, store { - id: UID, - /// (Mutable) ID of the package that can be upgraded. - package: ID, - /// (Mutable) The number of upgrades that have been applied - /// successively to the original package. Initially 0. - version: u64, - /// What kind of upgrades are allowed. - policy: u8, - } +/// Capability controlling the ability to upgrade a package. +public struct UpgradeCap has key, store { + id: UID, + /// (Mutable) ID of the package that can be upgraded. + package: ID, + /// (Mutable) The number of upgrades that have been applied + /// successively to the original package. Initially 0. + version: u64, + /// What kind of upgrades are allowed. + policy: u8, +} - /// Permission to perform a particular upgrade (for a fixed version of - /// the package, bytecode to upgrade with and transitive dependencies to - /// depend against). - /// - /// An `UpgradeCap` can only issue one ticket at a time, to prevent races - /// between concurrent updates or a change in its upgrade policy after - /// issuing a ticket, so the ticket is a "Hot Potato" to preserve forward - /// progress. - public struct UpgradeTicket { - /// (Immutable) ID of the `UpgradeCap` this originated from. - cap: ID, - /// (Immutable) ID of the package that can be upgraded. - package: ID, - /// (Immutable) The policy regarding what kind of upgrade this ticket - /// permits. - policy: u8, - /// (Immutable) SHA256 digest of the bytecode and transitive - /// dependencies that will be used in the upgrade. - digest: vector, - } +/// Permission to perform a particular upgrade (for a fixed version of +/// the package, bytecode to upgrade with and transitive dependencies to +/// depend against). +/// +/// An `UpgradeCap` can only issue one ticket at a time, to prevent races +/// between concurrent updates or a change in its upgrade policy after +/// issuing a ticket, so the ticket is a "Hot Potato" to preserve forward +/// progress. +public struct UpgradeTicket { + /// (Immutable) ID of the `UpgradeCap` this originated from. + cap: ID, + /// (Immutable) ID of the package that can be upgraded. + package: ID, + /// (Immutable) The policy regarding what kind of upgrade this ticket + /// permits. + policy: u8, + /// (Immutable) SHA256 digest of the bytecode and transitive + /// dependencies that will be used in the upgrade. + digest: vector, +} - /// Issued as a result of a successful upgrade, containing the - /// information to be used to update the `UpgradeCap`. This is a "Hot - /// Potato" to ensure that it is used to update its `UpgradeCap` before - /// the end of the transaction that performed the upgrade. - public struct UpgradeReceipt { - /// (Immutable) ID of the `UpgradeCap` this originated from. - cap: ID, - /// (Immutable) ID of the package after it was upgraded. - package: ID, - } +/// Issued as a result of a successful upgrade, containing the +/// information to be used to update the `UpgradeCap`. This is a "Hot +/// Potato" to ensure that it is used to update its `UpgradeCap` before +/// the end of the transaction that performed the upgrade. +public struct UpgradeReceipt { + /// (Immutable) ID of the `UpgradeCap` this originated from. + cap: ID, + /// (Immutable) ID of the package after it was upgraded. + package: ID, +} - /// Claim a Publisher object. - /// Requires a One-Time-Witness to prove ownership. Due to this - /// constraint there can be only one Publisher object per module - /// but multiple per package (!). - public fun claim(otw: OTW, ctx: &mut TxContext): Publisher { - assert!(types::is_one_time_witness(&otw), ENotOneTimeWitness); +/// Claim a Publisher object. +/// Requires a One-Time-Witness to prove ownership. Due to this +/// constraint there can be only one Publisher object per module +/// but multiple per package (!). +public fun claim(otw: OTW, ctx: &mut TxContext): Publisher { + assert!(types::is_one_time_witness(&otw), ENotOneTimeWitness); - let tyname = type_name::get_with_original_ids(); + let tyname = type_name::get_with_original_ids(); - Publisher { - id: object::new(ctx), - package: tyname.get_address(), - module_name: tyname.get_module(), - } + Publisher { + id: object::new(ctx), + package: tyname.get_address(), + module_name: tyname.get_module(), } +} - #[allow(lint(self_transfer))] - /// Claim a Publisher object and send it to transaction sender. - /// Since this function can only be called in the module initializer, - /// the sender is the publisher. - public fun claim_and_keep(otw: OTW, ctx: &mut TxContext) { - sui::transfer::public_transfer(claim(otw, ctx), ctx.sender()) - } +#[allow(lint(self_transfer))] +/// Claim a Publisher object and send it to transaction sender. +/// Since this function can only be called in the module initializer, +/// the sender is the publisher. +public fun claim_and_keep(otw: OTW, ctx: &mut TxContext) { + sui::transfer::public_transfer(claim(otw, ctx), ctx.sender()) +} - /// Destroy a Publisher object effectively removing all privileges - /// associated with it. - public fun burn_publisher(self: Publisher) { - let Publisher { id, package: _, module_name: _ } = self; - id.delete(); - } +/// Destroy a Publisher object effectively removing all privileges +/// associated with it. +public fun burn_publisher(self: Publisher) { + let Publisher { id, package: _, module_name: _ } = self; + id.delete(); +} - /// Check whether type belongs to the same package as the publisher object. - public fun from_package(self: &Publisher): bool { - type_name::get_with_original_ids().get_address() == self.package - } +/// Check whether type belongs to the same package as the publisher object. +public fun from_package(self: &Publisher): bool { + type_name::get_with_original_ids().get_address() == self.package +} - /// Check whether a type belongs to the same module as the publisher object. - public fun from_module(self: &Publisher): bool { - let tyname = type_name::get_with_original_ids(); +/// Check whether a type belongs to the same module as the publisher object. +public fun from_module(self: &Publisher): bool { + let tyname = type_name::get_with_original_ids(); - (tyname.get_address() == self.package) && (tyname.get_module() == self.module_name) - } + (tyname.get_address() == self.package) && (tyname.get_module() == self.module_name) +} - /// Read the name of the module. - public fun published_module(self: &Publisher): &String { - &self.module_name - } +/// Read the name of the module. +public fun published_module(self: &Publisher): &String { + &self.module_name +} - /// Read the package address string. - public fun published_package(self: &Publisher): &String { - &self.package - } +/// Read the package address string. +public fun published_package(self: &Publisher): &String { + &self.package +} - /// The ID of the package that this cap authorizes upgrades for. - /// Can be `0x0` if the cap cannot currently authorize an upgrade - /// because there is already a pending upgrade in the transaction. - /// Otherwise guaranteed to be the latest version of any given - /// package. - public fun upgrade_package(cap: &UpgradeCap): ID { - cap.package - } +/// The ID of the package that this cap authorizes upgrades for. +/// Can be `0x0` if the cap cannot currently authorize an upgrade +/// because there is already a pending upgrade in the transaction. +/// Otherwise guaranteed to be the latest version of any given +/// package. +public fun upgrade_package(cap: &UpgradeCap): ID { + cap.package +} - /// The most recent version of the package, increments by one for each - /// successfully applied upgrade. - public fun version(cap: &UpgradeCap): u64 { - cap.version - } +/// The most recent version of the package, increments by one for each +/// successfully applied upgrade. +public fun version(cap: &UpgradeCap): u64 { + cap.version +} - /// The most permissive kind of upgrade currently supported by this - /// `cap`. - public fun upgrade_policy(cap: &UpgradeCap): u8 { - cap.policy - } +/// The most permissive kind of upgrade currently supported by this +/// `cap`. +public fun upgrade_policy(cap: &UpgradeCap): u8 { + cap.policy +} - /// The package that this ticket is authorized to upgrade - public fun ticket_package(ticket: &UpgradeTicket): ID { - ticket.package - } +/// The package that this ticket is authorized to upgrade +public fun ticket_package(ticket: &UpgradeTicket): ID { + ticket.package +} - /// The kind of upgrade that this ticket authorizes. - public fun ticket_policy(ticket: &UpgradeTicket): u8 { - ticket.policy - } +/// The kind of upgrade that this ticket authorizes. +public fun ticket_policy(ticket: &UpgradeTicket): u8 { + ticket.policy +} - /// ID of the `UpgradeCap` that this `receipt` should be used to - /// update. - public fun receipt_cap(receipt: &UpgradeReceipt): ID { - receipt.cap - } +/// ID of the `UpgradeCap` that this `receipt` should be used to +/// update. +public fun receipt_cap(receipt: &UpgradeReceipt): ID { + receipt.cap +} - /// ID of the package that was upgraded to: the latest version of - /// the package, as of the upgrade represented by this `receipt`. - public fun receipt_package(receipt: &UpgradeReceipt): ID { - receipt.package - } +/// ID of the package that was upgraded to: the latest version of +/// the package, as of the upgrade represented by this `receipt`. +public fun receipt_package(receipt: &UpgradeReceipt): ID { + receipt.package +} - /// A hash of the package contents for the new version of the - /// package. This ticket only authorizes an upgrade to a package - /// that matches this digest. A package's contents are identified - /// by two things: - /// - /// - modules: [[u8]] a list of the package's module contents - /// - deps: [[u8; 32]] a list of 32 byte ObjectIDs of the - /// package's transitive dependencies - /// - /// A package's digest is calculated as: - /// - /// sha3_256(sort(modules ++ deps)) - public fun ticket_digest(ticket: &UpgradeTicket): &vector { - &ticket.digest - } +/// A hash of the package contents for the new version of the +/// package. This ticket only authorizes an upgrade to a package +/// that matches this digest. A package's contents are identified +/// by two things: +/// +/// - modules: [[u8]] a list of the package's module contents +/// - deps: [[u8; 32]] a list of 32 byte ObjectIDs of the +/// package's transitive dependencies +/// +/// A package's digest is calculated as: +/// +/// sha3_256(sort(modules ++ deps)) +public fun ticket_digest(ticket: &UpgradeTicket): &vector { + &ticket.digest +} - /// Expose the constants representing various upgrade policies - public fun compatible_policy(): u8 { COMPATIBLE } - public fun additive_policy(): u8 { ADDITIVE } - public fun dep_only_policy(): u8 { DEP_ONLY } +/// Expose the constants representing various upgrade policies +public fun compatible_policy(): u8 { COMPATIBLE } - /// Restrict upgrades through this upgrade `cap` to just add code, or - /// change dependencies. - public entry fun only_additive_upgrades(cap: &mut UpgradeCap) { - cap.restrict(ADDITIVE) - } +public fun additive_policy(): u8 { ADDITIVE } - /// Restrict upgrades through this upgrade `cap` to just change - /// dependencies. - public entry fun only_dep_upgrades(cap: &mut UpgradeCap) { - cap.restrict(DEP_ONLY) - } +public fun dep_only_policy(): u8 { DEP_ONLY } - /// Discard the `UpgradeCap` to make a package immutable. - public entry fun make_immutable(cap: UpgradeCap) { - let UpgradeCap { id, package: _, version: _, policy: _ } = cap; - id.delete(); - } +/// Restrict upgrades through this upgrade `cap` to just add code, or +/// change dependencies. +public entry fun only_additive_upgrades(cap: &mut UpgradeCap) { + cap.restrict(ADDITIVE) +} + +/// Restrict upgrades through this upgrade `cap` to just change +/// dependencies. +public entry fun only_dep_upgrades(cap: &mut UpgradeCap) { + cap.restrict(DEP_ONLY) +} - /// Issue a ticket authorizing an upgrade to a particular new bytecode - /// (identified by its digest). A ticket will only be issued if one has - /// not already been issued, and if the `policy` requested is at least as - /// restrictive as the policy set out by the `cap`. - /// - /// The `digest` supplied and the `policy` will both be checked by - /// validators when running the upgrade. I.e. the bytecode supplied in - /// the upgrade must have a matching digest, and the changes relative to - /// the parent package must be compatible with the policy in the ticket - /// for the upgrade to succeed. - public fun authorize_upgrade( - cap: &mut UpgradeCap, - policy: u8, - digest: vector - ): UpgradeTicket { - let id_zero = @0x0.to_id(); - assert!(cap.package != id_zero, EAlreadyAuthorized); - assert!(policy >= cap.policy, ETooPermissive); - - let package = cap.package; - cap.package = id_zero; - - UpgradeTicket { - cap: object::id(cap), - package, - policy, - digest, - } +/// Discard the `UpgradeCap` to make a package immutable. +public entry fun make_immutable(cap: UpgradeCap) { + let UpgradeCap { id, package: _, version: _, policy: _ } = cap; + id.delete(); +} + +/// Issue a ticket authorizing an upgrade to a particular new bytecode +/// (identified by its digest). A ticket will only be issued if one has +/// not already been issued, and if the `policy` requested is at least as +/// restrictive as the policy set out by the `cap`. +/// +/// The `digest` supplied and the `policy` will both be checked by +/// validators when running the upgrade. I.e. the bytecode supplied in +/// the upgrade must have a matching digest, and the changes relative to +/// the parent package must be compatible with the policy in the ticket +/// for the upgrade to succeed. +public fun authorize_upgrade(cap: &mut UpgradeCap, policy: u8, digest: vector): UpgradeTicket { + let id_zero = @0x0.to_id(); + assert!(cap.package != id_zero, EAlreadyAuthorized); + assert!(policy >= cap.policy, ETooPermissive); + + let package = cap.package; + cap.package = id_zero; + + UpgradeTicket { + cap: object::id(cap), + package, + policy, + digest, } +} - /// Consume an `UpgradeReceipt` to update its `UpgradeCap`, finalizing - /// the upgrade. - public fun commit_upgrade( - cap: &mut UpgradeCap, - receipt: UpgradeReceipt, - ) { - let UpgradeReceipt { cap: cap_id, package } = receipt; +/// Consume an `UpgradeReceipt` to update its `UpgradeCap`, finalizing +/// the upgrade. +public fun commit_upgrade(cap: &mut UpgradeCap, receipt: UpgradeReceipt) { + let UpgradeReceipt { cap: cap_id, package } = receipt; - assert!(object::id(cap) == cap_id, EWrongUpgradeCap); - assert!(cap.package.to_address() == @0x0, ENotAuthorized); + assert!(object::id(cap) == cap_id, EWrongUpgradeCap); + assert!(cap.package.to_address() == @0x0, ENotAuthorized); - cap.package = package; - cap.version = cap.version + 1; - } + cap.package = package; + cap.version = cap.version + 1; +} - #[test_only] - /// Test-only function to claim a Publisher object bypassing OTW check. - public fun test_claim(_: OTW, ctx: &mut TxContext): Publisher { - let tyname = type_name::get_with_original_ids(); +#[test_only] +/// Test-only function to claim a Publisher object bypassing OTW check. +public fun test_claim(_: OTW, ctx: &mut TxContext): Publisher { + let tyname = type_name::get_with_original_ids(); - Publisher { - id: object::new(ctx), - package: tyname.get_address(), - module_name: tyname.get_module(), - } + Publisher { + id: object::new(ctx), + package: tyname.get_address(), + module_name: tyname.get_module(), } +} - #[test_only] - /// Test-only function to simulate publishing a package at address - /// `ID`, to create an `UpgradeCap`. - public fun test_publish(package: ID, ctx: &mut TxContext): UpgradeCap { - UpgradeCap { - id: object::new(ctx), - package, - version: 1, - policy: COMPATIBLE, - } +#[test_only] +/// Test-only function to simulate publishing a package at address +/// `ID`, to create an `UpgradeCap`. +public fun test_publish(package: ID, ctx: &mut TxContext): UpgradeCap { + UpgradeCap { + id: object::new(ctx), + package, + version: 1, + policy: COMPATIBLE, } +} - #[test_only] - /// Test-only function that takes the role of the actual `Upgrade` - /// command, converting the ticket for the pending upgrade to a - /// receipt for a completed upgrade. - public fun test_upgrade(ticket: UpgradeTicket): UpgradeReceipt { - let UpgradeTicket { cap, package, policy: _, digest: _ } = ticket; - - // Generate a fake package ID for the upgraded package by - // hashing the existing package and cap ID. - let mut data = cap.to_bytes(); - data.append(package.to_bytes()); - let package = object::id_from_bytes(sui::hash::blake2b256(&data)); - - UpgradeReceipt { - cap, package - } +#[test_only] +/// Test-only function that takes the role of the actual `Upgrade` +/// command, converting the ticket for the pending upgrade to a +/// receipt for a completed upgrade. +public fun test_upgrade(ticket: UpgradeTicket): UpgradeReceipt { + let UpgradeTicket { cap, package, policy: _, digest: _ } = ticket; + + // Generate a fake package ID for the upgraded package by + // hashing the existing package and cap ID. + let mut data = cap.to_bytes(); + data.append(package.to_bytes()); + let package = object::id_from_bytes(sui::hash::blake2b256(&data)); + + UpgradeReceipt { + cap, + package, } +} - fun restrict(cap: &mut UpgradeCap, policy: u8) { - assert!(cap.policy <= policy, ETooPermissive); - cap.policy = policy; - } +fun restrict(cap: &mut UpgradeCap, policy: u8) { + assert!(cap.policy <= policy, ETooPermissive); + cap.policy = policy; } diff --git a/crates/sui-framework/packages/sui-framework/sources/pay.move b/crates/sui-framework/packages/sui-framework/sources/pay.move index a13bab88c68c6..f161e935bcfb8 100644 --- a/crates/sui-framework/packages/sui-framework/sources/pay.move +++ b/crates/sui-framework/packages/sui-framework/sources/pay.move @@ -2,86 +2,82 @@ // SPDX-License-Identifier: Apache-2.0 /// This module provides handy functionality for wallets and `sui::Coin` management. -module sui::pay { - use sui::coin::Coin; +module sui::pay; - /// For when empty vector is supplied into join function. - const ENoCoins: u64 = 0; +use sui::coin::Coin; - #[allow(lint(self_transfer))] - /// Transfer `c` to the sender of the current transaction - public fun keep(c: Coin, ctx: &TxContext) { - transfer::public_transfer(c, ctx.sender()) - } +/// For when empty vector is supplied into join function. +const ENoCoins: u64 = 0; - /// Split coin `self` to two coins, one with balance `split_amount`, - /// and the remaining balance is left is `self`. - public entry fun split( - coin: &mut Coin, split_amount: u64, ctx: &mut TxContext - ) { - keep(coin.split(split_amount, ctx), ctx) - } +#[allow(lint(self_transfer))] +/// Transfer `c` to the sender of the current transaction +public fun keep(c: Coin, ctx: &TxContext) { + transfer::public_transfer(c, ctx.sender()) +} - /// Split coin `self` into multiple coins, each with balance specified - /// in `split_amounts`. Remaining balance is left in `self`. - public entry fun split_vec( - self: &mut Coin, split_amounts: vector, ctx: &mut TxContext - ) { - let (mut i, len) = (0, split_amounts.length()); - while (i < len) { - split(self, split_amounts[i], ctx); - i = i + 1; - }; - } +/// Split coin `self` to two coins, one with balance `split_amount`, +/// and the remaining balance is left is `self`. +public entry fun split(coin: &mut Coin, split_amount: u64, ctx: &mut TxContext) { + keep(coin.split(split_amount, ctx), ctx) +} - /// Send `amount` units of `c` to `recipient` - /// Aborts with `EVALUE` if `amount` is greater than or equal to `amount` - public entry fun split_and_transfer( - c: &mut Coin, amount: u64, recipient: address, ctx: &mut TxContext - ) { - transfer::public_transfer(c.split(amount, ctx), recipient) - } +/// Split coin `self` into multiple coins, each with balance specified +/// in `split_amounts`. Remaining balance is left in `self`. +public entry fun split_vec(self: &mut Coin, split_amounts: vector, ctx: &mut TxContext) { + let (mut i, len) = (0, split_amounts.length()); + while (i < len) { + split(self, split_amounts[i], ctx); + i = i + 1; + }; +} +/// Send `amount` units of `c` to `recipient` +/// Aborts with `EVALUE` if `amount` is greater than or equal to `amount` +public entry fun split_and_transfer( + c: &mut Coin, + amount: u64, + recipient: address, + ctx: &mut TxContext, +) { + transfer::public_transfer(c.split(amount, ctx), recipient) +} - #[allow(lint(self_transfer))] - /// Divide coin `self` into `n - 1` coins with equal balances. If the balance is - /// not evenly divisible by `n`, the remainder is left in `self`. - public entry fun divide_and_keep( - self: &mut Coin, n: u64, ctx: &mut TxContext - ) { - let mut vec: vector> = self.divide_into_n(n, ctx); - let (mut i, len) = (0, vec.length()); - while (i < len) { - transfer::public_transfer(vec.pop_back(), ctx.sender()); - i = i + 1; - }; - vec.destroy_empty(); - } +#[allow(lint(self_transfer))] +/// Divide coin `self` into `n - 1` coins with equal balances. If the balance is +/// not evenly divisible by `n`, the remainder is left in `self`. +public entry fun divide_and_keep(self: &mut Coin, n: u64, ctx: &mut TxContext) { + let mut vec: vector> = self.divide_into_n(n, ctx); + let (mut i, len) = (0, vec.length()); + while (i < len) { + transfer::public_transfer(vec.pop_back(), ctx.sender()); + i = i + 1; + }; + vec.destroy_empty(); +} - /// Join `coin` into `self`. Re-exports `coin::join` function. - /// Deprecated: you should call `coin.join(other)` directly. - public entry fun join(self: &mut Coin, coin: Coin) { - self.join(coin) - } +/// Join `coin` into `self`. Re-exports `coin::join` function. +/// Deprecated: you should call `coin.join(other)` directly. +public entry fun join(self: &mut Coin, coin: Coin) { + self.join(coin) +} - /// Join everything in `coins` with `self` - public entry fun join_vec(self: &mut Coin, mut coins: vector>) { - let (mut i, len) = (0, coins.length()); - while (i < len) { - let coin = coins.pop_back(); - self.join(coin); - i = i + 1 - }; - // safe because we've drained the vector - coins.destroy_empty() - } +/// Join everything in `coins` with `self` +public entry fun join_vec(self: &mut Coin, mut coins: vector>) { + let (mut i, len) = (0, coins.length()); + while (i < len) { + let coin = coins.pop_back(); + self.join(coin); + i = i + 1 + }; + // safe because we've drained the vector + coins.destroy_empty() +} - /// Join a vector of `Coin` into a single object and transfer it to `receiver`. - public entry fun join_vec_and_transfer(mut coins: vector>, receiver: address) { - assert!(coins.length() > 0, ENoCoins); +/// Join a vector of `Coin` into a single object and transfer it to `receiver`. +public entry fun join_vec_and_transfer(mut coins: vector>, receiver: address) { + assert!(coins.length() > 0, ENoCoins); - let mut self = coins.pop_back(); - join_vec(&mut self, coins); - transfer::public_transfer(self, receiver) - } + let mut self = coins.pop_back(); + join_vec(&mut self, coins); + transfer::public_transfer(self, receiver) } diff --git a/crates/sui-framework/packages/sui-framework/sources/priority_queue.move b/crates/sui-framework/packages/sui-framework/sources/priority_queue.move index 09f51b20f1b09..d205f98ed8c5f 100644 --- a/crates/sui-framework/packages/sui-framework/sources/priority_queue.move +++ b/crates/sui-framework/packages/sui-framework/sources/priority_queue.move @@ -2,178 +2,176 @@ // SPDX-License-Identifier: Apache-2.0 /// Priority queue implemented using a max heap. -module sui::priority_queue { - - /// For when heap is empty and there's no data to pop. - const EPopFromEmptyHeap: u64 = 0; - - /// Struct representing a priority queue. The `entries` vector represents a max - /// heap structure, where entries[0] is the root, entries[1] and entries[2] are the - /// left child and right child of the root, etc. More generally, the children of - /// entries[i] are at i * 2 + 1 and i * 2 + 2. The max heap should have the invariant - /// that the parent node's priority is always higher than its child nodes' priorities. - public struct PriorityQueue has store, drop { - entries: vector>, - } +module sui::priority_queue; + +/// For when heap is empty and there's no data to pop. +const EPopFromEmptyHeap: u64 = 0; + +/// Struct representing a priority queue. The `entries` vector represents a max +/// heap structure, where entries[0] is the root, entries[1] and entries[2] are the +/// left child and right child of the root, etc. More generally, the children of +/// entries[i] are at i * 2 + 1 and i * 2 + 2. The max heap should have the invariant +/// that the parent node's priority is always higher than its child nodes' priorities. +public struct PriorityQueue has store, drop { + entries: vector>, +} - public struct Entry has store, drop { - priority: u64, // higher value means higher priority and will be popped first - value: T, - } +public struct Entry has store, drop { + priority: u64, // higher value means higher priority and will be popped first + value: T, +} - /// Create a new priority queue from the input entry vectors. - public fun new(mut entries: vector>) : PriorityQueue { - let len = entries.length(); - let mut i = len / 2; - // Max heapify from the first node that is a parent (node at len / 2). - while (i > 0) { - i = i - 1; - max_heapify_recursive(&mut entries, len, i); - }; - PriorityQueue { entries } - } +/// Create a new priority queue from the input entry vectors. +public fun new(mut entries: vector>): PriorityQueue { + let len = entries.length(); + let mut i = len / 2; + // Max heapify from the first node that is a parent (node at len / 2). + while (i > 0) { + i = i - 1; + max_heapify_recursive(&mut entries, len, i); + }; + PriorityQueue { entries } +} - /// Pop the entry with the highest priority value. - public fun pop_max(pq: &mut PriorityQueue) : (u64, T) { - let len = pq.entries.length(); - assert!(len > 0, EPopFromEmptyHeap); - // Swap the max element with the last element in the entries and remove the max element. - let Entry { priority, value } = pq.entries.swap_remove(0); - // Now the max heap property has been violated at the root node, but nowhere else - // so we call max heapify on the root node. - max_heapify_recursive(&mut pq.entries, len - 1, 0); - (priority, value) - } +/// Pop the entry with the highest priority value. +public fun pop_max(pq: &mut PriorityQueue): (u64, T) { + let len = pq.entries.length(); + assert!(len > 0, EPopFromEmptyHeap); + // Swap the max element with the last element in the entries and remove the max element. + let Entry { priority, value } = pq.entries.swap_remove(0); + // Now the max heap property has been violated at the root node, but nowhere else + // so we call max heapify on the root node. + max_heapify_recursive(&mut pq.entries, len - 1, 0); + (priority, value) +} - /// Insert a new entry into the queue. - public fun insert(pq: &mut PriorityQueue, priority: u64, value: T) { - pq.entries.push_back(Entry { priority, value}); - let index = pq.entries.length() - 1; - restore_heap_recursive(&mut pq.entries, index); - } +/// Insert a new entry into the queue. +public fun insert(pq: &mut PriorityQueue, priority: u64, value: T) { + pq.entries.push_back(Entry { priority, value }); + let index = pq.entries.length() - 1; + restore_heap_recursive(&mut pq.entries, index); +} - public fun new_entry(priority: u64, value: T): Entry { - Entry { priority, value } - } +public fun new_entry(priority: u64, value: T): Entry { + Entry { priority, value } +} - public fun create_entries(mut p: vector, mut v: vector): vector> { - let len = p.length(); - assert!(v.length() == len, 0); - let mut res = vector[]; - let mut i = 0; - while (i < len) { - let priority = p.remove(0); - let value = v.remove(0); - res.push_back(Entry { priority, value }); - i = i + 1; - }; - res - } +public fun create_entries(mut p: vector, mut v: vector): vector> { + let len = p.length(); + assert!(v.length() == len, 0); + let mut res = vector[]; + let mut i = 0; + while (i < len) { + let priority = p.remove(0); + let value = v.remove(0); + res.push_back(Entry { priority, value }); + i = i + 1; + }; + res +} - // TODO: implement iterative version too and see performance difference. - fun restore_heap_recursive(v: &mut vector>, i: u64) { - if (i == 0) { - return - }; - let parent = (i - 1) / 2; - - // If new elem is greater than its parent, swap them and recursively - // do the restoration upwards. - if (*&v[i].priority > *&v[parent].priority) { - v.swap(i, parent); - restore_heap_recursive(v, parent); - } +// TODO: implement iterative version too and see performance difference. +fun restore_heap_recursive(v: &mut vector>, i: u64) { + if (i == 0) { + return + }; + let parent = (i - 1) / 2; + + // If new elem is greater than its parent, swap them and recursively + // do the restoration upwards. + if (*&v[i].priority > *&v[parent].priority) { + v.swap(i, parent); + restore_heap_recursive(v, parent); } +} - /// Max heapify the subtree whose root is at index `i`. That means after this function - /// finishes, the subtree should have the property that the parent node has higher priority - /// than both child nodes. - /// This function assumes that all the other nodes in the subtree (nodes other than the root) - /// do satisfy the max heap property. - fun max_heapify_recursive(v: &mut vector>, len: u64, i: u64) { - if (len == 0) { - return - }; - assert!(i < len, 1); - let left = i * 2 + 1; - let right = left + 1; - let mut max = i; - // Find the node with highest priority among node `i` and its two children. - if (left < len && *&v[left].priority > *&v[max].priority) { - max = left; - }; - if (right < len && *&v[right].priority > *&v[max].priority) { - max = right; - }; - // If the parent node (node `i`) doesn't have the highest priority, we swap the parent with the - // max priority node. - if (max != i) { - v.swap(max, i); - // After the swap, we have restored the property at node `i` but now the max heap property - // may be violated at node `max` since this node now has a new value. So we need to now - // max heapify the subtree rooted at node `max`. - max_heapify_recursive(v, len, max); - } +/// Max heapify the subtree whose root is at index `i`. That means after this function +/// finishes, the subtree should have the property that the parent node has higher priority +/// than both child nodes. +/// This function assumes that all the other nodes in the subtree (nodes other than the root) +/// do satisfy the max heap property. +fun max_heapify_recursive(v: &mut vector>, len: u64, i: u64) { + if (len == 0) { + return + }; + assert!(i < len, 1); + let left = i * 2 + 1; + let right = left + 1; + let mut max = i; + // Find the node with highest priority among node `i` and its two children. + if (left < len && *&v[left].priority > *&v[max].priority) { + max = left; + }; + if (right < len && *&v[right].priority > *&v[max].priority) { + max = right; + }; + // If the parent node (node `i`) doesn't have the highest priority, we swap the parent with the + // max priority node. + if (max != i) { + v.swap(max, i); + // After the swap, we have restored the property at node `i` but now the max heap property + // may be violated at node `max` since this node now has a new value. So we need to now + // max heapify the subtree rooted at node `max`. + max_heapify_recursive(v, len, max); } +} - public fun priorities(pq: &PriorityQueue): vector { - let mut res = vector[]; - let mut i = 0; - while (i < pq.entries.length()) { - res.push_back(pq.entries[i].priority); - i = i +1; - }; - res - } +public fun priorities(pq: &PriorityQueue): vector { + let mut res = vector[]; + let mut i = 0; + while (i < pq.entries.length()) { + res.push_back(pq.entries[i].priority); + i = i +1; + }; + res +} - #[test] - fun test_pq() { - let mut h = new(create_entries(vector[3,1,4,2,5,2], vector[10, 20, 30, 40, 50, 60])); - check_pop_max(&mut h, 5, 50); - check_pop_max(&mut h, 4, 30); - check_pop_max(&mut h, 3, 10); - insert(&mut h, 7, 70); - check_pop_max(&mut h, 7, 70); - check_pop_max(&mut h, 2, 40); - insert(&mut h, 0, 80); - check_pop_max(&mut h, 2, 60); - check_pop_max(&mut h, 1, 20); - check_pop_max(&mut h, 0, 80); - - - let mut h = new(create_entries(vector[5,3,1,2,4], vector[10, 20, 30, 40, 50])); - check_pop_max(&mut h, 5, 10); - check_pop_max(&mut h, 4, 50); - check_pop_max(&mut h, 3, 20); - check_pop_max(&mut h, 2, 40); - check_pop_max(&mut h, 1, 30); - } +#[test] +fun test_pq() { + let mut h = new(create_entries(vector[3, 1, 4, 2, 5, 2], vector[10, 20, 30, 40, 50, 60])); + check_pop_max(&mut h, 5, 50); + check_pop_max(&mut h, 4, 30); + check_pop_max(&mut h, 3, 10); + insert(&mut h, 7, 70); + check_pop_max(&mut h, 7, 70); + check_pop_max(&mut h, 2, 40); + insert(&mut h, 0, 80); + check_pop_max(&mut h, 2, 60); + check_pop_max(&mut h, 1, 20); + check_pop_max(&mut h, 0, 80); + + let mut h = new(create_entries(vector[5, 3, 1, 2, 4], vector[10, 20, 30, 40, 50])); + check_pop_max(&mut h, 5, 10); + check_pop_max(&mut h, 4, 50); + check_pop_max(&mut h, 3, 20); + check_pop_max(&mut h, 2, 40); + check_pop_max(&mut h, 1, 30); +} - #[test] - fun test_swap_remove_edge_case() { - // This test would fail if `remove` is used incorrectly instead of `swap_remove` in `pop_max`. - // It's hard to characterize exactly under what condition this bug is triggered but roughly - // it happens when the entire tree vector is shifted left by one because of the incorrect usage - // of `remove`, and the resulting new root and its two children appear to satisfy the heap invariant - // so we stop max-heapifying there, while the rest of the tree is all messed up because of the shift. - let priorities = vector[8, 7, 3, 6, 2, 1, 0, 5, 4]; - let values = vector[0, 0, 0, 0, 0, 0, 0, 0, 0]; - let mut h = new(create_entries(priorities, values)); - check_pop_max(&mut h, 8, 0); - check_pop_max(&mut h, 7, 0); - check_pop_max(&mut h, 6, 0); - check_pop_max(&mut h, 5, 0); - check_pop_max(&mut h, 4, 0); - check_pop_max(&mut h, 3, 0); - check_pop_max(&mut h, 2, 0); - check_pop_max(&mut h, 1, 0); - check_pop_max(&mut h, 0, 0); - } +#[test] +fun test_swap_remove_edge_case() { + // This test would fail if `remove` is used incorrectly instead of `swap_remove` in `pop_max`. + // It's hard to characterize exactly under what condition this bug is triggered but roughly + // it happens when the entire tree vector is shifted left by one because of the incorrect usage + // of `remove`, and the resulting new root and its two children appear to satisfy the heap invariant + // so we stop max-heapifying there, while the rest of the tree is all messed up because of the shift. + let priorities = vector[8, 7, 3, 6, 2, 1, 0, 5, 4]; + let values = vector[0, 0, 0, 0, 0, 0, 0, 0, 0]; + let mut h = new(create_entries(priorities, values)); + check_pop_max(&mut h, 8, 0); + check_pop_max(&mut h, 7, 0); + check_pop_max(&mut h, 6, 0); + check_pop_max(&mut h, 5, 0); + check_pop_max(&mut h, 4, 0); + check_pop_max(&mut h, 3, 0); + check_pop_max(&mut h, 2, 0); + check_pop_max(&mut h, 1, 0); + check_pop_max(&mut h, 0, 0); +} - #[test_only] - fun check_pop_max(h: &mut PriorityQueue, expected_priority: u64, expected_value: u64) { - let (priority, value) = pop_max(h); - assert!(priority == expected_priority); - assert!(value == expected_value); - } +#[test_only] +fun check_pop_max(h: &mut PriorityQueue, expected_priority: u64, expected_value: u64) { + let (priority, value) = pop_max(h); + assert!(priority == expected_priority); + assert!(value == expected_value); } diff --git a/crates/sui-framework/packages/sui-framework/sources/prover.move b/crates/sui-framework/packages/sui-framework/sources/prover.move index e52feb4826479..6c06173ef87a9 100644 --- a/crates/sui-framework/packages/sui-framework/sources/prover.move +++ b/crates/sui-framework/packages/sui-framework/sources/prover.move @@ -1,5 +1,4 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -module sui::prover { -} +module sui::prover; diff --git a/crates/sui-framework/packages/sui-framework/sources/random.move b/crates/sui-framework/packages/sui-framework/sources/random.move index 8d7cdf10025d7..851007cdf6c38 100644 --- a/crates/sui-framework/packages/sui-framework/sources/random.move +++ b/crates/sui-framework/packages/sui-framework/sources/random.move @@ -2,325 +2,325 @@ // SPDX-License-Identifier: Apache-2.0 /// This module provides functionality for generating secure randomness. -module sui::random { - use std::bcs; - use sui::hmac::hmac_sha3_256; - use sui::versioned::{Self, Versioned}; - - // Sender is not @0x0 the system address. - const ENotSystemAddress: u64 = 0; - const EWrongInnerVersion: u64 = 1; - const EInvalidRandomnessUpdate: u64 = 2; - const EInvalidRange: u64 = 3; - const EInvalidLength: u64 = 4; - - const CURRENT_VERSION: u64 = 1; - const RAND_OUTPUT_LEN: u16 = 32; - const U16_MAX: u64 = 0xFFFF; - - /// Singleton shared object which stores the global randomness state. - /// The actual state is stored in a versioned inner field. - public struct Random has key { - id: UID, - // The inner object must never be accessed outside this module as it could be used for accessing global - // randomness via deserialization of RandomInner. - inner: Versioned, - } - - public struct RandomInner has store { - version: u64, - epoch: u64, - randomness_round: u64, - random_bytes: vector, - } - - #[allow(unused_function)] - /// Create and share the Random object. This function is called exactly once, when - /// the Random object is first created. - /// Can only be called by genesis or change_epoch transactions. - fun create(ctx: &mut TxContext) { - assert!(ctx.sender() == @0x0, ENotSystemAddress); - - let version = CURRENT_VERSION; - - let inner = RandomInner { - version, - epoch: ctx.epoch(), - randomness_round: 0, - random_bytes: vector[], - }; - - let self = Random { - id: object::randomness_state(), - inner: versioned::create(version, inner, ctx), - }; - transfer::share_object(self); - } - - #[test_only] - public fun create_for_testing(ctx: &mut TxContext) { - create(ctx); - } - - fun load_inner_mut( - self: &mut Random, - ): &mut RandomInner { - let version = versioned::version(&self.inner); - - // Replace this with a lazy update function when we add a new version of the inner object. - assert!(version == CURRENT_VERSION, EWrongInnerVersion); - let inner: &mut RandomInner = versioned::load_value_mut(&mut self.inner); - assert!(inner.version == version, EWrongInnerVersion); - inner - } - - fun load_inner( - self: &Random, - ): &RandomInner { - let version = versioned::version(&self.inner); - - // Replace this with a lazy update function when we add a new version of the inner object. - assert!(version == CURRENT_VERSION, EWrongInnerVersion); - let inner: &RandomInner = versioned::load_value(&self.inner); - assert!(inner.version == version, EWrongInnerVersion); - inner - } - - #[allow(unused_function)] - /// Record new randomness. Called when executing the RandomnessStateUpdate system - /// transaction. - fun update_randomness_state( - self: &mut Random, - new_round: u64, - new_bytes: vector, - ctx: &TxContext, - ) { - // Validator will make a special system call with sender set as 0x0. - assert!(ctx.sender() == @0x0, ENotSystemAddress); - - // Randomness should only be incremented. - let epoch = ctx.epoch(); - let inner = self.load_inner_mut(); - if (inner.randomness_round == 0 && inner.epoch == 0 && inner.random_bytes.is_empty()) { - // First update should be for round zero. - assert!(new_round == 0, EInvalidRandomnessUpdate); - } else { - // Subsequent updates should either increase epoch or increment randomness_round. - // Note that epoch may increase by more than 1 if an epoch is completed without - // randomness ever being generated in that epoch. - assert!( - (epoch > inner.epoch && new_round == 0) || +module sui::random; + +use std::bcs; +use sui::hmac::hmac_sha3_256; +use sui::versioned::{Self, Versioned}; + +// Sender is not @0x0 the system address. +const ENotSystemAddress: u64 = 0; +const EWrongInnerVersion: u64 = 1; +const EInvalidRandomnessUpdate: u64 = 2; +const EInvalidRange: u64 = 3; +const EInvalidLength: u64 = 4; + +const CURRENT_VERSION: u64 = 1; +const RAND_OUTPUT_LEN: u16 = 32; +const U16_MAX: u64 = 0xFFFF; + +/// Singleton shared object which stores the global randomness state. +/// The actual state is stored in a versioned inner field. +public struct Random has key { + id: UID, + // The inner object must never be accessed outside this module as it could be used for accessing global + // randomness via deserialization of RandomInner. + inner: Versioned, +} + +public struct RandomInner has store { + version: u64, + epoch: u64, + randomness_round: u64, + random_bytes: vector, +} + +#[allow(unused_function)] +/// Create and share the Random object. This function is called exactly once, when +/// the Random object is first created. +/// Can only be called by genesis or change_epoch transactions. +fun create(ctx: &mut TxContext) { + assert!(ctx.sender() == @0x0, ENotSystemAddress); + + let version = CURRENT_VERSION; + + let inner = RandomInner { + version, + epoch: ctx.epoch(), + randomness_round: 0, + random_bytes: vector[], + }; + + let self = Random { + id: object::randomness_state(), + inner: versioned::create(version, inner, ctx), + }; + transfer::share_object(self); +} + +#[test_only] +public fun create_for_testing(ctx: &mut TxContext) { + create(ctx); +} + +fun load_inner_mut(self: &mut Random): &mut RandomInner { + let version = versioned::version(&self.inner); + + // Replace this with a lazy update function when we add a new version of the inner object. + assert!(version == CURRENT_VERSION, EWrongInnerVersion); + let inner: &mut RandomInner = versioned::load_value_mut(&mut self.inner); + assert!(inner.version == version, EWrongInnerVersion); + inner +} + +fun load_inner(self: &Random): &RandomInner { + let version = versioned::version(&self.inner); + + // Replace this with a lazy update function when we add a new version of the inner object. + assert!(version == CURRENT_VERSION, EWrongInnerVersion); + let inner: &RandomInner = versioned::load_value(&self.inner); + assert!(inner.version == version, EWrongInnerVersion); + inner +} + +#[allow(unused_function)] +/// Record new randomness. Called when executing the RandomnessStateUpdate system +/// transaction. +fun update_randomness_state( + self: &mut Random, + new_round: u64, + new_bytes: vector, + ctx: &TxContext, +) { + // Validator will make a special system call with sender set as 0x0. + assert!(ctx.sender() == @0x0, ENotSystemAddress); + + // Randomness should only be incremented. + let epoch = ctx.epoch(); + let inner = self.load_inner_mut(); + if (inner.randomness_round == 0 && inner.epoch == 0 && inner.random_bytes.is_empty()) { + // First update should be for round zero. + assert!(new_round == 0, EInvalidRandomnessUpdate); + } else { + // Subsequent updates should either increase epoch or increment randomness_round. + // Note that epoch may increase by more than 1 if an epoch is completed without + // randomness ever being generated in that epoch. + assert!( + (epoch > inner.epoch && new_round == 0) || (new_round == inner.randomness_round + 1), - EInvalidRandomnessUpdate - ); - }; - - inner.epoch = ctx.epoch(); - inner.randomness_round = new_round; - inner.random_bytes = new_bytes; - } - - #[test_only] - public fun update_randomness_state_for_testing( - self: &mut Random, - new_round: u64, - new_bytes: vector, - ctx: &TxContext, - ) { - self.update_randomness_state(new_round, new_bytes, ctx); - } - - - /// Unique randomness generator, derived from the global randomness. - public struct RandomGenerator has drop { - seed: vector, - counter: u16, - buffer: vector, - } - - /// Create a generator. Can be used to derive up to MAX_U16 * 32 random bytes. - public fun new_generator(r: &Random, ctx: &mut TxContext): RandomGenerator { - let inner = load_inner(r); - let seed = hmac_sha3_256( - &inner.random_bytes, - &ctx.fresh_object_address().to_bytes() + EInvalidRandomnessUpdate, ); - RandomGenerator { seed, counter: 0, buffer: vector[] } - } - - // Get the next block of random bytes. - fun derive_next_block(g: &mut RandomGenerator): vector { - g.counter = g.counter + 1; - hmac_sha3_256(&g.seed, &bcs::to_bytes(&g.counter)) - } - - // Fill the generator's buffer with 32 random bytes. - fun fill_buffer(g: &mut RandomGenerator) { - let next_block = derive_next_block(g); - vector::append(&mut g.buffer, next_block); - } - - /// Generate n random bytes. - public fun generate_bytes(g: &mut RandomGenerator, num_of_bytes: u16): vector { - let mut result = vector[]; - // Append RAND_OUTPUT_LEN size buffers directly without going through the generator's buffer. - let mut num_of_blocks = num_of_bytes / RAND_OUTPUT_LEN; - while (num_of_blocks > 0) { - vector::append(&mut result, derive_next_block(g)); - num_of_blocks = num_of_blocks - 1; - }; - // Fill the generator's buffer if needed. - let num_of_bytes = num_of_bytes as u64; - if (vector::length(&g.buffer) < (num_of_bytes - vector::length(&result))) { - fill_buffer(g); - }; - // Take remaining bytes from the generator's buffer. - while (vector::length(&result) < num_of_bytes) { - vector::push_back(&mut result, vector::pop_back(&mut g.buffer)); - }; - result - } - - // Helper function that extracts the given number of bytes from the random generator and returns it as u256. - // Assumes that the caller has already checked that num_of_bytes is valid. - // TODO: Replace with a macro when we have support for it. - fun u256_from_bytes(g: &mut RandomGenerator, num_of_bytes: u8): u256 { - if (vector::length(&g.buffer) < num_of_bytes as u64) { - fill_buffer(g); - }; - let mut result: u256 = 0; - let mut i = 0; - while (i < num_of_bytes) { - let byte = vector::pop_back(&mut g.buffer); - result = (result << 8) + (byte as u256); - i = i + 1; - }; - result - } - - /// Generate a u256. - public fun generate_u256(g: &mut RandomGenerator): u256 { - u256_from_bytes(g, 32) - } - - /// Generate a u128. - public fun generate_u128(g: &mut RandomGenerator): u128 { - u256_from_bytes(g, 16) as u128 - } - - /// Generate a u64. - public fun generate_u64(g: &mut RandomGenerator): u64 { - u256_from_bytes(g, 8) as u64 - } - - /// Generate a u32. - public fun generate_u32(g: &mut RandomGenerator): u32 { - u256_from_bytes(g, 4) as u32 - } - - /// Generate a u16. - public fun generate_u16(g: &mut RandomGenerator): u16 { - u256_from_bytes(g, 2) as u16 - } - - /// Generate a u8. - public fun generate_u8(g: &mut RandomGenerator): u8 { - u256_from_bytes(g, 1) as u8 - } - - /// Generate a boolean. - public fun generate_bool(g: &mut RandomGenerator): bool { - (u256_from_bytes(g, 1) & 1) == 1 - } - - // Helper function to generate a random u128 in [min, max] using a random number with num_of_bytes bytes. - // Assumes that the caller verified the inputs, and uses num_of_bytes to control the bias (e.g., 8 bytes larger - // than the actual type used by the caller function to limit the bias by 2^{-64}). - // TODO: Replace with a macro when we have support for it. - fun u128_in_range(g: &mut RandomGenerator, min: u128, max: u128, num_of_bytes: u8): u128 { - assert!(min <= max, EInvalidRange); - if (min == max) { - return min - }; - // Pick a random number in [0, max - min] by generating a random number that is larger than max-min, and taking - // the modulo of the random number by the range size. Then add the min to the result to get a number in - // [min, max]. - let range_size = (max - min) as u256 + 1; - let rand = u256_from_bytes(g, num_of_bytes); - min + (rand % range_size as u128) - } - - /// Generate a random u128 in [min, max] (with a bias of 2^{-64}). - public fun generate_u128_in_range(g: &mut RandomGenerator, min: u128, max: u128): u128 { - u128_in_range(g, min, max, 24) - } - - //// Generate a random u64 in [min, max] (with a bias of 2^{-64}). - public fun generate_u64_in_range(g: &mut RandomGenerator, min: u64, max: u64): u64 { - u128_in_range(g, min as u128, max as u128, 16) as u64 - } - - /// Generate a random u32 in [min, max] (with a bias of 2^{-64}). - public fun generate_u32_in_range(g: &mut RandomGenerator, min: u32, max: u32): u32 { - u128_in_range(g, min as u128, max as u128, 12) as u32 - } - - /// Generate a random u16 in [min, max] (with a bias of 2^{-64}). - public fun generate_u16_in_range(g: &mut RandomGenerator, min: u16, max: u16): u16 { - u128_in_range(g, min as u128, max as u128, 10) as u16 - } - - /// Generate a random u8 in [min, max] (with a bias of 2^{-64}). - public fun generate_u8_in_range(g: &mut RandomGenerator, min: u8, max: u8): u8 { - u128_in_range(g, min as u128, max as u128, 9) as u8 - } - - /// Shuffle a vector using the random generator (Fisher–Yates/Knuth shuffle). - public fun shuffle(g: &mut RandomGenerator, v: &mut vector) { - let n = vector::length(v); - if (n == 0) { - return - }; - assert!(n <= U16_MAX, EInvalidLength); - let n = n as u16; - let mut i: u16 = 0; - let end = n - 1; - while (i < end) { - let j = generate_u16_in_range(g, i, end); - vector::swap(v, i as u64, j as u64); - i = i + 1; - }; - } - - #[test_only] - public fun generator_seed(r: &RandomGenerator): &vector { - &r.seed - } - - #[test_only] - public fun generator_counter(r: &RandomGenerator): u16 { - r.counter - } - - #[test_only] - public fun generator_buffer(r: &RandomGenerator): &vector { - &r.buffer - } - - #[test_only] - /// Random generator from a non-deterministic seed. - /// To be used when non-deterministic randomness is needed in tests (e.g., fuzzing). - public fun new_generator_for_testing(): RandomGenerator { - let seed = generate_rand_seed_for_testing(); - new_generator_from_seed_for_testing(seed) - } - - #[test_only] - /// Random generator from a given seed. - public fun new_generator_from_seed_for_testing(seed: vector): RandomGenerator { - RandomGenerator { seed, counter: 0, buffer: vector[] } - } - - #[test_only] - native fun generate_rand_seed_for_testing(): vector; + }; + + inner.epoch = ctx.epoch(); + inner.randomness_round = new_round; + inner.random_bytes = new_bytes; +} + +#[test_only] +public fun update_randomness_state_for_testing( + self: &mut Random, + new_round: u64, + new_bytes: vector, + ctx: &TxContext, +) { + self.update_randomness_state(new_round, new_bytes, ctx); +} + +/// Unique randomness generator, derived from the global randomness. +public struct RandomGenerator has drop { + seed: vector, + counter: u16, + buffer: vector, +} + +/// Create a generator. Can be used to derive up to MAX_U16 * 32 random bytes. +/// +/// Using randomness can be error-prone if you don't observe the subtleties in its correct use, for example, randomness +/// dependent code might be exploitable to attacks that carefully set the gas budget +/// in a way that breaks security. For more information, see: +/// https://docs.sui.io/guides/developer/advanced/randomness-onchain +public fun new_generator(r: &Random, ctx: &mut TxContext): RandomGenerator { + let inner = load_inner(r); + let seed = hmac_sha3_256( + &inner.random_bytes, + &ctx.fresh_object_address().to_bytes(), + ); + RandomGenerator { seed, counter: 0, buffer: vector[] } +} + +// Get the next block of random bytes. +fun derive_next_block(g: &mut RandomGenerator): vector { + g.counter = g.counter + 1; + hmac_sha3_256(&g.seed, &bcs::to_bytes(&g.counter)) +} + +// Fill the generator's buffer with 32 random bytes. +fun fill_buffer(g: &mut RandomGenerator) { + let next_block = derive_next_block(g); + vector::append(&mut g.buffer, next_block); +} + +/// Generate n random bytes. +public fun generate_bytes(g: &mut RandomGenerator, num_of_bytes: u16): vector { + let mut result = vector[]; + // Append RAND_OUTPUT_LEN size buffers directly without going through the generator's buffer. + let mut num_of_blocks = num_of_bytes / RAND_OUTPUT_LEN; + while (num_of_blocks > 0) { + vector::append(&mut result, derive_next_block(g)); + num_of_blocks = num_of_blocks - 1; + }; + // Fill the generator's buffer if needed. + let num_of_bytes = num_of_bytes as u64; + if (vector::length(&g.buffer) < (num_of_bytes - vector::length(&result))) { + fill_buffer(g); + }; + // Take remaining bytes from the generator's buffer. + while (vector::length(&result) < num_of_bytes) { + vector::push_back(&mut result, vector::pop_back(&mut g.buffer)); + }; + result +} + +// Helper function that extracts the given number of bytes from the random generator and returns it as u256. +// Assumes that the caller has already checked that num_of_bytes is valid. +// TODO: Replace with a macro when we have support for it. +fun u256_from_bytes(g: &mut RandomGenerator, num_of_bytes: u8): u256 { + if (vector::length(&g.buffer) < num_of_bytes as u64) { + fill_buffer(g); + }; + let mut result: u256 = 0; + let mut i = 0; + while (i < num_of_bytes) { + let byte = vector::pop_back(&mut g.buffer); + result = (result << 8) + (byte as u256); + i = i + 1; + }; + result +} + +/// Generate a u256. +public fun generate_u256(g: &mut RandomGenerator): u256 { + u256_from_bytes(g, 32) +} + +/// Generate a u128. +public fun generate_u128(g: &mut RandomGenerator): u128 { + u256_from_bytes(g, 16) as u128 +} + +/// Generate a u64. +public fun generate_u64(g: &mut RandomGenerator): u64 { + u256_from_bytes(g, 8) as u64 +} + +/// Generate a u32. +public fun generate_u32(g: &mut RandomGenerator): u32 { + u256_from_bytes(g, 4) as u32 +} + +/// Generate a u16. +public fun generate_u16(g: &mut RandomGenerator): u16 { + u256_from_bytes(g, 2) as u16 +} + +/// Generate a u8. +public fun generate_u8(g: &mut RandomGenerator): u8 { + u256_from_bytes(g, 1) as u8 +} + +/// Generate a boolean. +public fun generate_bool(g: &mut RandomGenerator): bool { + (u256_from_bytes(g, 1) & 1) == 1 +} + +// Helper function to generate a random u128 in [min, max] using a random number with num_of_bytes bytes. +// Assumes that the caller verified the inputs, and uses num_of_bytes to control the bias (e.g., 8 bytes larger +// than the actual type used by the caller function to limit the bias by 2^{-64}). +// TODO: Replace with a macro when we have support for it. +fun u128_in_range(g: &mut RandomGenerator, min: u128, max: u128, num_of_bytes: u8): u128 { + assert!(min <= max, EInvalidRange); + if (min == max) { + return min + }; + // Pick a random number in [0, max - min] by generating a random number that is larger than max-min, and taking + // the modulo of the random number by the range size. Then add the min to the result to get a number in + // [min, max]. + let range_size = (max - min) as u256 + 1; + let rand = u256_from_bytes(g, num_of_bytes); + min + (rand % range_size as u128) +} + +/// Generate a random u128 in [min, max] (with a bias of 2^{-64}). +public fun generate_u128_in_range(g: &mut RandomGenerator, min: u128, max: u128): u128 { + u128_in_range(g, min, max, 24) +} + +//// Generate a random u64 in [min, max] (with a bias of 2^{-64}). +public fun generate_u64_in_range(g: &mut RandomGenerator, min: u64, max: u64): u64 { + u128_in_range(g, min as u128, max as u128, 16) as u64 +} + +/// Generate a random u32 in [min, max] (with a bias of 2^{-64}). +public fun generate_u32_in_range(g: &mut RandomGenerator, min: u32, max: u32): u32 { + u128_in_range(g, min as u128, max as u128, 12) as u32 } + +/// Generate a random u16 in [min, max] (with a bias of 2^{-64}). +public fun generate_u16_in_range(g: &mut RandomGenerator, min: u16, max: u16): u16 { + u128_in_range(g, min as u128, max as u128, 10) as u16 +} + +/// Generate a random u8 in [min, max] (with a bias of 2^{-64}). +public fun generate_u8_in_range(g: &mut RandomGenerator, min: u8, max: u8): u8 { + u128_in_range(g, min as u128, max as u128, 9) as u8 +} + +/// Shuffle a vector using the random generator (Fisher–Yates/Knuth shuffle). +public fun shuffle(g: &mut RandomGenerator, v: &mut vector) { + let n = vector::length(v); + if (n == 0) { + return + }; + assert!(n <= U16_MAX, EInvalidLength); + let n = n as u16; + let mut i: u16 = 0; + let end = n - 1; + while (i < end) { + let j = generate_u16_in_range(g, i, end); + vector::swap(v, i as u64, j as u64); + i = i + 1; + }; +} + +#[test_only] +public fun generator_seed(r: &RandomGenerator): &vector { + &r.seed +} + +#[test_only] +public fun generator_counter(r: &RandomGenerator): u16 { + r.counter +} + +#[test_only] +public fun generator_buffer(r: &RandomGenerator): &vector { + &r.buffer +} + +#[test_only] +/// Random generator from a non-deterministic seed. +/// To be used when non-deterministic randomness is needed in tests (e.g., fuzzing). +public fun new_generator_for_testing(): RandomGenerator { + let seed = generate_rand_seed_for_testing(); + new_generator_from_seed_for_testing(seed) +} + +#[test_only] +/// Random generator from a given seed. +public fun new_generator_from_seed_for_testing(seed: vector): RandomGenerator { + RandomGenerator { seed, counter: 0, buffer: vector[] } +} + +#[test_only] +native fun generate_rand_seed_for_testing(): vector; diff --git a/crates/sui-framework/packages/sui-framework/sources/sui.move b/crates/sui-framework/packages/sui-framework/sources/sui.move index 90c7daab59cdf..0bbc843e321df 100644 --- a/crates/sui-framework/packages/sui-framework/sources/sui.move +++ b/crates/sui-framework/packages/sui-framework/sources/sui.move @@ -3,54 +3,54 @@ /// Coin is the token used to pay for gas in Sui. /// It has 9 decimals, and the smallest unit (10^-9) is called "mist". -module sui::sui { - use sui::balance::Balance; - use sui::coin; - - const EAlreadyMinted: u64 = 0; - /// Sender is not @0x0 the system address. - const ENotSystemAddress: u64 = 1; - - #[allow(unused_const)] - /// The amount of Mist per Sui token based on the fact that mist is - /// 10^-9 of a Sui token - const MIST_PER_SUI: u64 = 1_000_000_000; - - #[allow(unused_const)] - /// The total supply of Sui denominated in whole Sui tokens (10 Billion) - const TOTAL_SUPPLY_SUI: u64 = 10_000_000_000; - - /// The total supply of Sui denominated in Mist (10 Billion * 10^9) - const TOTAL_SUPPLY_MIST: u64 = 10_000_000_000_000_000_000; - - /// Name of the coin - public struct SUI has drop {} - - #[allow(unused_function)] - /// Register the `SUI` Coin to acquire its `Supply`. - /// This should be called only once during genesis creation. - fun new(ctx: &mut TxContext): Balance { - assert!(ctx.sender() == @0x0, ENotSystemAddress); - assert!(ctx.epoch() == 0, EAlreadyMinted); - - let (treasury, metadata) = coin::create_currency( - SUI {}, - 9, - b"SUI", - b"Sui", - // TODO: add appropriate description and logo url - b"", - option::none(), - ctx - ); - transfer::public_freeze_object(metadata); - let mut supply = treasury.treasury_into_supply(); - let total_sui = supply.increase_supply(TOTAL_SUPPLY_MIST); - supply.destroy_supply(); - total_sui - } - - public entry fun transfer(c: coin::Coin, recipient: address) { - transfer::public_transfer(c, recipient) - } +module sui::sui; + +use sui::balance::Balance; +use sui::coin; + +const EAlreadyMinted: u64 = 0; +/// Sender is not @0x0 the system address. +const ENotSystemAddress: u64 = 1; + +#[allow(unused_const)] +/// The amount of Mist per Sui token based on the fact that mist is +/// 10^-9 of a Sui token +const MIST_PER_SUI: u64 = 1_000_000_000; + +#[allow(unused_const)] +/// The total supply of Sui denominated in whole Sui tokens (10 Billion) +const TOTAL_SUPPLY_SUI: u64 = 10_000_000_000; + +/// The total supply of Sui denominated in Mist (10 Billion * 10^9) +const TOTAL_SUPPLY_MIST: u64 = 10_000_000_000_000_000_000; + +/// Name of the coin +public struct SUI has drop {} + +#[allow(unused_function)] +/// Register the `SUI` Coin to acquire its `Supply`. +/// This should be called only once during genesis creation. +fun new(ctx: &mut TxContext): Balance { + assert!(ctx.sender() == @0x0, ENotSystemAddress); + assert!(ctx.epoch() == 0, EAlreadyMinted); + + let (treasury, metadata) = coin::create_currency( + SUI {}, + 9, + b"SUI", + b"Sui", + // TODO: add appropriate description and logo url + b"", + option::none(), + ctx, + ); + transfer::public_freeze_object(metadata); + let mut supply = treasury.treasury_into_supply(); + let total_sui = supply.increase_supply(TOTAL_SUPPLY_MIST); + supply.destroy_supply(); + total_sui +} + +public entry fun transfer(c: coin::Coin, recipient: address) { + transfer::public_transfer(c, recipient) } diff --git a/crates/sui-framework/packages/sui-framework/sources/table.move b/crates/sui-framework/packages/sui-framework/sources/table.move index a3cd682bb353f..6848d373cb756 100644 --- a/crates/sui-framework/packages/sui-framework/sources/table.move +++ b/crates/sui-framework/packages/sui-framework/sources/table.move @@ -16,87 +16,87 @@ /// // table1 does not equal table2, despite having the same entries /// assert!(&table1 != &table2); /// ``` -module sui::table { - use sui::dynamic_field as field; +module sui::table; - // Attempted to destroy a non-empty table - const ETableNotEmpty: u64 = 0; +use sui::dynamic_field as field; - public struct Table has key, store { - /// the ID of this table - id: UID, - /// the number of key-value pairs in the table - size: u64, - } +// Attempted to destroy a non-empty table +const ETableNotEmpty: u64 = 0; - /// Creates a new, empty table - public fun new(ctx: &mut TxContext): Table { - Table { - id: object::new(ctx), - size: 0, - } - } +public struct Table has key, store { + /// the ID of this table + id: UID, + /// the number of key-value pairs in the table + size: u64, +} - /// Adds a key-value pair to the table `table: &mut Table` - /// Aborts with `sui::dynamic_field::EFieldAlreadyExists` if the table already has an entry with - /// that key `k: K`. - public fun add(table: &mut Table, k: K, v: V) { - field::add(&mut table.id, k, v); - table.size = table.size + 1; +/// Creates a new, empty table +public fun new(ctx: &mut TxContext): Table { + Table { + id: object::new(ctx), + size: 0, } +} - #[syntax(index)] - /// Immutable borrows the value associated with the key in the table `table: &Table`. - /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with - /// that key `k: K`. - public fun borrow(table: &Table, k: K): &V { - field::borrow(&table.id, k) - } +/// Adds a key-value pair to the table `table: &mut Table` +/// Aborts with `sui::dynamic_field::EFieldAlreadyExists` if the table already has an entry with +/// that key `k: K`. +public fun add(table: &mut Table, k: K, v: V) { + field::add(&mut table.id, k, v); + table.size = table.size + 1; +} - #[syntax(index)] - /// Mutably borrows the value associated with the key in the table `table: &mut Table`. - /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with - /// that key `k: K`. - public fun borrow_mut(table: &mut Table, k: K): &mut V { - field::borrow_mut(&mut table.id, k) - } +#[syntax(index)] +/// Immutable borrows the value associated with the key in the table `table: &Table`. +/// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with +/// that key `k: K`. +public fun borrow(table: &Table, k: K): &V { + field::borrow(&table.id, k) +} - /// Removes the key-value pair in the table `table: &mut Table` and returns the value. - /// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with - /// that key `k: K`. - public fun remove(table: &mut Table, k: K): V { - let v = field::remove(&mut table.id, k); - table.size = table.size - 1; - v - } +#[syntax(index)] +/// Mutably borrows the value associated with the key in the table `table: &mut Table`. +/// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with +/// that key `k: K`. +public fun borrow_mut(table: &mut Table, k: K): &mut V { + field::borrow_mut(&mut table.id, k) +} - /// Returns true iff there is a value associated with the key `k: K` in table `table: &Table` - public fun contains(table: &Table, k: K): bool { - field::exists_with_type(&table.id, k) - } +/// Removes the key-value pair in the table `table: &mut Table` and returns the value. +/// Aborts with `sui::dynamic_field::EFieldDoesNotExist` if the table does not have an entry with +/// that key `k: K`. +public fun remove(table: &mut Table, k: K): V { + let v = field::remove(&mut table.id, k); + table.size = table.size - 1; + v +} - /// Returns the size of the table, the number of key-value pairs - public fun length(table: &Table): u64 { - table.size - } +/// Returns true iff there is a value associated with the key `k: K` in table `table: &Table` +public fun contains(table: &Table, k: K): bool { + field::exists_with_type(&table.id, k) +} - /// Returns true iff the table is empty (if `length` returns `0`) - public fun is_empty(table: &Table): bool { - table.size == 0 - } +/// Returns the size of the table, the number of key-value pairs +public fun length(table: &Table): u64 { + table.size +} - /// Destroys an empty table - /// Aborts with `ETableNotEmpty` if the table still contains values - public fun destroy_empty(table: Table) { - let Table { id, size } = table; - assert!(size == 0, ETableNotEmpty); - id.delete() - } +/// Returns true iff the table is empty (if `length` returns `0`) +public fun is_empty(table: &Table): bool { + table.size == 0 +} - /// Drop a possibly non-empty table. - /// Usable only if the value type `V` has the `drop` ability - public fun drop(table: Table) { - let Table { id, size: _ } = table; - id.delete() - } +/// Destroys an empty table +/// Aborts with `ETableNotEmpty` if the table still contains values +public fun destroy_empty(table: Table) { + let Table { id, size } = table; + assert!(size == 0, ETableNotEmpty); + id.delete() +} + +/// Drop a possibly non-empty table. +/// Usable only if the value type `V` has the `drop` ability +public fun drop(table: Table) { + let Table { id, size: _ } = table; + id.delete() } diff --git a/crates/sui-framework/packages/sui-framework/sources/table_vec.move b/crates/sui-framework/packages/sui-framework/sources/table_vec.move index 9ae8b9c1a2a50..d77a69178004a 100644 --- a/crates/sui-framework/packages/sui-framework/sources/table_vec.move +++ b/crates/sui-framework/packages/sui-framework/sources/table_vec.move @@ -2,129 +2,130 @@ // SPDX-License-Identifier: Apache-2.0 /// A basic scalable vector library implemented using `Table`. -module sui::table_vec { - use sui::table::{Self, Table}; +module sui::table_vec; - public struct TableVec has store { - /// The contents of the table vector. - contents: Table, - } +use sui::table::{Self, Table}; - const EIndexOutOfBound: u64 = 0; - const ETableNonEmpty: u64 = 1; +public struct TableVec has store { + /// The contents of the table vector. + contents: Table, +} - /// Create an empty TableVec. - public fun empty(ctx: &mut TxContext): TableVec { - TableVec { - contents: table::new(ctx) - } - } +const EIndexOutOfBound: u64 = 0; +const ETableNonEmpty: u64 = 1; - /// Return a TableVec of size one containing element `e`. - public fun singleton(e: Element, ctx: &mut TxContext): TableVec { - let mut t = empty(ctx); - t.push_back(e); - t +/// Create an empty TableVec. +public fun empty(ctx: &mut TxContext): TableVec { + TableVec { + contents: table::new(ctx), } +} - /// Return the length of the TableVec. - public fun length(t: &TableVec): u64 { - t.contents.length() - } +/// Return a TableVec of size one containing element `e`. +public fun singleton(e: Element, ctx: &mut TxContext): TableVec { + let mut t = empty(ctx); + t.push_back(e); + t +} - /// Return if the TableVec is empty or not. - public fun is_empty(t: &TableVec): bool { - t.length() == 0 - } +/// Return the length of the TableVec. +public fun length(t: &TableVec): u64 { + t.contents.length() +} - #[syntax(index)] - /// Acquire an immutable reference to the `i`th element of the TableVec `t`. - /// Aborts if `i` is out of bounds. - public fun borrow(t: &TableVec, i: u64): &Element { - assert!(t.length() > i, EIndexOutOfBound); - &t.contents[i] - } +/// Return if the TableVec is empty or not. +public fun is_empty(t: &TableVec): bool { + t.length() == 0 +} - /// Add element `e` to the end of the TableVec `t`. - public fun push_back(t: &mut TableVec, e: Element) { - let key = t.length(); - t.contents.add(key, e); - } +#[syntax(index)] +/// Acquire an immutable reference to the `i`th element of the TableVec `t`. +/// Aborts if `i` is out of bounds. +public fun borrow(t: &TableVec, i: u64): &Element { + assert!(t.length() > i, EIndexOutOfBound); + &t.contents[i] +} - #[syntax(index)] - /// Return a mutable reference to the `i`th element in the TableVec `t`. - /// Aborts if `i` is out of bounds. - public fun borrow_mut(t: &mut TableVec, i: u64): &mut Element { - assert!(t.length() > i, EIndexOutOfBound); - &mut t.contents[i] - } +/// Add element `e` to the end of the TableVec `t`. +public fun push_back(t: &mut TableVec, e: Element) { + let key = t.length(); + t.contents.add(key, e); +} - /// Pop an element from the end of TableVec `t`. - /// Aborts if `t` is empty. - public fun pop_back(t: &mut TableVec): Element { - let length = length(t); - assert!(length > 0, EIndexOutOfBound); - t.contents.remove(length - 1) - } +#[syntax(index)] +/// Return a mutable reference to the `i`th element in the TableVec `t`. +/// Aborts if `i` is out of bounds. +public fun borrow_mut(t: &mut TableVec, i: u64): &mut Element { + assert!(t.length() > i, EIndexOutOfBound); + &mut t.contents[i] +} - /// Destroy the TableVec `t`. - /// Aborts if `t` is not empty. - public fun destroy_empty(t: TableVec) { - assert!(length(&t) == 0, ETableNonEmpty); - let TableVec { contents } = t; - contents.destroy_empty(); - } +/// Pop an element from the end of TableVec `t`. +/// Aborts if `t` is empty. +public fun pop_back(t: &mut TableVec): Element { + let length = length(t); + assert!(length > 0, EIndexOutOfBound); + t.contents.remove(length - 1) +} - /// Drop a possibly non-empty TableVec `t`. - /// Usable only if the value type `Element` has the `drop` ability - public fun drop(t: TableVec) { - let TableVec { contents } = t; - contents.drop() - } +/// Destroy the TableVec `t`. +/// Aborts if `t` is not empty. +public fun destroy_empty(t: TableVec) { + assert!(length(&t) == 0, ETableNonEmpty); + let TableVec { contents } = t; + contents.destroy_empty(); +} - /// Swaps the elements at the `i`th and `j`th indices in the TableVec `t`. - /// Aborts if `i` or `j` is out of bounds. - public fun swap(t: &mut TableVec, i: u64, j: u64) { - assert!(t.length() > i, EIndexOutOfBound); - assert!(t.length() > j, EIndexOutOfBound); - if (i == j) { return }; - let element_i = t.contents.remove(i); - let element_j = t.contents.remove(j); - t.contents.add(j, element_i); - t.contents.add(i, element_j); - } +/// Drop a possibly non-empty TableVec `t`. +/// Usable only if the value type `Element` has the `drop` ability +public fun drop(t: TableVec) { + let TableVec { contents } = t; + contents.drop() +} - /// Swap the `i`th element of the TableVec `t` with the last element and then pop the TableVec. - /// This is O(1), but does not preserve ordering of elements in the TableVec. - /// Aborts if `i` is out of bounds. - public fun swap_remove(t: &mut TableVec, i: u64): Element { - assert!(t.length() > i, EIndexOutOfBound); - let last_idx = t.length() - 1; - t.swap(i, last_idx); - t.pop_back() - } +/// Swaps the elements at the `i`th and `j`th indices in the TableVec `t`. +/// Aborts if `i` or `j` is out of bounds. +public fun swap(t: &mut TableVec, i: u64, j: u64) { + assert!(t.length() > i, EIndexOutOfBound); + assert!(t.length() > j, EIndexOutOfBound); + if (i == j) { + return + }; + let element_i = t.contents.remove(i); + let element_j = t.contents.remove(j); + t.contents.add(j, element_i); + t.contents.add(i, element_j); +} - #[test] - fun test_swap() { - let ctx = &mut sui::tx_context::dummy(); - let mut tv = singleton(0, ctx); - tv.push_back(1); - tv.push_back(2); - tv.push_back(3); - tv.push_back(4); - tv.swap(4,2); - tv.check_pop(2); - tv.check_pop(3); - tv.check_pop(4); - tv.check_pop(1); - tv.check_pop(0); - tv.drop() - } +/// Swap the `i`th element of the TableVec `t` with the last element and then pop the TableVec. +/// This is O(1), but does not preserve ordering of elements in the TableVec. +/// Aborts if `i` is out of bounds. +public fun swap_remove(t: &mut TableVec, i: u64): Element { + assert!(t.length() > i, EIndexOutOfBound); + let last_idx = t.length() - 1; + t.swap(i, last_idx); + t.pop_back() +} - #[test_only] - fun check_pop(tv: &mut TableVec, expected_value: u64) { - let value = tv.pop_back(); - assert!(value == expected_value, value * 100 + expected_value); - } +#[test] +fun test_swap() { + let ctx = &mut sui::tx_context::dummy(); + let mut tv = singleton(0, ctx); + tv.push_back(1); + tv.push_back(2); + tv.push_back(3); + tv.push_back(4); + tv.swap(4, 2); + tv.check_pop(2); + tv.check_pop(3); + tv.check_pop(4); + tv.check_pop(1); + tv.check_pop(0); + tv.drop() +} +#[test_only] +fun check_pop(tv: &mut TableVec, expected_value: u64) { + let value = tv.pop_back(); + assert!(value == expected_value, value * 100 + expected_value); } diff --git a/crates/sui-framework/packages/sui-framework/sources/test/test_scenario.move b/crates/sui-framework/packages/sui-framework/sources/test/test_scenario.move index 2618b97204cc7..782339959a524 100644 --- a/crates/sui-framework/packages/sui-framework/sources/test/test_scenario.move +++ b/crates/sui-framework/packages/sui-framework/sources/test/test_scenario.move @@ -2,412 +2,405 @@ // SPDX-License-Identifier: Apache-2.0 #[test_only] -module sui::test_scenario { - use sui::vec_map::VecMap; - - #[allow(unused_const)] - /// the transaction failed when generating these effects. For example, a circular ownership - /// of objects was created - const ECouldNotGenerateEffects: u64 = 0; - - /// Transaction ended without all shared and immutable objects being returned or with those - /// objects being transferred or wrapped - const EInvalidSharedOrImmutableUsage: u64 = 1; - - /// Attempted to return an object to the inventory that was not previously removed from the - /// inventory during the current transaction. Can happen if the user attempts to call - /// `return_to_address` on a locally constructed object rather than one returned from a - /// `test_scenario` function such as `take_from_address`. - const ECantReturnObject: u64 = 2; - - /// Attempted to retrieve an object of a particular type from the inventory, but it is empty. - /// Can happen if the user already transferred the object or a previous transaction failed to - /// transfer the object to the user. - const EEmptyInventory: u64 = 3; - - /// Object of that ID was not found in that inventory. It was possibly already taken - const EObjectNotFound: u64 = 4; - - #[allow(unused_const)] - /// Unable to allocate a receiving ticket for the object - const EUnableToAllocateReceivingTicket: u64 = 5; - - #[allow(unused_const)] - /// A receiving ticket for the object was already allocated in the transaction - const EReceivingTicketAlreadyAllocated: u64 = 6; - - #[allow(unused_const)] - /// Unable to deallocate the receiving ticket - const EUnableToDeallocateReceivingTicket: u64 = 7; - - /// Utility for mocking a multi-transaction Sui execution in a single Move procedure. - /// A `Scenario` maintains a view of the global object pool built up by the execution. - /// These objects can be accessed via functions like `take_from_sender`, which gives the - /// transaction sender access to objects in (only) their inventory. - /// Example usage: - /// ``` - /// let addr1: address = 0; - /// let addr2: address = 1; - /// // begin a test scenario in a context where addr1 is the sender - /// let scenario = &mut test_scenario::begin(addr1); - /// // addr1 sends an object to addr2 - /// { - /// let some_object: SomeObject = ... // construct an object - /// transfer::public_transfer(some_object, copy addr2) - /// }; - /// // end the first transaction and begin a new one where addr2 is the sender - /// // Starting a new transaction moves any objects transferred into their respective - /// // inventories. In other words, if you call `take_from_sender` before `next_tx`, `addr2` - /// // will not yet have `some_object` - /// test_scenario::next_tx(scenario, addr2); - /// { - /// // remove the SomeObject value from addr2's inventory - /// let obj = test_scenario::take_from_sender(scenario); - /// // use it to test some function that needs this value - /// SomeObject::some_function(obj) - /// }; - /// ... // more txes - /// test_scenario::end(scenario); - /// ``` - public struct Scenario { - txn_number: u64, - ctx: TxContext, - } +module sui::test_scenario; + +use sui::vec_map::VecMap; + +#[allow(unused_const)] +/// the transaction failed when generating these effects. For example, a circular ownership +/// of objects was created +const ECouldNotGenerateEffects: u64 = 0; + +/// Transaction ended without all shared and immutable objects being returned or with those +/// objects being transferred or wrapped +const EInvalidSharedOrImmutableUsage: u64 = 1; + +/// Attempted to return an object to the inventory that was not previously removed from the +/// inventory during the current transaction. Can happen if the user attempts to call +/// `return_to_address` on a locally constructed object rather than one returned from a +/// `test_scenario` function such as `take_from_address`. +const ECantReturnObject: u64 = 2; + +/// Attempted to retrieve an object of a particular type from the inventory, but it is empty. +/// Can happen if the user already transferred the object or a previous transaction failed to +/// transfer the object to the user. +const EEmptyInventory: u64 = 3; + +/// Object of that ID was not found in that inventory. It was possibly already taken +const EObjectNotFound: u64 = 4; + +#[allow(unused_const)] +/// Unable to allocate a receiving ticket for the object +const EUnableToAllocateReceivingTicket: u64 = 5; + +#[allow(unused_const)] +/// A receiving ticket for the object was already allocated in the transaction +const EReceivingTicketAlreadyAllocated: u64 = 6; + +#[allow(unused_const)] +/// Unable to deallocate the receiving ticket +const EUnableToDeallocateReceivingTicket: u64 = 7; + +/// Utility for mocking a multi-transaction Sui execution in a single Move procedure. +/// A `Scenario` maintains a view of the global object pool built up by the execution. +/// These objects can be accessed via functions like `take_from_sender`, which gives the +/// transaction sender access to objects in (only) their inventory. +/// Example usage: +/// ``` +/// let addr1: address = 0; +/// let addr2: address = 1; +/// // begin a test scenario in a context where addr1 is the sender +/// let scenario = &mut test_scenario::begin(addr1); +/// // addr1 sends an object to addr2 +/// { +/// let some_object: SomeObject = ... // construct an object +/// transfer::public_transfer(some_object, copy addr2) +/// }; +/// // end the first transaction and begin a new one where addr2 is the sender +/// // Starting a new transaction moves any objects transferred into their respective +/// // inventories. In other words, if you call `take_from_sender` before `next_tx`, `addr2` +/// // will not yet have `some_object` +/// test_scenario::next_tx(scenario, addr2); +/// { +/// // remove the SomeObject value from addr2's inventory +/// let obj = test_scenario::take_from_sender(scenario); +/// // use it to test some function that needs this value +/// SomeObject::some_function(obj) +/// }; +/// ... // more txes +/// test_scenario::end(scenario); +/// ``` +public struct Scenario { + txn_number: u64, + ctx: TxContext, +} - /// The effects of a transaction - public struct TransactionEffects has drop { - /// The objects created this transaction - created: vector, - /// The objects written/modified this transaction - written: vector, - /// The objects deleted this transaction - deleted: vector, - /// The objects transferred to an account this transaction - transferred_to_account: VecMap, - /// The objects transferred to an object this transaction - transferred_to_object: VecMap, - /// The objects shared this transaction - shared: vector, - /// The objects frozen this transaction - frozen: vector, - /// The number of user events emitted this transaction - num_user_events: u64, - } +/// The effects of a transaction +public struct TransactionEffects has drop { + /// The objects created this transaction + created: vector, + /// The objects written/modified this transaction + written: vector, + /// The objects deleted this transaction + deleted: vector, + /// The objects transferred to an account this transaction + transferred_to_account: VecMap, + /// The objects transferred to an object this transaction + transferred_to_object: VecMap, + /// The objects shared this transaction + shared: vector, + /// The objects frozen this transaction + frozen: vector, + /// The number of user events emitted this transaction + num_user_events: u64, +} - /// Begin a new multi-transaction test scenario in a context where `sender` is the tx sender - public fun begin(sender: address): Scenario { - Scenario { - txn_number: 0, - ctx: tx_context::new_from_hint(sender, 0, 0, 0, 0), - } +/// Begin a new multi-transaction test scenario in a context where `sender` is the tx sender +public fun begin(sender: address): Scenario { + Scenario { + txn_number: 0, + ctx: tx_context::new_from_hint(sender, 0, 0, 0, 0), } +} - /// Advance the scenario to a new transaction where `sender` is the transaction sender - /// All objects transferred will be moved into the inventories of the account or the global - /// inventory. In other words, in order to access an object with one of the various "take" - /// functions below, e.g. `take_from_address_by_id`, the transaction must first be ended via - /// `next_tx`. - /// Returns the results from the previous transaction - /// Will abort if shared or immutable objects were deleted, transferred, or wrapped. - /// Will abort if TransactionEffects cannot be generated - public fun next_tx(scenario: &mut Scenario, sender: address): TransactionEffects { - // create a seed for new transaction digest to ensure that this tx has a different - // digest (and consequently, different object ID's) than the previous tx - scenario.txn_number = scenario.txn_number + 1; - let epoch = scenario.ctx.epoch(); - let epoch_timestamp_ms = scenario.ctx.epoch_timestamp_ms(); - scenario.ctx = tx_context::new_from_hint( +/// Advance the scenario to a new transaction where `sender` is the transaction sender +/// All objects transferred will be moved into the inventories of the account or the global +/// inventory. In other words, in order to access an object with one of the various "take" +/// functions below, e.g. `take_from_address_by_id`, the transaction must first be ended via +/// `next_tx`. +/// Returns the results from the previous transaction +/// Will abort if shared or immutable objects were deleted, transferred, or wrapped. +/// Will abort if TransactionEffects cannot be generated +public fun next_tx(scenario: &mut Scenario, sender: address): TransactionEffects { + // create a seed for new transaction digest to ensure that this tx has a different + // digest (and consequently, different object ID's) than the previous tx + scenario.txn_number = scenario.txn_number + 1; + let epoch = scenario.ctx.epoch(); + let epoch_timestamp_ms = scenario.ctx.epoch_timestamp_ms(); + scenario.ctx = + tx_context::new_from_hint( sender, scenario.txn_number, epoch, epoch_timestamp_ms, 0, ); - // end the transaction - end_transaction() - } - - /// Advance the scenario to a new epoch and end the transaction - /// See `next_tx` for further details - public fun next_epoch(scenario: &mut Scenario, sender: address): TransactionEffects { - scenario.ctx.increment_epoch_number(); - next_tx(scenario, sender) - } + // end the transaction + end_transaction() +} - /// Advance the scenario to a new epoch, `delta_ms` milliseconds in the future and end - /// the transaction. - /// See `next_tx` for further details - public fun later_epoch( - scenario: &mut Scenario, - delta_ms: u64, - sender: address, - ): TransactionEffects { - scenario.ctx.increment_epoch_timestamp(delta_ms); - next_epoch(scenario, sender) - } +/// Advance the scenario to a new epoch and end the transaction +/// See `next_tx` for further details +public fun next_epoch(scenario: &mut Scenario, sender: address): TransactionEffects { + scenario.ctx.increment_epoch_number(); + next_tx(scenario, sender) +} - /// Ends the test scenario - /// Returns the results from the final transaction - /// Will abort if shared or immutable objects were deleted, transferred, or wrapped. - /// Will abort if TransactionEffects cannot be generated - public fun end(scenario: Scenario): TransactionEffects { - let Scenario { txn_number: _, ctx: _ } = scenario; - end_transaction() - } +/// Advance the scenario to a new epoch, `delta_ms` milliseconds in the future and end +/// the transaction. +/// See `next_tx` for further details +public fun later_epoch( + scenario: &mut Scenario, + delta_ms: u64, + sender: address, +): TransactionEffects { + scenario.ctx.increment_epoch_timestamp(delta_ms); + next_epoch(scenario, sender) +} - // == accessors and helpers == +/// Ends the test scenario +/// Returns the results from the final transaction +/// Will abort if shared or immutable objects were deleted, transferred, or wrapped. +/// Will abort if TransactionEffects cannot be generated +public fun end(scenario: Scenario): TransactionEffects { + let Scenario { txn_number: _, ctx: _ } = scenario; + end_transaction() +} - /// Return the `TxContext` associated with this `scenario` - public fun ctx(scenario: &mut Scenario): &mut TxContext { - &mut scenario.ctx - } +// == accessors and helpers == - /// Generate a fresh ID for the current tx associated with this `scenario` - public fun new_object(scenario: &mut Scenario): UID { - object::new(&mut scenario.ctx) - } +/// Return the `TxContext` associated with this `scenario` +public fun ctx(scenario: &mut Scenario): &mut TxContext { + &mut scenario.ctx +} - /// Return the sender of the current tx in this `scenario` - public fun sender(scenario: &Scenario): address { - scenario.ctx.sender() - } +/// Generate a fresh ID for the current tx associated with this `scenario` +public fun new_object(scenario: &mut Scenario): UID { + object::new(&mut scenario.ctx) +} - /// Return the number of concluded transactions in this scenario. - /// This does not include the current transaction, e.g. this will return 0 if `next_tx` has - /// not yet been called - public fun num_concluded_txes(scenario: &Scenario): u64 { - scenario.txn_number - } +/// Return the sender of the current tx in this `scenario` +public fun sender(scenario: &Scenario): address { + scenario.ctx.sender() +} - /// Accessor for `created` field of `TransactionEffects` - public fun created(effects: &TransactionEffects): vector { - effects.created - } +/// Return the number of concluded transactions in this scenario. +/// This does not include the current transaction, e.g. this will return 0 if `next_tx` has +/// not yet been called +public fun num_concluded_txes(scenario: &Scenario): u64 { + scenario.txn_number +} - /// Accessor for `written` field of `TransactionEffects` - public fun written(effects: &TransactionEffects): vector { - effects.written - } +/// Accessor for `created` field of `TransactionEffects` +public fun created(effects: &TransactionEffects): vector { + effects.created +} - /// Accessor for `deleted` field of `TransactionEffects` - public fun deleted(effects: &TransactionEffects): vector { - effects.deleted - } +/// Accessor for `written` field of `TransactionEffects` +public fun written(effects: &TransactionEffects): vector { + effects.written +} - /// Accessor for `transferred_to_account` field of `TransactionEffects` - public fun transferred_to_account(effects: &TransactionEffects): VecMap { - effects.transferred_to_account - } +/// Accessor for `deleted` field of `TransactionEffects` +public fun deleted(effects: &TransactionEffects): vector { + effects.deleted +} - /// Accessor for `transferred_to_object` field of `TransactionEffects` - public fun transferred_to_object(effects: &TransactionEffects): VecMap { - effects.transferred_to_object - } +/// Accessor for `transferred_to_account` field of `TransactionEffects` +public fun transferred_to_account(effects: &TransactionEffects): VecMap { + effects.transferred_to_account +} - /// Accessor for `shared` field of `TransactionEffects` - public fun shared(effects: &TransactionEffects): vector { - effects.shared - } +/// Accessor for `transferred_to_object` field of `TransactionEffects` +public fun transferred_to_object(effects: &TransactionEffects): VecMap { + effects.transferred_to_object +} - /// Accessor for `frozen` field of `TransactionEffects` - public fun frozen(effects: &TransactionEffects): vector { - effects.frozen - } +/// Accessor for `shared` field of `TransactionEffects` +public fun shared(effects: &TransactionEffects): vector { + effects.shared +} - /// Accessor for `num_user_events` field of `TransactionEffects` - public fun num_user_events(effects: &TransactionEffects): u64 { - effects.num_user_events - } +/// Accessor for `frozen` field of `TransactionEffects` +public fun frozen(effects: &TransactionEffects): vector { + effects.frozen +} - // == from address == - - /// Remove the object of type `T` with ID `id` from the inventory of the `account` - /// An object is in the address's inventory if the object was transferred to the `account` - /// in a previous transaction. Using `return_to_address` is similar to `transfer` and you - /// must wait until the next transaction to re-take the object. - /// Aborts if there is no object of type `T` in the inventory with ID `id` - public native fun take_from_address_by_id( - scenario: &Scenario, - account: address, - id: ID, - ): T; - - /// Returns the most recent object of type `T` transferred to address `account` that has not - /// been taken - public native fun most_recent_id_for_address(account: address): Option; - - /// Returns all ids of type `T` transferred to address `account`. - public native fun ids_for_address(account: address): vector; - - /// helper that returns true iff `most_recent_id_for_address` returns some - public fun has_most_recent_for_address(account: address): bool { - most_recent_id_for_address(account).is_some() - } +/// Accessor for `num_user_events` field of `TransactionEffects` +public fun num_user_events(effects: &TransactionEffects): u64 { + effects.num_user_events +} - /// Helper combining `take_from_address_by_id` and `most_recent_id_for_address` - /// Aborts if there is no object of type `T` in the inventory of `account` - public fun take_from_address(scenario: &Scenario, account: address): T { - let id_opt = most_recent_id_for_address(account); - assert!(id_opt.is_some(), EEmptyInventory); - take_from_address_by_id(scenario, account, id_opt.destroy_some()) - } +// == from address == - /// Return `t` to the inventory of the `account`. `transfer` can be used directly instead, - /// but this function is helpful for test cleanliness as it will abort if the object was not - /// originally taken from this account - public fun return_to_address(account: address, t: T) { - let id = object::id(&t); - assert!(was_taken_from_address(account, id), ECantReturnObject); - sui::transfer::transfer_impl(t, account) - } +/// Remove the object of type `T` with ID `id` from the inventory of the `account` +/// An object is in the address's inventory if the object was transferred to the `account` +/// in a previous transaction. Using `return_to_address` is similar to `transfer` and you +/// must wait until the next transaction to re-take the object. +/// Aborts if there is no object of type `T` in the inventory with ID `id` +public native fun take_from_address_by_id(scenario: &Scenario, account: address, id: ID): T; - /// Returns true if the object with `ID` id was in the inventory for `account` - public native fun was_taken_from_address(account: address, id: ID): bool; +/// Returns the most recent object of type `T` transferred to address `account` that has not +/// been taken +public native fun most_recent_id_for_address(account: address): Option; - // == from sender == +/// Returns all ids of type `T` transferred to address `account`. +public native fun ids_for_address(account: address): vector; - /// helper for `take_from_address_by_id` that operates over the transaction sender - public fun take_from_sender_by_id(scenario: &Scenario, id: ID): T { - take_from_address_by_id(scenario, sender(scenario), id) - } +/// helper that returns true iff `most_recent_id_for_address` returns some +public fun has_most_recent_for_address(account: address): bool { + most_recent_id_for_address(account).is_some() +} - /// helper for `most_recent_id_for_address` that operates over the transaction sender - public fun most_recent_id_for_sender(scenario: &Scenario): Option { - most_recent_id_for_address(sender(scenario)) - } +/// Helper combining `take_from_address_by_id` and `most_recent_id_for_address` +/// Aborts if there is no object of type `T` in the inventory of `account` +public fun take_from_address(scenario: &Scenario, account: address): T { + let id_opt = most_recent_id_for_address(account); + assert!(id_opt.is_some(), EEmptyInventory); + take_from_address_by_id(scenario, account, id_opt.destroy_some()) +} - /// helper that returns true iff `most_recent_id_for_sender` returns some - public fun has_most_recent_for_sender(scenario: &Scenario): bool { - most_recent_id_for_address(sender(scenario)).is_some() - } +/// Return `t` to the inventory of the `account`. `transfer` can be used directly instead, +/// but this function is helpful for test cleanliness as it will abort if the object was not +/// originally taken from this account +public fun return_to_address(account: address, t: T) { + let id = object::id(&t); + assert!(was_taken_from_address(account, id), ECantReturnObject); + sui::transfer::transfer_impl(t, account) +} - /// helper for `take_from_address` that operates over the transaction sender - public fun take_from_sender(scenario: &Scenario): T { - take_from_address(scenario, sender(scenario)) - } +/// Returns true if the object with `ID` id was in the inventory for `account` +public native fun was_taken_from_address(account: address, id: ID): bool; - /// helper for `return_to_address` that operates over the transaction sender - public fun return_to_sender(scenario: &Scenario, t: T) { - return_to_address(sender(scenario), t) - } +// == from sender == - /// Returns true if the object with `ID` id was in the inventory for the sender - public fun was_taken_from_sender(scenario: &Scenario, id: ID): bool { - was_taken_from_address(sender(scenario), id) - } +/// helper for `take_from_address_by_id` that operates over the transaction sender +public fun take_from_sender_by_id(scenario: &Scenario, id: ID): T { + take_from_address_by_id(scenario, sender(scenario), id) +} - /// Returns all ids of type `T` transferred to the sender. - public fun ids_for_sender(scenario: &Scenario): vector { - ids_for_address(sender(scenario)) - } +/// helper for `most_recent_id_for_address` that operates over the transaction sender +public fun most_recent_id_for_sender(scenario: &Scenario): Option { + most_recent_id_for_address(sender(scenario)) +} - // == immutable == +/// helper that returns true iff `most_recent_id_for_sender` returns some +public fun has_most_recent_for_sender(scenario: &Scenario): bool { + most_recent_id_for_address(sender(scenario)).is_some() +} - /// Remove the immutable object of type `T` with ID `id` from the global inventory - /// Aborts if there is no object of type `T` in the inventory with ID `id` - public native fun take_immutable_by_id(scenario: &Scenario, id: ID): T; +/// helper for `take_from_address` that operates over the transaction sender +public fun take_from_sender(scenario: &Scenario): T { + take_from_address(scenario, sender(scenario)) +} - /// Returns the most recent immutable object of type `T` that has not been taken - public native fun most_recent_immutable_id(): Option; +/// helper for `return_to_address` that operates over the transaction sender +public fun return_to_sender(scenario: &Scenario, t: T) { + return_to_address(sender(scenario), t) +} - /// helper that returns true iff `most_recent_immutable_id` returns some - public fun has_most_recent_immutable(): bool { - most_recent_immutable_id().is_some() - } +/// Returns true if the object with `ID` id was in the inventory for the sender +public fun was_taken_from_sender(scenario: &Scenario, id: ID): bool { + was_taken_from_address(sender(scenario), id) +} - /// Helper combining `take_immutable_by_id` and `most_recent_immutable_id` - /// Aborts if there is no immutable object of type `T` in the global inventory - public fun take_immutable(scenario: &Scenario): T { - let id_opt = most_recent_immutable_id(); - assert!(id_opt.is_some(), EEmptyInventory); - take_immutable_by_id(scenario, id_opt.destroy_some()) - } +/// Returns all ids of type `T` transferred to the sender. +public fun ids_for_sender(scenario: &Scenario): vector { + ids_for_address(sender(scenario)) +} - /// Return `t` to the global inventory - public fun return_immutable(t: T) { - let id = object::id(&t); - assert!(was_taken_immutable(id), ECantReturnObject); - sui::transfer::freeze_object_impl(t) - } +// == immutable == - /// Returns true if the object with `ID` id was an immutable object in the global inventory - public native fun was_taken_immutable(id: ID): bool; +/// Remove the immutable object of type `T` with ID `id` from the global inventory +/// Aborts if there is no object of type `T` in the inventory with ID `id` +public native fun take_immutable_by_id(scenario: &Scenario, id: ID): T; - // == shared == +/// Returns the most recent immutable object of type `T` that has not been taken +public native fun most_recent_immutable_id(): Option; - /// Remove the shared object of type `T` with ID `id` from the global inventory - /// Aborts if there is no object of type `T` in the inventory with ID `id` - public native fun take_shared_by_id(scenario: &Scenario, id: ID): T; +/// helper that returns true iff `most_recent_immutable_id` returns some +public fun has_most_recent_immutable(): bool { + most_recent_immutable_id().is_some() +} - /// Returns the most recent shared object of type `T` that has not been taken - public native fun most_recent_id_shared(): Option; +/// Helper combining `take_immutable_by_id` and `most_recent_immutable_id` +/// Aborts if there is no immutable object of type `T` in the global inventory +public fun take_immutable(scenario: &Scenario): T { + let id_opt = most_recent_immutable_id(); + assert!(id_opt.is_some(), EEmptyInventory); + take_immutable_by_id(scenario, id_opt.destroy_some()) +} - /// helper that returns true iff `most_recent_id_shared` returns some - public fun has_most_recent_shared(): bool { - most_recent_id_shared().is_some() - } +/// Return `t` to the global inventory +public fun return_immutable(t: T) { + let id = object::id(&t); + assert!(was_taken_immutable(id), ECantReturnObject); + sui::transfer::freeze_object_impl(t) +} - /// Helper combining `take_shared_by_id` and `most_recent_id_shared` - /// Aborts if there is no shared object of type `T` in the global inventory - public fun take_shared(scenario: &Scenario): T { - let id_opt = most_recent_id_shared(); - assert!(id_opt.is_some(), EEmptyInventory); - take_shared_by_id(scenario, id_opt.destroy_some()) - } +/// Returns true if the object with `ID` id was an immutable object in the global inventory +public native fun was_taken_immutable(id: ID): bool; - /// Return `t` to the global inventory - public fun return_shared(t: T) { - let id = object::id(&t); - assert!(was_taken_shared(id), ECantReturnObject); - sui::transfer::share_object_impl(t) - } +// == shared == - /// Return the IDs of the receivalbe objects that `object` owns. - public fun receivable_object_ids_for_owner_id(object: ID): vector { - ids_for_address(object::id_to_address(&object)) - } +/// Remove the shared object of type `T` with ID `id` from the global inventory +/// Aborts if there is no object of type `T` in the inventory with ID `id` +public native fun take_shared_by_id(scenario: &Scenario, id: ID): T; - /// Create a `Receiving` receiving ticket for the most recent - /// object of type `T` that is owned by the `owner` object ID. - public fun most_recent_receiving_ticket( - owner: &ID - ): sui::transfer::Receiving { - let id_opt = most_recent_id_for_address(object::id_to_address(owner)); - assert!(option::is_some(&id_opt), EEmptyInventory); - let id = option::destroy_some(id_opt); - receiving_ticket_by_id(id) - } +/// Returns the most recent shared object of type `T` that has not been taken +public native fun most_recent_id_shared(): Option; - /// Create a `Receiving` receiving ticket for the object of type - /// `T` with the given `object_id`. - public fun receiving_ticket_by_id( - object_id: ID - ): sui::transfer::Receiving { - let version = allocate_receiving_ticket_for_object(object_id); - sui::transfer::make_receiver(object_id, version) - } +/// helper that returns true iff `most_recent_id_shared` returns some +public fun has_most_recent_shared(): bool { + most_recent_id_shared().is_some() +} - /// Deallocate a `Receiving` receiving ticket. This must be done in - /// order to use the object further (unless the object was received) in a - /// test scenario. - public fun return_receiving_ticket(ticket: sui::transfer::Receiving) { - let id = sui::transfer::receiving_id(&ticket); - deallocate_receiving_ticket_for_object(id); - } +/// Helper combining `take_shared_by_id` and `most_recent_id_shared` +/// Aborts if there is no shared object of type `T` in the global inventory +public fun take_shared(scenario: &Scenario): T { + let id_opt = most_recent_id_shared(); + assert!(id_opt.is_some(), EEmptyInventory); + take_shared_by_id(scenario, id_opt.destroy_some()) +} - /// Returns true if the object with `ID` id was an shared object in the global inventory - native fun was_taken_shared(id: ID): bool; +/// Return `t` to the global inventory +public fun return_shared(t: T) { + let id = object::id(&t); + assert!(was_taken_shared(id), ECantReturnObject); + sui::transfer::share_object_impl(t) +} - /// Allocate the receiving ticket for the object of type `T` with the given - /// `object_id`. Returns the current version of object. - native fun allocate_receiving_ticket_for_object(object_id: ID): u64; +/// Return the IDs of the receivalbe objects that `object` owns. +public fun receivable_object_ids_for_owner_id(object: ID): vector { + ids_for_address(object::id_to_address(&object)) +} - /// Deallocate the receiving ticket for the object with the given `object_id`. - native fun deallocate_receiving_ticket_for_object(object_id: ID); +/// Create a `Receiving` receiving ticket for the most recent +/// object of type `T` that is owned by the `owner` object ID. +public fun most_recent_receiving_ticket(owner: &ID): sui::transfer::Receiving { + let id_opt = most_recent_id_for_address(object::id_to_address(owner)); + assert!(option::is_some(&id_opt), EEmptyInventory); + let id = option::destroy_some(id_opt); + receiving_ticket_by_id(id) +} - // == internal == +/// Create a `Receiving` receiving ticket for the object of type +/// `T` with the given `object_id`. +public fun receiving_ticket_by_id(object_id: ID): sui::transfer::Receiving { + let version = allocate_receiving_ticket_for_object(object_id); + sui::transfer::make_receiver(object_id, version) +} - // internal function that ends the transaction, realizing changes (may abort with - // `ECouldNotGenerateEffects`) - native fun end_transaction(): TransactionEffects; +/// Deallocate a `Receiving` receiving ticket. This must be done in +/// order to use the object further (unless the object was received) in a +/// test scenario. +public fun return_receiving_ticket(ticket: sui::transfer::Receiving) { + let id = sui::transfer::receiving_id(&ticket); + deallocate_receiving_ticket_for_object(id); } + +/// Returns true if the object with `ID` id was an shared object in the global inventory +native fun was_taken_shared(id: ID): bool; + +/// Allocate the receiving ticket for the object of type `T` with the given +/// `object_id`. Returns the current version of object. +native fun allocate_receiving_ticket_for_object(object_id: ID): u64; + +/// Deallocate the receiving ticket for the object with the given `object_id`. +native fun deallocate_receiving_ticket_for_object(object_id: ID); + +// == internal == + +// internal function that ends the transaction, realizing changes (may abort with +// `ECouldNotGenerateEffects`) +native fun end_transaction(): TransactionEffects; diff --git a/crates/sui-framework/packages/sui-framework/sources/test/test_utils.move b/crates/sui-framework/packages/sui-framework/sources/test/test_utils.move index 4e0c56b4abf8c..5a5b1e254c1f7 100644 --- a/crates/sui-framework/packages/sui-framework/sources/test/test_utils.move +++ b/crates/sui-framework/packages/sui-framework/sources/test/test_utils.move @@ -2,27 +2,27 @@ // SPDX-License-Identifier: Apache-2.0 #[test_only] -module sui::test_utils { - public fun assert_eq(t1: T, t2: T) { - assert_ref_eq(&t1, &t2) - } +module sui::test_utils; - public fun assert_ref_eq(t1: &T, t2: &T) { - let res = t1 == t2; - if (!res) { - print(b"Assertion failed:"); - std::debug::print(t1); - print(b"!="); - std::debug::print(t2); - abort(0) - } - } +public fun assert_eq(t1: T, t2: T) { + assert_ref_eq(&t1, &t2) +} - public fun print(str: vector) { - std::debug::print(&str.to_ascii_string()) +public fun assert_ref_eq(t1: &T, t2: &T) { + let res = t1 == t2; + if (!res) { + print(b"Assertion failed:"); + std::debug::print(t1); + print(b"!="); + std::debug::print(t2); + abort (0) } +} - public native fun destroy(x: T); - - public native fun create_one_time_witness(): T; +public fun print(str: vector) { + std::debug::print(&str.to_ascii_string()) } + +public native fun destroy(x: T); + +public native fun create_one_time_witness(): T; diff --git a/crates/sui-framework/packages/sui-framework/sources/token.move b/crates/sui-framework/packages/sui-framework/sources/token.move index 634efa027b99f..e625eb2a1079d 100644 --- a/crates/sui-framework/packages/sui-framework/sources/token.move +++ b/crates/sui-framework/packages/sui-framework/sources/token.move @@ -19,726 +19,719 @@ /// The Token system allows for fine-grained control over the actions performed /// on the token. And hence it is highly suitable for applications that require /// control over the currency which a simple open-loop system can't provide. -module sui::token { - use std::string::String; - use std::type_name::{Self, TypeName}; - use sui::coin::{Coin, TreasuryCap}; - use sui::balance::{Self, Balance}; - use sui::vec_map::{Self, VecMap}; - use sui::vec_set::{Self, VecSet}; - use sui::dynamic_field as df; - use sui::event; - - /// The action is not allowed (defined) in the policy. - const EUnknownAction: u64 = 0; - /// The rule was not approved. - const ENotApproved: u64 = 1; - /// Trying to perform an admin action with a wrong cap. - const ENotAuthorized: u64 = 2; - /// The balance is too low to perform the action. - const EBalanceTooLow: u64 = 3; - /// The balance is not zero. - const ENotZero: u64 = 4; - /// The balance is not zero when trying to confirm with `TransferPolicyCap`. - const ECantConsumeBalance: u64 = 5; - /// Rule is trying to access a missing config (with type). - const ENoConfig: u64 = 6; - /// Using `confirm_request_mut` without `spent_balance`. Immutable version - /// of the function must be used instead. - const EUseImmutableConfirm: u64 = 7; - - // === Protected Actions === - - /// A Tag for the `spend` action. - const SPEND: vector = b"spend"; - /// A Tag for the `transfer` action. - const TRANSFER: vector = b"transfer"; - /// A Tag for the `to_coin` action. - const TO_COIN: vector = b"to_coin"; - /// A Tag for the `from_coin` action. - const FROM_COIN: vector = b"from_coin"; - - /// A single `Token` with `Balance` inside. Can only be owned by an address, - /// and actions performed on it must be confirmed in a matching `TokenPolicy`. - public struct Token has key { - id: UID, - /// The Balance of the `Token`. - balance: Balance, - } +module sui::token; + +use std::{string::String, type_name::{Self, TypeName}}; +use sui::{ + balance::{Self, Balance}, + coin::{Coin, TreasuryCap}, + dynamic_field as df, + event, + vec_map::{Self, VecMap}, + vec_set::{Self, VecSet} +}; + +/// The action is not allowed (defined) in the policy. +const EUnknownAction: u64 = 0; +/// The rule was not approved. +const ENotApproved: u64 = 1; +/// Trying to perform an admin action with a wrong cap. +const ENotAuthorized: u64 = 2; +/// The balance is too low to perform the action. +const EBalanceTooLow: u64 = 3; +/// The balance is not zero. +const ENotZero: u64 = 4; +/// The balance is not zero when trying to confirm with `TransferPolicyCap`. +const ECantConsumeBalance: u64 = 5; +/// Rule is trying to access a missing config (with type). +const ENoConfig: u64 = 6; +/// Using `confirm_request_mut` without `spent_balance`. Immutable version +/// of the function must be used instead. +const EUseImmutableConfirm: u64 = 7; + +// === Protected Actions === + +/// A Tag for the `spend` action. +const SPEND: vector = b"spend"; +/// A Tag for the `transfer` action. +const TRANSFER: vector = b"transfer"; +/// A Tag for the `to_coin` action. +const TO_COIN: vector = b"to_coin"; +/// A Tag for the `from_coin` action. +const FROM_COIN: vector = b"from_coin"; + +/// A single `Token` with `Balance` inside. Can only be owned by an address, +/// and actions performed on it must be confirmed in a matching `TokenPolicy`. +public struct Token has key { + id: UID, + /// The Balance of the `Token`. + balance: Balance, +} - /// A Capability that manages a single `TokenPolicy` specified in the `for` - /// field. Created together with `TokenPolicy` in the `new` function. - public struct TokenPolicyCap has key, store { id: UID, `for`: ID } +/// A Capability that manages a single `TokenPolicy` specified in the `for` +/// field. Created together with `TokenPolicy` in the `new` function. +public struct TokenPolicyCap has key, store { id: UID, `for`: ID } - /// `TokenPolicy` represents a set of rules that define what actions can be - /// performed on a `Token` and which `Rules` must be satisfied for the - /// action to succeed. +/// `TokenPolicy` represents a set of rules that define what actions can be +/// performed on a `Token` and which `Rules` must be satisfied for the +/// action to succeed. +/// +/// - For the sake of availability, `TokenPolicy` is a `key`-only object. +/// - Each `TokenPolicy` is managed by a matching `TokenPolicyCap`. +/// - For an action to become available, there needs to be a record in the +/// `rules` VecMap. To allow an action to be performed freely, there's an +/// `allow` function that can be called by the `TokenPolicyCap` owner. +public struct TokenPolicy has key { + id: UID, + /// The balance that is effectively spent by the user on the "spend" + /// action. However, actual decrease of the supply can only be done by + /// the `TreasuryCap` owner when `flush` is called. /// - /// - For the sake of availability, `TokenPolicy` is a `key`-only object. - /// - Each `TokenPolicy` is managed by a matching `TokenPolicyCap`. - /// - For an action to become available, there needs to be a record in the - /// `rules` VecMap. To allow an action to be performed freely, there's an - /// `allow` function that can be called by the `TokenPolicyCap` owner. - public struct TokenPolicy has key { - id: UID, - /// The balance that is effectively spent by the user on the "spend" - /// action. However, actual decrease of the supply can only be done by - /// the `TreasuryCap` owner when `flush` is called. - /// - /// This balance is effectively spent and cannot be accessed by anyone - /// but the `TreasuryCap` owner. - spent_balance: Balance, - /// The set of rules that define what actions can be performed on the - /// token. For each "action" there's a set of Rules that must be - /// satisfied for the `ActionRequest` to be confirmed. - rules: VecMap> - } + /// This balance is effectively spent and cannot be accessed by anyone + /// but the `TreasuryCap` owner. + spent_balance: Balance, + /// The set of rules that define what actions can be performed on the + /// token. For each "action" there's a set of Rules that must be + /// satisfied for the `ActionRequest` to be confirmed. + rules: VecMap>, +} - /// A request to perform an "Action" on a token. Stores the information - /// about the action to be performed and must be consumed by the `confirm_request` - /// or `confirm_request_mut` functions when the Rules are satisfied. - public struct ActionRequest { - /// Name of the Action to look up in the Policy. Name can be one of the - /// default actions: `transfer`, `spend`, `to_coin`, `from_coin` or a - /// custom action. - name: String, - /// Amount is present in all of the txs - amount: u64, - /// Sender is a permanent field always - sender: address, - /// Recipient is only available in `transfer` action. - recipient: Option
, - /// The balance to be "spent" in the `TokenPolicy`, only available - /// in the `spend` action. - spent_balance: Option>, - /// Collected approvals (stamps) from completed `Rules`. They're matched - /// against `TokenPolicy.rules` to determine if the request can be - /// confirmed. - approvals: VecSet, - } +/// A request to perform an "Action" on a token. Stores the information +/// about the action to be performed and must be consumed by the `confirm_request` +/// or `confirm_request_mut` functions when the Rules are satisfied. +public struct ActionRequest { + /// Name of the Action to look up in the Policy. Name can be one of the + /// default actions: `transfer`, `spend`, `to_coin`, `from_coin` or a + /// custom action. + name: String, + /// Amount is present in all of the txs + amount: u64, + /// Sender is a permanent field always + sender: address, + /// Recipient is only available in `transfer` action. + recipient: Option
, + /// The balance to be "spent" in the `TokenPolicy`, only available + /// in the `spend` action. + spent_balance: Option>, + /// Collected approvals (stamps) from completed `Rules`. They're matched + /// against `TokenPolicy.rules` to determine if the request can be + /// confirmed. + approvals: VecSet, +} - /// Dynamic field key for the `TokenPolicy` to store the `Config` for a - /// specific action `Rule`. There can be only one configuration per - /// `Rule` per `TokenPolicy`. - public struct RuleKey has store, copy, drop { is_protected: bool } - - /// An event emitted when a `TokenPolicy` is created and shared. Because - /// `TokenPolicy` can only be shared (and potentially frozen in the future), - /// we emit this event in the `share_policy` function and mark it as mutable. - public struct TokenPolicyCreated has copy, drop { - /// ID of the `TokenPolicy` that was created. - id: ID, - /// Whether the `TokenPolicy` is "shared" (mutable) or "frozen" - /// (immutable) - TBD. - is_mutable: bool, - } +/// Dynamic field key for the `TokenPolicy` to store the `Config` for a +/// specific action `Rule`. There can be only one configuration per +/// `Rule` per `TokenPolicy`. +public struct RuleKey has store, copy, drop { is_protected: bool } + +/// An event emitted when a `TokenPolicy` is created and shared. Because +/// `TokenPolicy` can only be shared (and potentially frozen in the future), +/// we emit this event in the `share_policy` function and mark it as mutable. +public struct TokenPolicyCreated has copy, drop { + /// ID of the `TokenPolicy` that was created. + id: ID, + /// Whether the `TokenPolicy` is "shared" (mutable) or "frozen" + /// (immutable) - TBD. + is_mutable: bool, +} - /// Create a new `TokenPolicy` and a matching `TokenPolicyCap`. - /// The `TokenPolicy` must then be shared using the `share_policy` method. - /// - /// `TreasuryCap` guarantees full ownership over the currency, and is unique, - /// hence it is safe to use it for authorization. - public fun new_policy( - _treasury_cap: &TreasuryCap, ctx: &mut TxContext - ): (TokenPolicy, TokenPolicyCap) { - let policy = TokenPolicy { - id: object::new(ctx), - spent_balance: balance::zero(), - rules: vec_map::empty() - }; - - let cap = TokenPolicyCap { - id: object::new(ctx), - `for`: object::id(&policy) - }; - - (policy, cap) - } +/// Create a new `TokenPolicy` and a matching `TokenPolicyCap`. +/// The `TokenPolicy` must then be shared using the `share_policy` method. +/// +/// `TreasuryCap` guarantees full ownership over the currency, and is unique, +/// hence it is safe to use it for authorization. +public fun new_policy( + _treasury_cap: &TreasuryCap, + ctx: &mut TxContext, +): (TokenPolicy, TokenPolicyCap) { + let policy = TokenPolicy { + id: object::new(ctx), + spent_balance: balance::zero(), + rules: vec_map::empty(), + }; + + let cap = TokenPolicyCap { + id: object::new(ctx), + `for`: object::id(&policy), + }; + + (policy, cap) +} - /// Share the `TokenPolicy`. Due to `key`-only restriction, it must be - /// shared after initialization. - public fun share_policy(policy: TokenPolicy) { - event::emit(TokenPolicyCreated { - id: object::id(&policy), - is_mutable: true, - }); +/// Share the `TokenPolicy`. Due to `key`-only restriction, it must be +/// shared after initialization. +public fun share_policy(policy: TokenPolicy) { + event::emit(TokenPolicyCreated { + id: object::id(&policy), + is_mutable: true, + }); - transfer::share_object(policy) - } + transfer::share_object(policy) +} + +// === Protected Actions === + +/// Transfer a `Token` to a `recipient`. Creates an `ActionRequest` for the +/// "transfer" action. The `ActionRequest` contains the `recipient` field +/// to be used in verification. +public fun transfer(t: Token, recipient: address, ctx: &mut TxContext): ActionRequest { + let amount = t.balance.value(); + transfer::transfer(t, recipient); + + new_request( + transfer_action(), + amount, + option::some(recipient), + option::none(), + ctx, + ) +} - // === Protected Actions === +/// Spend a `Token` by unwrapping it and storing the `Balance` in the +/// `ActionRequest` for the "spend" action. The `ActionRequest` contains +/// the `spent_balance` field to be used in verification. +/// +/// Spend action requires `confirm_request_mut` to be called to confirm the +/// request and join the spent balance with the `TokenPolicy.spent_balance`. +public fun spend(t: Token, ctx: &mut TxContext): ActionRequest { + let Token { id, balance } = t; + id.delete(); + + new_request( + spend_action(), + balance.value(), + option::none(), + option::some(balance), + ctx, + ) +} - /// Transfer a `Token` to a `recipient`. Creates an `ActionRequest` for the - /// "transfer" action. The `ActionRequest` contains the `recipient` field - /// to be used in verification. - public fun transfer( - t: Token, recipient: address, ctx: &mut TxContext - ): ActionRequest { - let amount = t.balance.value(); - transfer::transfer(t, recipient); +/// Convert `Token` into an open `Coin`. Creates an `ActionRequest` for the +/// "to_coin" action. +public fun to_coin(t: Token, ctx: &mut TxContext): (Coin, ActionRequest) { + let Token { id, balance } = t; + let amount = balance.value(); + id.delete(); + ( + balance.into_coin(ctx), new_request( - transfer_action(), + to_coin_action(), amount, - option::some(recipient), option::none(), - ctx - ) - } - - /// Spend a `Token` by unwrapping it and storing the `Balance` in the - /// `ActionRequest` for the "spend" action. The `ActionRequest` contains - /// the `spent_balance` field to be used in verification. - /// - /// Spend action requires `confirm_request_mut` to be called to confirm the - /// request and join the spent balance with the `TokenPolicy.spent_balance`. - public fun spend(t: Token, ctx: &mut TxContext): ActionRequest { - let Token { id, balance } = t; - id.delete(); + option::none(), + ctx, + ), + ) +} +/// Convert an open `Coin` into a `Token`. Creates an `ActionRequest` for +/// the "from_coin" action. +public fun from_coin(coin: Coin, ctx: &mut TxContext): (Token, ActionRequest) { + let amount = coin.value(); + let token = Token { + id: object::new(ctx), + balance: coin.into_balance(), + }; + + ( + token, new_request( - spend_action(), - balance.value(), + from_coin_action(), + amount, option::none(), - option::some(balance), - ctx - ) - } - - /// Convert `Token` into an open `Coin`. Creates an `ActionRequest` for the - /// "to_coin" action. - public fun to_coin( - t: Token, ctx: &mut TxContext - ): (Coin, ActionRequest) { - let Token { id, balance } = t; - let amount = balance.value(); - id.delete(); - - ( - balance.into_coin(ctx), - new_request( - to_coin_action(), - amount, - option::none(), - option::none(), - ctx - ) - ) - } - - /// Convert an open `Coin` into a `Token`. Creates an `ActionRequest` for - /// the "from_coin" action. - public fun from_coin( - coin: Coin, ctx: &mut TxContext - ): (Token, ActionRequest) { - let amount = coin.value(); - let token = Token { - id: object::new(ctx), - balance: coin.into_balance() - }; - - ( - token, - new_request( - from_coin_action(), - amount, - option::none(), - option::none(), - ctx - ) - ) - } + option::none(), + ctx, + ), + ) +} - // === Public Actions === +// === Public Actions === - /// Join two `Token`s into one, always available. - public fun join(token: &mut Token, another: Token) { - let Token { id, balance } = another; - token.balance.join(balance); - id.delete(); - } +/// Join two `Token`s into one, always available. +public fun join(token: &mut Token, another: Token) { + let Token { id, balance } = another; + token.balance.join(balance); + id.delete(); +} - /// Split a `Token` with `amount`. - /// Aborts if the `Token.balance` is lower than `amount`. - public fun split( - token: &mut Token, amount: u64, ctx: &mut TxContext - ): Token { - assert!(token.balance.value() >= amount, EBalanceTooLow); - Token { - id: object::new(ctx), - balance: token.balance.split(amount), - } +/// Split a `Token` with `amount`. +/// Aborts if the `Token.balance` is lower than `amount`. +public fun split(token: &mut Token, amount: u64, ctx: &mut TxContext): Token { + assert!(token.balance.value() >= amount, EBalanceTooLow); + Token { + id: object::new(ctx), + balance: token.balance.split(amount), } +} - /// Create a zero `Token`. - public fun zero(ctx: &mut TxContext): Token { - Token { - id: object::new(ctx), - balance: balance::zero(), - } +/// Create a zero `Token`. +public fun zero(ctx: &mut TxContext): Token { + Token { + id: object::new(ctx), + balance: balance::zero(), } +} - /// Destroy an empty `Token`, fails if the balance is non-zero. - /// Aborts if the `Token.balance` is not zero. - public fun destroy_zero(token: Token) { - let Token { id, balance } = token; - assert!(balance.value() == 0, ENotZero); - balance.destroy_zero(); - id.delete(); - } +/// Destroy an empty `Token`, fails if the balance is non-zero. +/// Aborts if the `Token.balance` is not zero. +public fun destroy_zero(token: Token) { + let Token { id, balance } = token; + assert!(balance.value() == 0, ENotZero); + balance.destroy_zero(); + id.delete(); +} - #[allow(lint(self_transfer))] - /// Transfer the `Token` to the transaction sender. - public fun keep(token: Token, ctx: &mut TxContext) { - transfer::transfer(token, ctx.sender()) - } +#[allow(lint(self_transfer))] +/// Transfer the `Token` to the transaction sender. +public fun keep(token: Token, ctx: &mut TxContext) { + transfer::transfer(token, ctx.sender()) +} - // === Request Handling === - - /// Create a new `ActionRequest`. - /// Publicly available method to allow for custom actions. - public fun new_request( - name: String, - amount: u64, - recipient: Option
, - spent_balance: Option>, - ctx: &TxContext - ): ActionRequest { - ActionRequest { - name, - amount, - recipient, - spent_balance, - sender: ctx.sender(), - approvals: vec_set::empty(), - } +// === Request Handling === + +/// Create a new `ActionRequest`. +/// Publicly available method to allow for custom actions. +public fun new_request( + name: String, + amount: u64, + recipient: Option
, + spent_balance: Option>, + ctx: &TxContext, +): ActionRequest { + ActionRequest { + name, + amount, + recipient, + spent_balance, + sender: ctx.sender(), + approvals: vec_set::empty(), } +} - /// Confirm the request against the `TokenPolicy` and return the parameters - /// of the request: (Name, Amount, Sender, Recipient). - /// - /// Cannot be used for `spend` and similar actions that deliver `spent_balance` - /// to the `TokenPolicy`. For those actions use `confirm_request_mut`. - /// - /// Aborts if: - /// - the action is not allowed (missing record in `rules`) - /// - action contains `spent_balance` (use `confirm_request_mut`) - /// - the `ActionRequest` does not meet the `TokenPolicy` rules for the action - public fun confirm_request( - policy: &TokenPolicy, - request: ActionRequest, - _ctx: &mut TxContext - ): (String, u64, address, Option
) { - assert!(request.spent_balance.is_none(), ECantConsumeBalance); - assert!(policy.rules.contains(&request.name), EUnknownAction); - - let ActionRequest { - name, approvals, - spent_balance, - amount, sender, recipient, - } = request; - - spent_balance.destroy_none(); - - let rules = &(*policy.rules.get(&name)).into_keys(); - let rules_len = rules.length(); - let mut i = 0; - - while (i < rules_len) { - let rule = &rules[i]; - assert!(approvals.contains(rule), ENotApproved); - i = i + 1; - }; - - (name, amount, sender, recipient) - } +/// Confirm the request against the `TokenPolicy` and return the parameters +/// of the request: (Name, Amount, Sender, Recipient). +/// +/// Cannot be used for `spend` and similar actions that deliver `spent_balance` +/// to the `TokenPolicy`. For those actions use `confirm_request_mut`. +/// +/// Aborts if: +/// - the action is not allowed (missing record in `rules`) +/// - action contains `spent_balance` (use `confirm_request_mut`) +/// - the `ActionRequest` does not meet the `TokenPolicy` rules for the action +public fun confirm_request( + policy: &TokenPolicy, + request: ActionRequest, + _ctx: &mut TxContext, +): (String, u64, address, Option
) { + assert!(request.spent_balance.is_none(), ECantConsumeBalance); + assert!(policy.rules.contains(&request.name), EUnknownAction); + + let ActionRequest { + name, + approvals, + spent_balance, + amount, + sender, + recipient, + } = request; + + spent_balance.destroy_none(); + + let rules = &(*policy.rules.get(&name)).into_keys(); + let rules_len = rules.length(); + let mut i = 0; + + while (i < rules_len) { + let rule = &rules[i]; + assert!(approvals.contains(rule), ENotApproved); + i = i + 1; + }; + + (name, amount, sender, recipient) +} - /// Confirm the request against the `TokenPolicy` and return the parameters - /// of the request: (Name, Amount, Sender, Recipient). - /// - /// Unlike `confirm_request` this function requires mutable access to the - /// `TokenPolicy` and must be used on `spend` action. After dealing with the - /// spent balance it calls `confirm_request` internally. - /// - /// See `confirm_request` for the list of abort conditions. - public fun confirm_request_mut( - policy: &mut TokenPolicy, - mut request: ActionRequest, - ctx: &mut TxContext - ): (String, u64, address, Option
) { - assert!(policy.rules.contains(&request.name), EUnknownAction); - assert!(request.spent_balance.is_some(), EUseImmutableConfirm); - - policy.spent_balance.join(request.spent_balance.extract()); - - confirm_request(policy, request, ctx) - } +/// Confirm the request against the `TokenPolicy` and return the parameters +/// of the request: (Name, Amount, Sender, Recipient). +/// +/// Unlike `confirm_request` this function requires mutable access to the +/// `TokenPolicy` and must be used on `spend` action. After dealing with the +/// spent balance it calls `confirm_request` internally. +/// +/// See `confirm_request` for the list of abort conditions. +public fun confirm_request_mut( + policy: &mut TokenPolicy, + mut request: ActionRequest, + ctx: &mut TxContext, +): (String, u64, address, Option
) { + assert!(policy.rules.contains(&request.name), EUnknownAction); + assert!(request.spent_balance.is_some(), EUseImmutableConfirm); + + policy.spent_balance.join(request.spent_balance.extract()); + + confirm_request(policy, request, ctx) +} - /// Confirm an `ActionRequest` as the `TokenPolicyCap` owner. This function - /// allows `TokenPolicy` owner to perform Capability-gated actions ignoring - /// the ruleset specified in the `TokenPolicy`. - /// - /// Aborts if request contains `spent_balance` due to inability of the - /// `TokenPolicyCap` to decrease supply. For scenarios like this a - /// `TreasuryCap` is required (see `confirm_with_treasury_cap`). - public fun confirm_with_policy_cap( - _policy_cap: &TokenPolicyCap, - request: ActionRequest, - _ctx: &mut TxContext - ): (String, u64, address, Option
) { - assert!(request.spent_balance.is_none(), ECantConsumeBalance); - - let ActionRequest { - name, amount, sender, recipient, approvals: _, spent_balance - } = request; +/// Confirm an `ActionRequest` as the `TokenPolicyCap` owner. This function +/// allows `TokenPolicy` owner to perform Capability-gated actions ignoring +/// the ruleset specified in the `TokenPolicy`. +/// +/// Aborts if request contains `spent_balance` due to inability of the +/// `TokenPolicyCap` to decrease supply. For scenarios like this a +/// `TreasuryCap` is required (see `confirm_with_treasury_cap`). +public fun confirm_with_policy_cap( + _policy_cap: &TokenPolicyCap, + request: ActionRequest, + _ctx: &mut TxContext, +): (String, u64, address, Option
) { + assert!(request.spent_balance.is_none(), ECantConsumeBalance); + + let ActionRequest { + name, + amount, + sender, + recipient, + approvals: _, + spent_balance, + } = request; + + spent_balance.destroy_none(); + + (name, amount, sender, recipient) +} +/// Confirm an `ActionRequest` as the `TreasuryCap` owner. This function +/// allows `TreasuryCap` owner to perform Capability-gated actions ignoring +/// the ruleset specified in the `TokenPolicy`. +/// +/// Unlike `confirm_with_policy_cap` this function allows `spent_balance` +/// to be consumed, decreasing the `total_supply` of the `Token`. +public fun confirm_with_treasury_cap( + treasury_cap: &mut TreasuryCap, + request: ActionRequest, + _ctx: &mut TxContext, +): (String, u64, address, Option
) { + let ActionRequest { + name, + amount, + sender, + recipient, + approvals: _, + spent_balance, + } = request; + + if (spent_balance.is_some()) { + treasury_cap.supply_mut().decrease_supply(spent_balance.destroy_some()); + } else { spent_balance.destroy_none(); + }; - (name, amount, sender, recipient) - } - - /// Confirm an `ActionRequest` as the `TreasuryCap` owner. This function - /// allows `TreasuryCap` owner to perform Capability-gated actions ignoring - /// the ruleset specified in the `TokenPolicy`. - /// - /// Unlike `confirm_with_policy_cap` this function allows `spent_balance` - /// to be consumed, decreasing the `total_supply` of the `Token`. - public fun confirm_with_treasury_cap( - treasury_cap: &mut TreasuryCap, - request: ActionRequest, - _ctx: &mut TxContext - ): (String, u64, address, Option
) { - let ActionRequest { - name, amount, sender, recipient, approvals: _, - spent_balance - } = request; - - if (spent_balance.is_some()) { - treasury_cap.supply_mut().decrease_supply(spent_balance.destroy_some()); - } else { - spent_balance.destroy_none(); - }; - - (name, amount, sender, recipient) - } + (name, amount, sender, recipient) +} - // === Rules API === +// === Rules API === - /// Add an "approval" to the `ActionRequest` by providing a Witness. - /// Intended to be used by Rules to add their own approvals, however, can - /// be used to add arbitrary approvals to the request (not only the ones - /// required by the `TokenPolicy`). - public fun add_approval( - _t: W, request: &mut ActionRequest, _ctx: &mut TxContext - ) { - request.approvals.insert(type_name::get()) - } +/// Add an "approval" to the `ActionRequest` by providing a Witness. +/// Intended to be used by Rules to add their own approvals, however, can +/// be used to add arbitrary approvals to the request (not only the ones +/// required by the `TokenPolicy`). +public fun add_approval(_t: W, request: &mut ActionRequest, _ctx: &mut TxContext) { + request.approvals.insert(type_name::get()) +} - /// Add a `Config` for a `Rule` in the `TokenPolicy`. Rule configuration is - /// independent from the `TokenPolicy.rules` and needs to be managed by the - /// Rule itself. Configuration is stored per `Rule` and not per `Rule` per - /// `Action` to allow reuse in different actions. - /// - /// - Rule witness guarantees that the `Config` is approved by the Rule. - /// - `TokenPolicyCap` guarantees that the `Config` setup is initiated by - /// the `TokenPolicy` owner. - public fun add_rule_config( - _rule: Rule, - self: &mut TokenPolicy, - cap: &TokenPolicyCap, - config: Config, - _ctx: &mut TxContext - ) { - assert!(object::id(self) == cap.`for`, ENotAuthorized); - df::add(&mut self.id, key(), config) - } +/// Add a `Config` for a `Rule` in the `TokenPolicy`. Rule configuration is +/// independent from the `TokenPolicy.rules` and needs to be managed by the +/// Rule itself. Configuration is stored per `Rule` and not per `Rule` per +/// `Action` to allow reuse in different actions. +/// +/// - Rule witness guarantees that the `Config` is approved by the Rule. +/// - `TokenPolicyCap` guarantees that the `Config` setup is initiated by +/// the `TokenPolicy` owner. +public fun add_rule_config( + _rule: Rule, + self: &mut TokenPolicy, + cap: &TokenPolicyCap, + config: Config, + _ctx: &mut TxContext, +) { + assert!(object::id(self) == cap.`for`, ENotAuthorized); + df::add(&mut self.id, key(), config) +} - /// Get a `Config` for a `Rule` in the `TokenPolicy`. Requires `Rule` - /// witness, hence can only be read by the `Rule` itself. This requirement - /// guarantees safety of the stored `Config` and allows for simpler dynamic - /// field management inside the Rule Config (custom type keys are not needed - /// for access gating). - /// - /// Aborts if the Config is not present. - public fun rule_config( - _rule: Rule, self: &TokenPolicy - ): &Config { - assert!(has_rule_config_with_type(self), ENoConfig); - df::borrow(&self.id, key()) - } +/// Get a `Config` for a `Rule` in the `TokenPolicy`. Requires `Rule` +/// witness, hence can only be read by the `Rule` itself. This requirement +/// guarantees safety of the stored `Config` and allows for simpler dynamic +/// field management inside the Rule Config (custom type keys are not needed +/// for access gating). +/// +/// Aborts if the Config is not present. +public fun rule_config(_rule: Rule, self: &TokenPolicy): &Config { + assert!(has_rule_config_with_type(self), ENoConfig); + df::borrow(&self.id, key()) +} - /// Get mutable access to the `Config` for a `Rule` in the `TokenPolicy`. - /// Requires `Rule` witness, hence can only be read by the `Rule` itself, - /// as well as `TokenPolicyCap` to guarantee that the `TokenPolicy` owner - /// is the one who initiated the `Config` modification. - /// - /// Aborts if: - /// - the Config is not present - /// - `TokenPolicyCap` is not matching the `TokenPolicy` - public fun rule_config_mut( - _rule: Rule, self: &mut TokenPolicy, cap: &TokenPolicyCap - ): &mut Config { - assert!(has_rule_config_with_type(self), ENoConfig); - assert!(object::id(self) == cap.`for`, ENotAuthorized); - df::borrow_mut(&mut self.id, key()) - } +/// Get mutable access to the `Config` for a `Rule` in the `TokenPolicy`. +/// Requires `Rule` witness, hence can only be read by the `Rule` itself, +/// as well as `TokenPolicyCap` to guarantee that the `TokenPolicy` owner +/// is the one who initiated the `Config` modification. +/// +/// Aborts if: +/// - the Config is not present +/// - `TokenPolicyCap` is not matching the `TokenPolicy` +public fun rule_config_mut( + _rule: Rule, + self: &mut TokenPolicy, + cap: &TokenPolicyCap, +): &mut Config { + assert!(has_rule_config_with_type(self), ENoConfig); + assert!(object::id(self) == cap.`for`, ENotAuthorized); + df::borrow_mut(&mut self.id, key()) +} - /// Remove a `Config` for a `Rule` in the `TokenPolicy`. - /// Unlike the `add_rule_config`, this function does not require a `Rule` - /// witness, hence can be performed by the `TokenPolicy` owner on their own. - /// - /// Rules need to make sure that the `Config` is present when performing - /// verification of the `ActionRequest`. - /// - /// Aborts if: - /// - the Config is not present - /// - `TokenPolicyCap` is not matching the `TokenPolicy` - public fun remove_rule_config( - self: &mut TokenPolicy, - cap: &TokenPolicyCap, - _ctx: &mut TxContext - ): Config { - assert!(has_rule_config_with_type(self), ENoConfig); - assert!(object::id(self) == cap.`for`, ENotAuthorized); - df::remove(&mut self.id, key()) - } +/// Remove a `Config` for a `Rule` in the `TokenPolicy`. +/// Unlike the `add_rule_config`, this function does not require a `Rule` +/// witness, hence can be performed by the `TokenPolicy` owner on their own. +/// +/// Rules need to make sure that the `Config` is present when performing +/// verification of the `ActionRequest`. +/// +/// Aborts if: +/// - the Config is not present +/// - `TokenPolicyCap` is not matching the `TokenPolicy` +public fun remove_rule_config( + self: &mut TokenPolicy, + cap: &TokenPolicyCap, + _ctx: &mut TxContext, +): Config { + assert!(has_rule_config_with_type(self), ENoConfig); + assert!(object::id(self) == cap.`for`, ENotAuthorized); + df::remove(&mut self.id, key()) +} - /// Check if a config for a `Rule` is set in the `TokenPolicy` without - /// checking the type of the `Config`. - public fun has_rule_config(self: &TokenPolicy): bool { - df::exists_>(&self.id, key()) - } +/// Check if a config for a `Rule` is set in the `TokenPolicy` without +/// checking the type of the `Config`. +public fun has_rule_config(self: &TokenPolicy): bool { + df::exists_>(&self.id, key()) +} - /// Check if a `Config` for a `Rule` is set in the `TokenPolicy` and that - /// it matches the type provided. - public fun has_rule_config_with_type( - self: &TokenPolicy - ): bool { - df::exists_with_type, Config>(&self.id, key()) - } +/// Check if a `Config` for a `Rule` is set in the `TokenPolicy` and that +/// it matches the type provided. +public fun has_rule_config_with_type(self: &TokenPolicy): bool { + df::exists_with_type, Config>(&self.id, key()) +} - // === Protected: Setting Rules === +// === Protected: Setting Rules === - /// Allows an `action` to be performed on the `Token` freely by adding an - /// empty set of `Rules` for the `action`. - /// - /// Aborts if the `TokenPolicyCap` is not matching the `TokenPolicy`. - public fun allow( - self: &mut TokenPolicy, - cap: &TokenPolicyCap, - action: String, - _ctx: &mut TxContext - ) { - assert!(object::id(self) == cap.`for`, ENotAuthorized); - self.rules.insert(action, vec_set::empty()); - } +/// Allows an `action` to be performed on the `Token` freely by adding an +/// empty set of `Rules` for the `action`. +/// +/// Aborts if the `TokenPolicyCap` is not matching the `TokenPolicy`. +public fun allow( + self: &mut TokenPolicy, + cap: &TokenPolicyCap, + action: String, + _ctx: &mut TxContext, +) { + assert!(object::id(self) == cap.`for`, ENotAuthorized); + self.rules.insert(action, vec_set::empty()); +} - /// Completely disallows an `action` on the `Token` by removing the record - /// from the `TokenPolicy.rules`. - /// - /// Aborts if the `TokenPolicyCap` is not matching the `TokenPolicy`. - public fun disallow( - self: &mut TokenPolicy, - cap: &TokenPolicyCap, - action: String, - _ctx: &mut TxContext - ) { - assert!(object::id(self) == cap.`for`, ENotAuthorized); - self.rules.remove(&action); - } +/// Completely disallows an `action` on the `Token` by removing the record +/// from the `TokenPolicy.rules`. +/// +/// Aborts if the `TokenPolicyCap` is not matching the `TokenPolicy`. +public fun disallow( + self: &mut TokenPolicy, + cap: &TokenPolicyCap, + action: String, + _ctx: &mut TxContext, +) { + assert!(object::id(self) == cap.`for`, ENotAuthorized); + self.rules.remove(&action); +} - /// Adds a Rule for an action with `name` in the `TokenPolicy`. - /// - /// Aborts if the `TokenPolicyCap` is not matching the `TokenPolicy`. - public fun add_rule_for_action( - self: &mut TokenPolicy, - cap: &TokenPolicyCap, - action: String, - ctx: &mut TxContext - ) { - assert!(object::id(self) == cap.`for`, ENotAuthorized); - if (!self.rules.contains(&action)) { - allow(self, cap, action, ctx); - }; - - self.rules.get_mut(&action).insert(type_name::get()) - } +/// Adds a Rule for an action with `name` in the `TokenPolicy`. +/// +/// Aborts if the `TokenPolicyCap` is not matching the `TokenPolicy`. +public fun add_rule_for_action( + self: &mut TokenPolicy, + cap: &TokenPolicyCap, + action: String, + ctx: &mut TxContext, +) { + assert!(object::id(self) == cap.`for`, ENotAuthorized); + if (!self.rules.contains(&action)) { + allow(self, cap, action, ctx); + }; + + self.rules.get_mut(&action).insert(type_name::get()) +} - /// Removes a rule for an action with `name` in the `TokenPolicy`. Returns - /// the config object to be handled by the sender (or a Rule itself). - /// - /// Aborts if the `TokenPolicyCap` is not matching the `TokenPolicy`. - public fun remove_rule_for_action( - self: &mut TokenPolicy, - cap: &TokenPolicyCap, - action: String, - _ctx: &mut TxContext - ) { - assert!(object::id(self) == cap.`for`, ENotAuthorized); - - self.rules.get_mut(&action).remove(&type_name::get()) - } +/// Removes a rule for an action with `name` in the `TokenPolicy`. Returns +/// the config object to be handled by the sender (or a Rule itself). +/// +/// Aborts if the `TokenPolicyCap` is not matching the `TokenPolicy`. +public fun remove_rule_for_action( + self: &mut TokenPolicy, + cap: &TokenPolicyCap, + action: String, + _ctx: &mut TxContext, +) { + assert!(object::id(self) == cap.`for`, ENotAuthorized); + + self.rules.get_mut(&action).remove(&type_name::get()) +} - // === Protected: Treasury Management === +// === Protected: Treasury Management === - /// Mint a `Token` with a given `amount` using the `TreasuryCap`. - public fun mint( - cap: &mut TreasuryCap, amount: u64, ctx: &mut TxContext - ): Token { - let balance = cap.supply_mut().increase_supply(amount); - Token { id: object::new(ctx), balance } - } +/// Mint a `Token` with a given `amount` using the `TreasuryCap`. +public fun mint(cap: &mut TreasuryCap, amount: u64, ctx: &mut TxContext): Token { + let balance = cap.supply_mut().increase_supply(amount); + Token { id: object::new(ctx), balance } +} - /// Burn a `Token` using the `TreasuryCap`. - public fun burn(cap: &mut TreasuryCap, token: Token) { - let Token { id, balance } = token; - cap.supply_mut().decrease_supply(balance); - id.delete(); - } +/// Burn a `Token` using the `TreasuryCap`. +public fun burn(cap: &mut TreasuryCap, token: Token) { + let Token { id, balance } = token; + cap.supply_mut().decrease_supply(balance); + id.delete(); +} - /// Flush the `TokenPolicy.spent_balance` into the `TreasuryCap`. This - /// action is only available to the `TreasuryCap` owner. - public fun flush( - self: &mut TokenPolicy, - cap: &mut TreasuryCap, - _ctx: &mut TxContext - ): u64 { - let amount = self.spent_balance.value(); - let balance = self.spent_balance.split(amount); - cap.supply_mut().decrease_supply(balance) - } +/// Flush the `TokenPolicy.spent_balance` into the `TreasuryCap`. This +/// action is only available to the `TreasuryCap` owner. +public fun flush( + self: &mut TokenPolicy, + cap: &mut TreasuryCap, + _ctx: &mut TxContext, +): u64 { + let amount = self.spent_balance.value(); + let balance = self.spent_balance.split(amount); + cap.supply_mut().decrease_supply(balance) +} - // === Getters: `TokenPolicy` and `Token` === +// === Getters: `TokenPolicy` and `Token` === - /// Check whether an action is present in the rules VecMap. - public fun is_allowed(self: &TokenPolicy, action: &String): bool { - self.rules.contains(action) - } +/// Check whether an action is present in the rules VecMap. +public fun is_allowed(self: &TokenPolicy, action: &String): bool { + self.rules.contains(action) +} - /// Returns the rules required for a specific action. - public fun rules( - self: &TokenPolicy, action: &String - ): VecSet { - *self.rules.get(action) - } +/// Returns the rules required for a specific action. +public fun rules(self: &TokenPolicy, action: &String): VecSet { + *self.rules.get(action) +} - /// Returns the `spent_balance` of the `TokenPolicy`. - public fun spent_balance(self: &TokenPolicy): u64 { - self.spent_balance.value() - } +/// Returns the `spent_balance` of the `TokenPolicy`. +public fun spent_balance(self: &TokenPolicy): u64 { + self.spent_balance.value() +} - /// Returns the `balance` of the `Token`. - public fun value(t: &Token): u64 { - t.balance.value() - } +/// Returns the `balance` of the `Token`. +public fun value(t: &Token): u64 { + t.balance.value() +} - // === Action Names === +// === Action Names === - /// Name of the Transfer action. - public fun transfer_action(): String { - let transfer_str = TRANSFER; - transfer_str.to_string() - } +/// Name of the Transfer action. +public fun transfer_action(): String { + let transfer_str = TRANSFER; + transfer_str.to_string() +} - /// Name of the `Spend` action. - public fun spend_action(): String { - let spend_str = SPEND; - spend_str.to_string() - } +/// Name of the `Spend` action. +public fun spend_action(): String { + let spend_str = SPEND; + spend_str.to_string() +} - /// Name of the `ToCoin` action. - public fun to_coin_action(): String { - let to_coin_str = TO_COIN; - to_coin_str.to_string() - } +/// Name of the `ToCoin` action. +public fun to_coin_action(): String { + let to_coin_str = TO_COIN; + to_coin_str.to_string() +} - /// Name of the `FromCoin` action. - public fun from_coin_action(): String { - let from_coin_str = FROM_COIN; - from_coin_str.to_string() - } +/// Name of the `FromCoin` action. +public fun from_coin_action(): String { + let from_coin_str = FROM_COIN; + from_coin_str.to_string() +} - // === Action Request Fields == +// === Action Request Fields == - /// The Action in the `ActionRequest`. - public fun action(self: &ActionRequest): String { self.name } +/// The Action in the `ActionRequest`. +public fun action(self: &ActionRequest): String { self.name } - /// Amount of the `ActionRequest`. - public fun amount(self: &ActionRequest): u64 { self.amount } +/// Amount of the `ActionRequest`. +public fun amount(self: &ActionRequest): u64 { self.amount } - /// Sender of the `ActionRequest`. - public fun sender(self: &ActionRequest): address { self.sender } +/// Sender of the `ActionRequest`. +public fun sender(self: &ActionRequest): address { self.sender } - /// Recipient of the `ActionRequest`. - public fun recipient(self: &ActionRequest): Option
{ - self.recipient - } +/// Recipient of the `ActionRequest`. +public fun recipient(self: &ActionRequest): Option
{ + self.recipient +} - /// Approvals of the `ActionRequest`. - public fun approvals(self: &ActionRequest): VecSet { - self.approvals - } +/// Approvals of the `ActionRequest`. +public fun approvals(self: &ActionRequest): VecSet { + self.approvals +} - /// Burned balance of the `ActionRequest`. - public fun spent(self: &ActionRequest): Option { - if (self.spent_balance.is_some()) { - option::some(self.spent_balance.borrow().value()) - } else { - option::none() - } +/// Burned balance of the `ActionRequest`. +public fun spent(self: &ActionRequest): Option { + if (self.spent_balance.is_some()) { + option::some(self.spent_balance.borrow().value()) + } else { + option::none() } +} - // === Internal === +// === Internal === - /// Create a new `RuleKey` for a `Rule`. The `is_protected` field is kept - /// for potential future use, if Rules were to have a freely modifiable - /// storage as addition / replacement for the `Config` system. - /// - /// The goal of `is_protected` is to potentially allow Rules store a mutable - /// version of their configuration and mutate state on user action. - fun key(): RuleKey { RuleKey { is_protected: true } } - - // === Testing === - - #[test_only] - public fun new_policy_for_testing( - ctx: &mut TxContext - ): (TokenPolicy, TokenPolicyCap) { - let policy = TokenPolicy { - id: object::new(ctx), - rules: vec_map::empty(), - spent_balance: balance::zero(), - }; - let cap = TokenPolicyCap { - id: object::new(ctx), - `for`: object::id(&policy) - }; - - (policy, cap) - } +/// Create a new `RuleKey` for a `Rule`. The `is_protected` field is kept +/// for potential future use, if Rules were to have a freely modifiable +/// storage as addition / replacement for the `Config` system. +/// +/// The goal of `is_protected` is to potentially allow Rules store a mutable +/// version of their configuration and mutate state on user action. +fun key(): RuleKey { RuleKey { is_protected: true } } + +// === Testing === + +#[test_only] +public fun new_policy_for_testing(ctx: &mut TxContext): (TokenPolicy, TokenPolicyCap) { + let policy = TokenPolicy { + id: object::new(ctx), + rules: vec_map::empty(), + spent_balance: balance::zero(), + }; + let cap = TokenPolicyCap { + id: object::new(ctx), + `for`: object::id(&policy), + }; + + (policy, cap) +} - #[test_only] - public fun burn_policy_for_testing( - policy: TokenPolicy, - cap: TokenPolicyCap - ) { - let TokenPolicyCap { id: cap_id, `for`: _ } = cap; - let TokenPolicy { id, rules: _, spent_balance } = policy; - spent_balance.destroy_for_testing(); - cap_id.delete(); - id.delete(); - } +#[test_only] +public fun burn_policy_for_testing(policy: TokenPolicy, cap: TokenPolicyCap) { + let TokenPolicyCap { id: cap_id, `for`: _ } = cap; + let TokenPolicy { id, rules: _, spent_balance } = policy; + spent_balance.destroy_for_testing(); + cap_id.delete(); + id.delete(); +} - #[test_only] - public fun mint_for_testing(amount: u64, ctx: &mut TxContext): Token { - let balance = balance::create_for_testing(amount); - Token { id: object::new(ctx), balance } - } +#[test_only] +public fun mint_for_testing(amount: u64, ctx: &mut TxContext): Token { + let balance = balance::create_for_testing(amount); + Token { id: object::new(ctx), balance } +} - #[test_only] - public fun burn_for_testing(token: Token) { - let Token { id, balance } = token; - balance.destroy_for_testing(); - id.delete(); - } +#[test_only] +public fun burn_for_testing(token: Token) { + let Token { id, balance } = token; + balance.destroy_for_testing(); + id.delete(); } diff --git a/crates/sui-framework/packages/sui-framework/sources/transfer.move b/crates/sui-framework/packages/sui-framework/sources/transfer.move index 7c603da4c9f64..dfa1db2046e2e 100644 --- a/crates/sui-framework/packages/sui-framework/sources/transfer.move +++ b/crates/sui-framework/packages/sui-framework/sources/transfer.move @@ -2,147 +2,136 @@ // SPDX-License-Identifier: Apache-2.0 #[allow(unused_const)] -module sui::transfer { - - /// This represents the ability to `receive` an object of type `T`. - /// This type is ephemeral per-transaction and cannot be stored on-chain. - /// This does not represent the obligation to receive the object that it - /// references, but simply the ability to receive the object with object ID - /// `id` at version `version` if you can prove mutable access to the parent - /// object during the transaction. - /// Internals of this struct are opaque outside this module. - public struct Receiving has drop { - id: ID, - version: u64, - } - - /// Shared an object that was previously created. Shared objects must currently - /// be constructed in the transaction they are created. - const ESharedNonNewObject: u64 = 0; - - #[allow(unused_const)] - /// Serialization of the object failed. - const EBCSSerializationFailure: u64 = 1; - - #[allow(unused_const)] - /// The object being received is not of the expected type. - const EReceivingObjectTypeMismatch: u64 = 2; - - #[allow(unused_const)] - /// Represents both the case where the object does not exist and the case where the object is not - /// able to be accessed through the parent that is passed-in. - const EUnableToReceiveObject: u64 = 3; - - #[allow(unused_const)] - /// Shared object operations such as wrapping, freezing, and converting to owned are not allowed. - const ESharedObjectOperationNotSupported: u64 = 4; - - - /// Transfer ownership of `obj` to `recipient`. `obj` must have the `key` attribute, - /// which (in turn) ensures that `obj` has a globally unique ID. Note that if the recipient - /// address represents an object ID, the `obj` sent will be inaccessible after the transfer - /// (though they will be retrievable at a future date once new features are added). - /// This function has custom rules performed by the Sui Move bytecode verifier that ensures - /// that `T` is an object defined in the module where `transfer` is invoked. Use - /// `public_transfer` to transfer an object with `store` outside of its module. - public fun transfer(obj: T, recipient: address) { - transfer_impl(obj, recipient) - } - - /// Transfer ownership of `obj` to `recipient`. `obj` must have the `key` attribute, - /// which (in turn) ensures that `obj` has a globally unique ID. Note that if the recipient - /// address represents an object ID, the `obj` sent will be inaccessible after the transfer - /// (though they will be retrievable at a future date once new features are added). - /// The object must have `store` to be transferred outside of its module. - public fun public_transfer(obj: T, recipient: address) { - transfer_impl(obj, recipient) - } - - /// Freeze `obj`. After freezing `obj` becomes immutable and can no longer be transferred or - /// mutated. - /// This function has custom rules performed by the Sui Move bytecode verifier that ensures - /// that `T` is an object defined in the module where `freeze_object` is invoked. Use - /// `public_freeze_object` to freeze an object with `store` outside of its module. - public fun freeze_object(obj: T) { - freeze_object_impl(obj) - } - - /// Freeze `obj`. After freezing `obj` becomes immutable and can no longer be transferred or - /// mutated. - /// The object must have `store` to be frozen outside of its module. - public fun public_freeze_object(obj: T) { - freeze_object_impl(obj) - } - - /// Turn the given object into a mutable shared object that everyone can access and mutate. - /// This is irreversible, i.e. once an object is shared, it will stay shared forever. - /// Aborts with `ESharedNonNewObject` of the object being shared was not created in this - /// transaction. This restriction may be relaxed in the future. - /// This function has custom rules performed by the Sui Move bytecode verifier that ensures - /// that `T` is an object defined in the module where `share_object` is invoked. Use - /// `public_share_object` to share an object with `store` outside of its module. - public fun share_object(obj: T) { - share_object_impl(obj) - } - - /// Turn the given object into a mutable shared object that everyone can access and mutate. - /// This is irreversible, i.e. once an object is shared, it will stay shared forever. - /// Aborts with `ESharedNonNewObject` of the object being shared was not created in this - /// transaction. This restriction may be relaxed in the future. - /// The object must have `store` to be shared outside of its module. - public fun public_share_object(obj: T) { - share_object_impl(obj) - } - - /// Given mutable (i.e., locked) access to the `parent` and a `Receiving` argument - /// referencing an object of type `T` owned by `parent` use the `to_receive` - /// argument to receive and return the referenced owned object of type `T`. - /// This function has custom rules performed by the Sui Move bytecode verifier that ensures - /// that `T` is an object defined in the module where `receive` is invoked. Use - /// `public_receive` to receivne an object with `store` outside of its module. - public fun receive(parent: &mut UID, to_receive: Receiving): T { - let Receiving { - id, - version, - } = to_receive; - receive_impl(parent.to_address(), id, version) - } - - /// Given mutable (i.e., locked) access to the `parent` and a `Receiving` argument - /// referencing an object of type `T` owned by `parent` use the `to_receive` - /// argument to receive and return the referenced owned object of type `T`. - /// The object must have `store` to be received outside of its defining module. - public fun public_receive(parent: &mut UID, to_receive: Receiving): T { - let Receiving { - id, - version, - } = to_receive; - receive_impl(parent.to_address(), id, version) - } - - /// Return the object ID that the given `Receiving` argument references. - public fun receiving_object_id(receiving: &Receiving): ID { - receiving.id - } - - public(package) native fun freeze_object_impl(obj: T); - - public(package) native fun share_object_impl(obj: T); - - public(package) native fun transfer_impl(obj: T, recipient: address); - - native fun receive_impl(parent: address, to_receive: ID, version: u64): T; - - #[test_only] - public(package) fun make_receiver(id: ID, version: u64): Receiving { - Receiving { - id, - version, - } - } - - #[test_only] - public(package) fun receiving_id(r: &Receiving): ID { - r.id - } +module sui::transfer; + +/// This represents the ability to `receive` an object of type `T`. +/// This type is ephemeral per-transaction and cannot be stored on-chain. +/// This does not represent the obligation to receive the object that it +/// references, but simply the ability to receive the object with object ID +/// `id` at version `version` if you can prove mutable access to the parent +/// object during the transaction. +/// Internals of this struct are opaque outside this module. +public struct Receiving has drop { + id: ID, + version: u64, +} + +/// Shared an object that was previously created. Shared objects must currently +/// be constructed in the transaction they are created. +const ESharedNonNewObject: u64 = 0; + +#[allow(unused_const)] +/// Serialization of the object failed. +const EBCSSerializationFailure: u64 = 1; + +#[allow(unused_const)] +/// The object being received is not of the expected type. +const EReceivingObjectTypeMismatch: u64 = 2; + +#[allow(unused_const)] +/// Represents both the case where the object does not exist and the case where the object is not +/// able to be accessed through the parent that is passed-in. +const EUnableToReceiveObject: u64 = 3; + +#[allow(unused_const)] +/// Shared object operations such as wrapping, freezing, and converting to owned are not allowed. +const ESharedObjectOperationNotSupported: u64 = 4; + +/// Transfer ownership of `obj` to `recipient`. `obj` must have the `key` attribute, +/// which (in turn) ensures that `obj` has a globally unique ID. Note that if the recipient +/// address represents an object ID, the `obj` sent will be inaccessible after the transfer +/// (though they will be retrievable at a future date once new features are added). +/// This function has custom rules performed by the Sui Move bytecode verifier that ensures +/// that `T` is an object defined in the module where `transfer` is invoked. Use +/// `public_transfer` to transfer an object with `store` outside of its module. +public fun transfer(obj: T, recipient: address) { + transfer_impl(obj, recipient) +} + +/// Transfer ownership of `obj` to `recipient`. `obj` must have the `key` attribute, +/// which (in turn) ensures that `obj` has a globally unique ID. Note that if the recipient +/// address represents an object ID, the `obj` sent will be inaccessible after the transfer +/// (though they will be retrievable at a future date once new features are added). +/// The object must have `store` to be transferred outside of its module. +public fun public_transfer(obj: T, recipient: address) { + transfer_impl(obj, recipient) +} + +/// Freeze `obj`. After freezing `obj` becomes immutable and can no longer be transferred or +/// mutated. +/// This function has custom rules performed by the Sui Move bytecode verifier that ensures +/// that `T` is an object defined in the module where `freeze_object` is invoked. Use +/// `public_freeze_object` to freeze an object with `store` outside of its module. +public fun freeze_object(obj: T) { + freeze_object_impl(obj) +} + +/// Freeze `obj`. After freezing `obj` becomes immutable and can no longer be transferred or +/// mutated. +/// The object must have `store` to be frozen outside of its module. +public fun public_freeze_object(obj: T) { + freeze_object_impl(obj) +} + +/// Turn the given object into a mutable shared object that everyone can access and mutate. +/// This is irreversible, i.e. once an object is shared, it will stay shared forever. +/// Aborts with `ESharedNonNewObject` of the object being shared was not created in this +/// transaction. This restriction may be relaxed in the future. +/// This function has custom rules performed by the Sui Move bytecode verifier that ensures +/// that `T` is an object defined in the module where `share_object` is invoked. Use +/// `public_share_object` to share an object with `store` outside of its module. +public fun share_object(obj: T) { + share_object_impl(obj) +} + +/// Turn the given object into a mutable shared object that everyone can access and mutate. +/// This is irreversible, i.e. once an object is shared, it will stay shared forever. +/// Aborts with `ESharedNonNewObject` of the object being shared was not created in this +/// transaction. This restriction may be relaxed in the future. +/// The object must have `store` to be shared outside of its module. +public fun public_share_object(obj: T) { + share_object_impl(obj) +} + +/// Given mutable (i.e., locked) access to the `parent` and a `Receiving` argument +/// referencing an object of type `T` owned by `parent` use the `to_receive` +/// argument to receive and return the referenced owned object of type `T`. +/// This function has custom rules performed by the Sui Move bytecode verifier that ensures +/// that `T` is an object defined in the module where `receive` is invoked. Use +/// `public_receive` to receivne an object with `store` outside of its module. +public fun receive(parent: &mut UID, to_receive: Receiving): T { + let Receiving { id, version } = to_receive; + receive_impl(parent.to_address(), id, version) +} + +/// Given mutable (i.e., locked) access to the `parent` and a `Receiving` argument +/// referencing an object of type `T` owned by `parent` use the `to_receive` +/// argument to receive and return the referenced owned object of type `T`. +/// The object must have `store` to be received outside of its defining module. +public fun public_receive(parent: &mut UID, to_receive: Receiving): T { + let Receiving { id, version } = to_receive; + receive_impl(parent.to_address(), id, version) +} + +/// Return the object ID that the given `Receiving` argument references. +public fun receiving_object_id(receiving: &Receiving): ID { + receiving.id +} + +public(package) native fun freeze_object_impl(obj: T); + +public(package) native fun share_object_impl(obj: T); + +public(package) native fun transfer_impl(obj: T, recipient: address); + +native fun receive_impl(parent: address, to_receive: ID, version: u64): T; + +#[test_only] +public(package) fun make_receiver(id: ID, version: u64): Receiving { + Receiving { id, version } +} + +#[test_only] +public(package) fun receiving_id(r: &Receiving): ID { + r.id } diff --git a/crates/sui-framework/packages/sui-framework/sources/tx_context.move b/crates/sui-framework/packages/sui-framework/sources/tx_context.move index 56111acecdc51..1fdef9ff83a81 100644 --- a/crates/sui-framework/packages/sui-framework/sources/tx_context.move +++ b/crates/sui-framework/packages/sui-framework/sources/tx_context.move @@ -1,142 +1,141 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -module sui::tx_context { - - #[test_only] - /// Number of bytes in an tx hash (which will be the transaction digest) - const TX_HASH_LENGTH: u64 = 32; - - #[test_only] - /// Expected an tx hash of length 32, but found a different length - const EBadTxHashLength: u64 = 0; - - #[test_only] - /// Attempt to get the most recent created object ID when none has been created. - const ENoIDsCreated: u64 = 1; - - /// Information about the transaction currently being executed. - /// This cannot be constructed by a transaction--it is a privileged object created by - /// the VM and passed in to the entrypoint of the transaction as `&mut TxContext`. - public struct TxContext has drop { - /// The address of the user that signed the current transaction - sender: address, - /// Hash of the current transaction - tx_hash: vector, - /// The current epoch number - epoch: u64, - /// Timestamp that the epoch started at - epoch_timestamp_ms: u64, - /// Counter recording the number of fresh id's created while executing - /// this transaction. Always 0 at the start of a transaction - ids_created: u64 - } - - /// Return the address of the user that signed the current - /// transaction - public fun sender(self: &TxContext): address { - self.sender - } - - /// Return the transaction digest (hash of transaction inputs). - /// Please do not use as a source of randomness. - public fun digest(self: &TxContext): &vector { - &self.tx_hash - } - - /// Return the current epoch - public fun epoch(self: &TxContext): u64 { - self.epoch - } - - /// Return the epoch start time as a unix timestamp in milliseconds. - public fun epoch_timestamp_ms(self: &TxContext): u64 { - self.epoch_timestamp_ms - } - - /// Create an `address` that has not been used. As it is an object address, it will never - /// occur as the address for a user. - /// In other words, the generated address is a globally unique object ID. - public fun fresh_object_address(ctx: &mut TxContext): address { - let ids_created = ctx.ids_created; - let id = derive_id(*&ctx.tx_hash, ids_created); - ctx.ids_created = ids_created + 1; - id - } - - #[allow(unused_function)] - /// Return the number of id's created by the current transaction. - /// Hidden for now, but may expose later - fun ids_created(self: &TxContext): u64 { - self.ids_created - } - - /// Native function for deriving an ID via hash(tx_hash || ids_created) - native fun derive_id(tx_hash: vector, ids_created: u64): address; - - // ==== test-only functions ==== - - #[test_only] - /// Create a `TxContext` for testing - public fun new( - sender: address, - tx_hash: vector, - epoch: u64, - epoch_timestamp_ms: u64, - ids_created: u64, - ): TxContext { - assert!(tx_hash.length() == TX_HASH_LENGTH, EBadTxHashLength); - TxContext { sender, tx_hash, epoch, epoch_timestamp_ms, ids_created } - } - - #[test_only] - /// Create a `TxContext` for testing, with a potentially non-zero epoch number. - public fun new_from_hint( - addr: address, - hint: u64, - epoch: u64, - epoch_timestamp_ms: u64, - ids_created: u64, - ): TxContext { - new(addr, dummy_tx_hash_with_hint(hint), epoch, epoch_timestamp_ms, ids_created) - } - - #[test_only] - /// Create a dummy `TxContext` for testing - public fun dummy(): TxContext { - let tx_hash = x"3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"; - new(@0x0, tx_hash, 0, 0, 0) - } - - #[test_only] - /// Utility for creating 256 unique input hashes. - /// These hashes are guaranteed to be unique given a unique `hint: u64` - fun dummy_tx_hash_with_hint(hint: u64): vector { - let mut tx_hash = std::bcs::to_bytes(&hint); - while (tx_hash.length() < TX_HASH_LENGTH) tx_hash.push_back(0); - tx_hash - } - - #[test_only] - public fun get_ids_created(self: &TxContext): u64 { - ids_created(self) - } - - #[test_only] - /// Return the most recent created object ID. - public fun last_created_object_id(self: &TxContext): address { - let ids_created = self.ids_created; - assert!(ids_created > 0, ENoIDsCreated); - derive_id(*&self.tx_hash, ids_created - 1) - } - - #[test_only] - public fun increment_epoch_number(self: &mut TxContext) { - self.epoch = self.epoch + 1 - } - - #[test_only] - public fun increment_epoch_timestamp(self: &mut TxContext, delta_ms: u64) { - self.epoch_timestamp_ms = self.epoch_timestamp_ms + delta_ms - } +module sui::tx_context; + +#[test_only] +/// Number of bytes in an tx hash (which will be the transaction digest) +const TX_HASH_LENGTH: u64 = 32; + +#[test_only] +/// Expected an tx hash of length 32, but found a different length +const EBadTxHashLength: u64 = 0; + +#[test_only] +/// Attempt to get the most recent created object ID when none has been created. +const ENoIDsCreated: u64 = 1; + +/// Information about the transaction currently being executed. +/// This cannot be constructed by a transaction--it is a privileged object created by +/// the VM and passed in to the entrypoint of the transaction as `&mut TxContext`. +public struct TxContext has drop { + /// The address of the user that signed the current transaction + sender: address, + /// Hash of the current transaction + tx_hash: vector, + /// The current epoch number + epoch: u64, + /// Timestamp that the epoch started at + epoch_timestamp_ms: u64, + /// Counter recording the number of fresh id's created while executing + /// this transaction. Always 0 at the start of a transaction + ids_created: u64, +} + +/// Return the address of the user that signed the current +/// transaction +public fun sender(self: &TxContext): address { + self.sender +} + +/// Return the transaction digest (hash of transaction inputs). +/// Please do not use as a source of randomness. +public fun digest(self: &TxContext): &vector { + &self.tx_hash +} + +/// Return the current epoch +public fun epoch(self: &TxContext): u64 { + self.epoch +} + +/// Return the epoch start time as a unix timestamp in milliseconds. +public fun epoch_timestamp_ms(self: &TxContext): u64 { + self.epoch_timestamp_ms +} + +/// Create an `address` that has not been used. As it is an object address, it will never +/// occur as the address for a user. +/// In other words, the generated address is a globally unique object ID. +public fun fresh_object_address(ctx: &mut TxContext): address { + let ids_created = ctx.ids_created; + let id = derive_id(*&ctx.tx_hash, ids_created); + ctx.ids_created = ids_created + 1; + id +} + +#[allow(unused_function)] +/// Return the number of id's created by the current transaction. +/// Hidden for now, but may expose later +fun ids_created(self: &TxContext): u64 { + self.ids_created +} + +/// Native function for deriving an ID via hash(tx_hash || ids_created) +native fun derive_id(tx_hash: vector, ids_created: u64): address; + +// ==== test-only functions ==== + +#[test_only] +/// Create a `TxContext` for testing +public fun new( + sender: address, + tx_hash: vector, + epoch: u64, + epoch_timestamp_ms: u64, + ids_created: u64, +): TxContext { + assert!(tx_hash.length() == TX_HASH_LENGTH, EBadTxHashLength); + TxContext { sender, tx_hash, epoch, epoch_timestamp_ms, ids_created } +} + +#[test_only] +/// Create a `TxContext` for testing, with a potentially non-zero epoch number. +public fun new_from_hint( + addr: address, + hint: u64, + epoch: u64, + epoch_timestamp_ms: u64, + ids_created: u64, +): TxContext { + new(addr, dummy_tx_hash_with_hint(hint), epoch, epoch_timestamp_ms, ids_created) +} + +#[test_only] +/// Create a dummy `TxContext` for testing +public fun dummy(): TxContext { + let tx_hash = x"3a985da74fe225b2045c172d6bd390bd855f086e3e9d525b46bfe24511431532"; + new(@0x0, tx_hash, 0, 0, 0) +} + +#[test_only] +/// Utility for creating 256 unique input hashes. +/// These hashes are guaranteed to be unique given a unique `hint: u64` +fun dummy_tx_hash_with_hint(hint: u64): vector { + let mut tx_hash = std::bcs::to_bytes(&hint); + while (tx_hash.length() < TX_HASH_LENGTH) tx_hash.push_back(0); + tx_hash +} + +#[test_only] +public fun get_ids_created(self: &TxContext): u64 { + ids_created(self) +} + +#[test_only] +/// Return the most recent created object ID. +public fun last_created_object_id(self: &TxContext): address { + let ids_created = self.ids_created; + assert!(ids_created > 0, ENoIDsCreated); + derive_id(*&self.tx_hash, ids_created - 1) +} + +#[test_only] +public fun increment_epoch_number(self: &mut TxContext) { + self.epoch = self.epoch + 1 +} + +#[test_only] +public fun increment_epoch_timestamp(self: &mut TxContext, delta_ms: u64) { + self.epoch_timestamp_ms = self.epoch_timestamp_ms + delta_ms } diff --git a/crates/sui-framework/packages/sui-framework/sources/types.move b/crates/sui-framework/packages/sui-framework/sources/types.move index 64a68a82434d3..adfb18a734ec2 100644 --- a/crates/sui-framework/packages/sui-framework/sources/types.move +++ b/crates/sui-framework/packages/sui-framework/sources/types.move @@ -2,10 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 /// Sui types helpers and utilities -module sui::types { - // === one-time witness === +module sui::types; - /// Tests if the argument type is a one-time witness, that is a type with only one instantiation - /// across the entire code base. - public native fun is_one_time_witness(_: &T): bool; -} +// === one-time witness === + +/// Tests if the argument type is a one-time witness, that is a type with only one instantiation +/// across the entire code base. +public native fun is_one_time_witness(_: &T): bool; diff --git a/crates/sui-framework/packages/sui-framework/sources/url.move b/crates/sui-framework/packages/sui-framework/sources/url.move index e4bfb272bf551..e2eac86c115b4 100644 --- a/crates/sui-framework/packages/sui-framework/sources/url.move +++ b/crates/sui-framework/packages/sui-framework/sources/url.move @@ -2,34 +2,34 @@ // SPDX-License-Identifier: Apache-2.0 /// URL: standard Uniform Resource Locator string -module sui::url { - use std::ascii::String; +module sui::url; - /// Standard Uniform Resource Locator (URL) string. - public struct Url has store, copy, drop { - // TODO: validate URL format - url: String, - } +use std::ascii::String; - /// Create a `Url`, with no validation - public fun new_unsafe(url: String): Url { - Url { url } - } +/// Standard Uniform Resource Locator (URL) string. +public struct Url has store, copy, drop { + // TODO: validate URL format + url: String, +} - /// Create a `Url` with no validation from bytes - /// Note: this will abort if `bytes` is not valid ASCII - public fun new_unsafe_from_bytes(bytes: vector): Url { - let url = bytes.to_ascii_string(); - Url { url } - } +/// Create a `Url`, with no validation +public fun new_unsafe(url: String): Url { + Url { url } +} - /// Get inner URL - public fun inner_url(self: &Url): String{ - self.url - } +/// Create a `Url` with no validation from bytes +/// Note: this will abort if `bytes` is not valid ASCII +public fun new_unsafe_from_bytes(bytes: vector): Url { + let url = bytes.to_ascii_string(); + Url { url } +} + +/// Get inner URL +public fun inner_url(self: &Url): String { + self.url +} - /// Update the inner URL - public fun update(self: &mut Url, url: String) { - self.url = url; - } +/// Update the inner URL +public fun update(self: &mut Url, url: String) { + self.url = url; } diff --git a/crates/sui-framework/packages/sui-framework/sources/vec_map.move b/crates/sui-framework/packages/sui-framework/sources/vec_map.move index ac3ccdb111909..d1fb7646b7e57 100644 --- a/crates/sui-framework/packages/sui-framework/sources/vec_map.move +++ b/crates/sui-framework/packages/sui-framework/sources/vec_map.move @@ -1,217 +1,213 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -module sui::vec_map { +module sui::vec_map; - /// This key already exists in the map - const EKeyAlreadyExists: u64 = 0; +/// This key already exists in the map +const EKeyAlreadyExists: u64 = 0; - /// This key does not exist in the map - const EKeyDoesNotExist: u64 = 1; +/// This key does not exist in the map +const EKeyDoesNotExist: u64 = 1; - /// Trying to destroy a map that is not empty - const EMapNotEmpty: u64 = 2; +/// Trying to destroy a map that is not empty +const EMapNotEmpty: u64 = 2; - /// Trying to access an element of the map at an invalid index - const EIndexOutOfBounds: u64 = 3; +/// Trying to access an element of the map at an invalid index +const EIndexOutOfBounds: u64 = 3; - /// Trying to pop from a map that is empty - const EMapEmpty: u64 = 4; +/// Trying to pop from a map that is empty +const EMapEmpty: u64 = 4; - /// Trying to construct a map from keys and values of different lengths - const EUnequalLengths: u64 = 5; +/// Trying to construct a map from keys and values of different lengths +const EUnequalLengths: u64 = 5; - /// A map data structure backed by a vector. The map is guaranteed not to contain duplicate keys, but entries - /// are *not* sorted by key--entries are included in insertion order. - /// All operations are O(N) in the size of the map--the intention of this data structure is only to provide - /// the convenience of programming against a map API. - /// Large maps should use handwritten parent/child relationships instead. - /// Maps that need sorted iteration rather than insertion order iteration should also be handwritten. - public struct VecMap has copy, drop, store { - contents: vector>, - } +/// A map data structure backed by a vector. The map is guaranteed not to contain duplicate keys, but entries +/// are *not* sorted by key--entries are included in insertion order. +/// All operations are O(N) in the size of the map--the intention of this data structure is only to provide +/// the convenience of programming against a map API. +/// Large maps should use handwritten parent/child relationships instead. +/// Maps that need sorted iteration rather than insertion order iteration should also be handwritten. +public struct VecMap has copy, drop, store { + contents: vector>, +} - /// An entry in the map - public struct Entry has copy, drop, store { - key: K, - value: V, - } +/// An entry in the map +public struct Entry has copy, drop, store { + key: K, + value: V, +} - /// Create an empty `VecMap` - public fun empty(): VecMap { - VecMap { contents: vector[] } - } +/// Create an empty `VecMap` +public fun empty(): VecMap { + VecMap { contents: vector[] } +} - /// Insert the entry `key` |-> `value` into `self`. - /// Aborts if `key` is already bound in `self`. - public fun insert(self: &mut VecMap, key: K, value: V) { - assert!(!self.contains(&key), EKeyAlreadyExists); - self.contents.push_back(Entry { key, value }) - } +/// Insert the entry `key` |-> `value` into `self`. +/// Aborts if `key` is already bound in `self`. +public fun insert(self: &mut VecMap, key: K, value: V) { + assert!(!self.contains(&key), EKeyAlreadyExists); + self.contents.push_back(Entry { key, value }) +} - /// Remove the entry `key` |-> `value` from self. Aborts if `key` is not bound in `self`. - public fun remove(self: &mut VecMap, key: &K): (K, V) { - let idx = self.get_idx(key); - let Entry { key, value } = self.contents.remove(idx); - (key, value) - } +/// Remove the entry `key` |-> `value` from self. Aborts if `key` is not bound in `self`. +public fun remove(self: &mut VecMap, key: &K): (K, V) { + let idx = self.get_idx(key); + let Entry { key, value } = self.contents.remove(idx); + (key, value) +} - /// Pop the most recently inserted entry from the map. Aborts if the map is empty. - public fun pop(self: &mut VecMap): (K, V) { - assert!(!self.contents.is_empty(), EMapEmpty); - let Entry { key, value } = self.contents.pop_back(); - (key, value) - } +/// Pop the most recently inserted entry from the map. Aborts if the map is empty. +public fun pop(self: &mut VecMap): (K, V) { + assert!(!self.contents.is_empty(), EMapEmpty); + let Entry { key, value } = self.contents.pop_back(); + (key, value) +} - #[syntax(index)] - /// Get a mutable reference to the value bound to `key` in `self`. - /// Aborts if `key` is not bound in `self`. - public fun get_mut(self: &mut VecMap, key: &K): &mut V { - let idx = self.get_idx(key); - let entry = &mut self.contents[idx]; - &mut entry.value - } +#[syntax(index)] +/// Get a mutable reference to the value bound to `key` in `self`. +/// Aborts if `key` is not bound in `self`. +public fun get_mut(self: &mut VecMap, key: &K): &mut V { + let idx = self.get_idx(key); + let entry = &mut self.contents[idx]; + &mut entry.value +} - #[syntax(index)] - /// Get a reference to the value bound to `key` in `self`. - /// Aborts if `key` is not bound in `self`. - public fun get(self: &VecMap, key: &K): &V { - let idx = self.get_idx(key); - let entry = &self.contents[idx]; - &entry.value - } +#[syntax(index)] +/// Get a reference to the value bound to `key` in `self`. +/// Aborts if `key` is not bound in `self`. +public fun get(self: &VecMap, key: &K): &V { + let idx = self.get_idx(key); + let entry = &self.contents[idx]; + &entry.value +} - /// Safely try borrow a value bound to `key` in `self`. - /// Return Some(V) if the value exists, None otherwise. - /// Only works for a "copyable" value as references cannot be stored in `vector`. - public fun try_get(self: &VecMap, key: &K): Option { - if (self.contains(key)) { - option::some(*get(self, key)) - } else { - option::none() - } +/// Safely try borrow a value bound to `key` in `self`. +/// Return Some(V) if the value exists, None otherwise. +/// Only works for a "copyable" value as references cannot be stored in `vector`. +public fun try_get(self: &VecMap, key: &K): Option { + if (self.contains(key)) { + option::some(*get(self, key)) + } else { + option::none() } +} - /// Return true if `self` contains an entry for `key`, false otherwise - public fun contains(self: &VecMap, key: &K): bool { - get_idx_opt(self, key).is_some() - } +/// Return true if `self` contains an entry for `key`, false otherwise +public fun contains(self: &VecMap, key: &K): bool { + get_idx_opt(self, key).is_some() +} - /// Return the number of entries in `self` - public fun size(self: &VecMap): u64 { - self.contents.length() - } +/// Return the number of entries in `self` +public fun size(self: &VecMap): u64 { + self.contents.length() +} - /// Return true if `self` has 0 elements, false otherwise - public fun is_empty(self: &VecMap): bool { - self.size() == 0 - } +/// Return true if `self` has 0 elements, false otherwise +public fun is_empty(self: &VecMap): bool { + self.size() == 0 +} - /// Destroy an empty map. Aborts if `self` is not empty - public fun destroy_empty(self: VecMap) { - let VecMap { contents } = self; - assert!(contents.is_empty(), EMapNotEmpty); - contents.destroy_empty() - } +/// Destroy an empty map. Aborts if `self` is not empty +public fun destroy_empty(self: VecMap) { + let VecMap { contents } = self; + assert!(contents.is_empty(), EMapNotEmpty); + contents.destroy_empty() +} - /// Unpack `self` into vectors of its keys and values. - /// The output keys and values are stored in insertion order, *not* sorted by key. - public fun into_keys_values(self: VecMap): (vector, vector) { - let VecMap { mut contents } = self; - // reverse the vector so the output keys and values will appear in insertion order - contents.reverse(); - let mut i = 0; - let n = contents.length(); - let mut keys = vector[]; - let mut values = vector[]; - while (i < n) { - let Entry { key, value } = contents.pop_back(); - keys.push_back(key); - values.push_back(value); - i = i + 1; - }; - contents.destroy_empty(); - (keys, values) - } +/// Unpack `self` into vectors of its keys and values. +/// The output keys and values are stored in insertion order, *not* sorted by key. +public fun into_keys_values(self: VecMap): (vector, vector) { + let VecMap { mut contents } = self; + // reverse the vector so the output keys and values will appear in insertion order + contents.reverse(); + let mut i = 0; + let n = contents.length(); + let mut keys = vector[]; + let mut values = vector[]; + while (i < n) { + let Entry { key, value } = contents.pop_back(); + keys.push_back(key); + values.push_back(value); + i = i + 1; + }; + contents.destroy_empty(); + (keys, values) +} - /// Construct a new `VecMap` from two vectors, one for keys and one for values. - /// The key value pairs are associated via their indices in the vectors, e.g. the key at index i - /// in `keys` is associated with the value at index i in `values`. - /// The key value pairs are stored in insertion order (the original vectors ordering) - /// and are *not* sorted. - public fun from_keys_values( - mut keys: vector, - mut values: vector, - ): VecMap { - assert!(keys.length() == values.length(), EUnequalLengths); - keys.reverse(); - values.reverse(); - let mut map = empty(); - while (!keys.is_empty()) map.insert(keys.pop_back(), values.pop_back()); - keys.destroy_empty(); - values.destroy_empty(); - map - } +/// Construct a new `VecMap` from two vectors, one for keys and one for values. +/// The key value pairs are associated via their indices in the vectors, e.g. the key at index i +/// in `keys` is associated with the value at index i in `values`. +/// The key value pairs are stored in insertion order (the original vectors ordering) +/// and are *not* sorted. +public fun from_keys_values(mut keys: vector, mut values: vector): VecMap { + assert!(keys.length() == values.length(), EUnequalLengths); + keys.reverse(); + values.reverse(); + let mut map = empty(); + while (!keys.is_empty()) map.insert(keys.pop_back(), values.pop_back()); + keys.destroy_empty(); + values.destroy_empty(); + map +} - /// Returns a list of keys in the map. - /// Do not assume any particular ordering. - public fun keys(self: &VecMap): vector { - let mut i = 0; - let n = self.contents.length(); - let mut keys = vector[]; - while (i < n) { - let entry = self.contents.borrow(i); - keys.push_back(entry.key); - i = i + 1; - }; - keys - } +/// Returns a list of keys in the map. +/// Do not assume any particular ordering. +public fun keys(self: &VecMap): vector { + let mut i = 0; + let n = self.contents.length(); + let mut keys = vector[]; + while (i < n) { + let entry = self.contents.borrow(i); + keys.push_back(entry.key); + i = i + 1; + }; + keys +} - /// Find the index of `key` in `self`. Return `None` if `key` is not in `self`. - /// Note that map entries are stored in insertion order, *not* sorted by key. - public fun get_idx_opt(self: &VecMap, key: &K): Option { - let mut i = 0; - let n = size(self); - while (i < n) { - if (&self.contents[i].key == key) { - return option::some(i) - }; - i = i + 1; +/// Find the index of `key` in `self`. Return `None` if `key` is not in `self`. +/// Note that map entries are stored in insertion order, *not* sorted by key. +public fun get_idx_opt(self: &VecMap, key: &K): Option { + let mut i = 0; + let n = size(self); + while (i < n) { + if (&self.contents[i].key == key) { + return option::some(i) }; - option::none() - } + i = i + 1; + }; + option::none() +} - /// Find the index of `key` in `self`. Aborts if `key` is not in `self`. - /// Note that map entries are stored in insertion order, *not* sorted by key. - public fun get_idx(self: &VecMap, key: &K): u64 { - let idx_opt = self.get_idx_opt(key); - assert!(idx_opt.is_some(), EKeyDoesNotExist); - idx_opt.destroy_some() - } +/// Find the index of `key` in `self`. Aborts if `key` is not in `self`. +/// Note that map entries are stored in insertion order, *not* sorted by key. +public fun get_idx(self: &VecMap, key: &K): u64 { + let idx_opt = self.get_idx_opt(key); + assert!(idx_opt.is_some(), EKeyDoesNotExist); + idx_opt.destroy_some() +} - /// Return a reference to the `idx`th entry of `self`. This gives direct access into the backing array of the map--use with caution. - /// Note that map entries are stored in insertion order, *not* sorted by key. - /// Aborts if `idx` is greater than or equal to `size(self)` - public fun get_entry_by_idx(self: &VecMap, idx: u64): (&K, &V) { - assert!(idx < size(self), EIndexOutOfBounds); - let entry = &self.contents[idx]; - (&entry.key, &entry.value) - } +/// Return a reference to the `idx`th entry of `self`. This gives direct access into the backing array of the map--use with caution. +/// Note that map entries are stored in insertion order, *not* sorted by key. +/// Aborts if `idx` is greater than or equal to `size(self)` +public fun get_entry_by_idx(self: &VecMap, idx: u64): (&K, &V) { + assert!(idx < size(self), EIndexOutOfBounds); + let entry = &self.contents[idx]; + (&entry.key, &entry.value) +} - /// Return a mutable reference to the `idx`th entry of `self`. This gives direct access into the backing array of the map--use with caution. - /// Note that map entries are stored in insertion order, *not* sorted by key. - /// Aborts if `idx` is greater than or equal to `size(self)` - public fun get_entry_by_idx_mut(self: &mut VecMap, idx: u64): (&K, &mut V) { - assert!(idx < size(self), EIndexOutOfBounds); - let entry = &mut self.contents[idx]; - (&entry.key, &mut entry.value) - } +/// Return a mutable reference to the `idx`th entry of `self`. This gives direct access into the backing array of the map--use with caution. +/// Note that map entries are stored in insertion order, *not* sorted by key. +/// Aborts if `idx` is greater than or equal to `size(self)` +public fun get_entry_by_idx_mut(self: &mut VecMap, idx: u64): (&K, &mut V) { + assert!(idx < size(self), EIndexOutOfBounds); + let entry = &mut self.contents[idx]; + (&entry.key, &mut entry.value) +} - /// Remove the entry at index `idx` from self. - /// Aborts if `idx` is greater than or equal to `size(self)` - public fun remove_entry_by_idx(self: &mut VecMap, idx: u64): (K, V) { - assert!(idx < size(self), EIndexOutOfBounds); - let Entry { key, value } = self.contents.remove(idx); - (key, value) - } +/// Remove the entry at index `idx` from self. +/// Aborts if `idx` is greater than or equal to `size(self)` +public fun remove_entry_by_idx(self: &mut VecMap, idx: u64): (K, V) { + assert!(idx < size(self), EIndexOutOfBounds); + let Entry { key, value } = self.contents.remove(idx); + (key, value) } diff --git a/crates/sui-framework/packages/sui-framework/sources/vec_set.move b/crates/sui-framework/packages/sui-framework/sources/vec_set.move index c5d6e26a249a8..e4d9301def975 100644 --- a/crates/sui-framework/packages/sui-framework/sources/vec_set.move +++ b/crates/sui-framework/packages/sui-framework/sources/vec_set.move @@ -1,106 +1,105 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -module sui::vec_set { - - /// This key already exists in the map - const EKeyAlreadyExists: u64 = 0; - - /// This key does not exist in the map - const EKeyDoesNotExist: u64 = 1; - - /// A set data structure backed by a vector. The set is guaranteed not to - /// contain duplicate keys. All operations are O(N) in the size of the set - /// - the intention of this data structure is only to provide the convenience - /// of programming against a set API. Sets that need sorted iteration rather - /// than insertion order iteration should be handwritten. - public struct VecSet has copy, drop, store { - contents: vector, - } - - /// Create an empty `VecSet` - public fun empty(): VecSet { - VecSet { contents: vector[] } - } - - /// Create a singleton `VecSet` that only contains one element. - public fun singleton(key: K): VecSet { - VecSet { contents: vector[key] } - } - - /// Insert a `key` into self. - /// Aborts if `key` is already present in `self`. - public fun insert(self: &mut VecSet, key: K) { - assert!(!self.contains(&key), EKeyAlreadyExists); - self.contents.push_back(key) - } - - /// Remove the entry `key` from self. Aborts if `key` is not present in `self`. - public fun remove(self: &mut VecSet, key: &K) { - let idx = get_idx(self, key); - self.contents.remove(idx); - } - - /// Return true if `self` contains an entry for `key`, false otherwise - public fun contains(self: &VecSet, key: &K): bool { - get_idx_opt(self, key).is_some() - } - - /// Return the number of entries in `self` - public fun size(self: &VecSet): u64 { - self.contents.length() - } - - /// Return true if `self` has 0 elements, false otherwise - public fun is_empty(self: &VecSet): bool { - size(self) == 0 - } - - /// Unpack `self` into vectors of keys. - /// The output keys are stored in insertion order, *not* sorted. - public fun into_keys(self: VecSet): vector { - let VecSet { contents } = self; - contents - } - - /// Construct a new `VecSet` from a vector of keys. - /// The keys are stored in insertion order (the original `keys` ordering) - /// and are *not* sorted. - public fun from_keys(mut keys: vector): VecSet { - keys.reverse(); - let mut set = empty(); - while (!keys.is_empty()) set.insert(keys.pop_back()); - set - } - - /// Borrow the `contents` of the `VecSet` to access content by index - /// without unpacking. The contents are stored in insertion order, - /// *not* sorted. - public fun keys(self: &VecSet): &vector { - &self.contents - } - - // == Helper functions == - - /// Find the index of `key` in `self`. Return `None` if `key` is not in `self`. - /// Note that keys are stored in insertion order, *not* sorted. - fun get_idx_opt(self: &VecSet, key: &K): Option { - let mut i = 0; - let n = size(self); - while (i < n) { - if (&self.contents[i] == key) { - return option::some(i) - }; - i = i + 1; +module sui::vec_set; + +/// This key already exists in the map +const EKeyAlreadyExists: u64 = 0; + +/// This key does not exist in the map +const EKeyDoesNotExist: u64 = 1; + +/// A set data structure backed by a vector. The set is guaranteed not to +/// contain duplicate keys. All operations are O(N) in the size of the set +/// - the intention of this data structure is only to provide the convenience +/// of programming against a set API. Sets that need sorted iteration rather +/// than insertion order iteration should be handwritten. +public struct VecSet has copy, drop, store { + contents: vector, +} + +/// Create an empty `VecSet` +public fun empty(): VecSet { + VecSet { contents: vector[] } +} + +/// Create a singleton `VecSet` that only contains one element. +public fun singleton(key: K): VecSet { + VecSet { contents: vector[key] } +} + +/// Insert a `key` into self. +/// Aborts if `key` is already present in `self`. +public fun insert(self: &mut VecSet, key: K) { + assert!(!self.contains(&key), EKeyAlreadyExists); + self.contents.push_back(key) +} + +/// Remove the entry `key` from self. Aborts if `key` is not present in `self`. +public fun remove(self: &mut VecSet, key: &K) { + let idx = get_idx(self, key); + self.contents.remove(idx); +} + +/// Return true if `self` contains an entry for `key`, false otherwise +public fun contains(self: &VecSet, key: &K): bool { + get_idx_opt(self, key).is_some() +} + +/// Return the number of entries in `self` +public fun size(self: &VecSet): u64 { + self.contents.length() +} + +/// Return true if `self` has 0 elements, false otherwise +public fun is_empty(self: &VecSet): bool { + size(self) == 0 +} + +/// Unpack `self` into vectors of keys. +/// The output keys are stored in insertion order, *not* sorted. +public fun into_keys(self: VecSet): vector { + let VecSet { contents } = self; + contents +} + +/// Construct a new `VecSet` from a vector of keys. +/// The keys are stored in insertion order (the original `keys` ordering) +/// and are *not* sorted. +public fun from_keys(mut keys: vector): VecSet { + keys.reverse(); + let mut set = empty(); + while (!keys.is_empty()) set.insert(keys.pop_back()); + set +} + +/// Borrow the `contents` of the `VecSet` to access content by index +/// without unpacking. The contents are stored in insertion order, +/// *not* sorted. +public fun keys(self: &VecSet): &vector { + &self.contents +} + +// == Helper functions == + +/// Find the index of `key` in `self`. Return `None` if `key` is not in `self`. +/// Note that keys are stored in insertion order, *not* sorted. +fun get_idx_opt(self: &VecSet, key: &K): Option { + let mut i = 0; + let n = size(self); + while (i < n) { + if (&self.contents[i] == key) { + return option::some(i) }; - option::none() - } - - /// Find the index of `key` in `self`. Aborts if `key` is not in `self`. - /// Note that map entries are stored in insertion order, *not* sorted. - fun get_idx(self: &VecSet, key: &K): u64 { - let idx_opt = get_idx_opt(self, key); - assert!(idx_opt.is_some(), EKeyDoesNotExist); - idx_opt.destroy_some() - } + i = i + 1; + }; + option::none() +} + +/// Find the index of `key` in `self`. Aborts if `key` is not in `self`. +/// Note that map entries are stored in insertion order, *not* sorted. +fun get_idx(self: &VecSet, key: &K): u64 { + let idx_opt = get_idx_opt(self, key); + assert!(idx_opt.is_some(), EKeyDoesNotExist); + idx_opt.destroy_some() } diff --git a/crates/sui-framework/packages/sui-framework/sources/versioned.move b/crates/sui-framework/packages/sui-framework/sources/versioned.move index ae916fcdef8b0..3746bbbd56286 100644 --- a/crates/sui-framework/packages/sui-framework/sources/versioned.move +++ b/crates/sui-framework/packages/sui-framework/sources/versioned.move @@ -1,83 +1,88 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -module sui::versioned { - use sui::dynamic_field; +module sui::versioned; - /// Failed to upgrade the inner object due to invalid capability or new version. - const EInvalidUpgrade: u64 = 0; +use sui::dynamic_field; - /// A wrapper type that supports versioning of the inner type. - /// The inner type is a dynamic field of the Versioned object, and is keyed using version. - /// User of this type could load the inner object using corresponding type based on the version. - /// You can also upgrade the inner object to a new type version. - /// If you want to support lazy upgrade of the inner type, one caveat is that all APIs would have - /// to use mutable reference even if it's a read-only API. - public struct Versioned has key, store { - id: UID, - version: u64, - } +/// Failed to upgrade the inner object due to invalid capability or new version. +const EInvalidUpgrade: u64 = 0; - /// Represents a hot potato object generated when we take out the dynamic field. - /// This is to make sure that we always put a new value back. - public struct VersionChangeCap { - versioned_id: ID, - old_version: u64, - } +/// A wrapper type that supports versioning of the inner type. +/// The inner type is a dynamic field of the Versioned object, and is keyed using version. +/// User of this type could load the inner object using corresponding type based on the version. +/// You can also upgrade the inner object to a new type version. +/// If you want to support lazy upgrade of the inner type, one caveat is that all APIs would have +/// to use mutable reference even if it's a read-only API. +public struct Versioned has key, store { + id: UID, + version: u64, +} - /// Create a new Versioned object that contains a initial value of type `T` with an initial version. - public fun create(init_version: u64, init_value: T, ctx: &mut TxContext): Versioned { - let mut self = Versioned { - id: object::new(ctx), - version: init_version, - }; - dynamic_field::add(&mut self.id, init_version, init_value); - self - } +/// Represents a hot potato object generated when we take out the dynamic field. +/// This is to make sure that we always put a new value back. +public struct VersionChangeCap { + versioned_id: ID, + old_version: u64, +} - /// Get the current version of the inner type. - public fun version(self: &Versioned): u64 { - self.version - } +/// Create a new Versioned object that contains a initial value of type `T` with an initial version. +public fun create(init_version: u64, init_value: T, ctx: &mut TxContext): Versioned { + let mut self = Versioned { + id: object::new(ctx), + version: init_version, + }; + dynamic_field::add(&mut self.id, init_version, init_value); + self +} - /// Load the inner value based on the current version. Caller specifies an expected type T. - /// If the type mismatch, the load will fail. - public fun load_value(self: &Versioned): &T { - dynamic_field::borrow(&self.id, self.version) - } +/// Get the current version of the inner type. +public fun version(self: &Versioned): u64 { + self.version +} - /// Similar to load_value, but return a mutable reference. - public fun load_value_mut(self: &mut Versioned): &mut T { - dynamic_field::borrow_mut(&mut self.id, self.version) - } +/// Load the inner value based on the current version. Caller specifies an expected type T. +/// If the type mismatch, the load will fail. +public fun load_value(self: &Versioned): &T { + dynamic_field::borrow(&self.id, self.version) +} - /// Take the inner object out for upgrade. To ensure we always upgrade properly, a capability object is returned - /// and must be used when we upgrade. - public fun remove_value_for_upgrade(self: &mut Versioned): (T, VersionChangeCap) { - ( - dynamic_field::remove(&mut self.id, self.version), - VersionChangeCap { - versioned_id: object::id(self), - old_version: self.version, - } - ) - } +/// Similar to load_value, but return a mutable reference. +public fun load_value_mut(self: &mut Versioned): &mut T { + dynamic_field::borrow_mut(&mut self.id, self.version) +} + +/// Take the inner object out for upgrade. To ensure we always upgrade properly, a capability object is returned +/// and must be used when we upgrade. +public fun remove_value_for_upgrade(self: &mut Versioned): (T, VersionChangeCap) { + ( + dynamic_field::remove(&mut self.id, self.version), + VersionChangeCap { + versioned_id: object::id(self), + old_version: self.version, + }, + ) +} - /// Upgrade the inner object with a new version and new value. Must use the capability returned - /// by calling remove_value_for_upgrade. - public fun upgrade(self: &mut Versioned, new_version: u64, new_value: T, cap: VersionChangeCap) { - let VersionChangeCap { versioned_id, old_version } = cap; - assert!(versioned_id == object::id(self), EInvalidUpgrade); - assert!(old_version < new_version, EInvalidUpgrade); - dynamic_field::add(&mut self.id, new_version, new_value); - self.version = new_version; - } +/// Upgrade the inner object with a new version and new value. Must use the capability returned +/// by calling remove_value_for_upgrade. +public fun upgrade( + self: &mut Versioned, + new_version: u64, + new_value: T, + cap: VersionChangeCap, +) { + let VersionChangeCap { versioned_id, old_version } = cap; + assert!(versioned_id == object::id(self), EInvalidUpgrade); + assert!(old_version < new_version, EInvalidUpgrade); + dynamic_field::add(&mut self.id, new_version, new_value); + self.version = new_version; +} - /// Destroy this Versioned container, and return the inner object. - public fun destroy(self: Versioned): T { - let Versioned { mut id, version } = self; - let ret = dynamic_field::remove(&mut id, version); - id.delete(); - ret - } +/// Destroy this Versioned container, and return the inner object. +public fun destroy(self: Versioned): T { + let Versioned { mut id, version } = self; + let ret = dynamic_field::remove(&mut id, version); + id.delete(); + ret } diff --git a/crates/sui-framework/packages/sui-framework/tests/balance_tests.move b/crates/sui-framework/packages/sui-framework/tests/balance_tests.move index ca2f11a5cbd79..bb2ce02ed4692 100644 --- a/crates/sui-framework/packages/sui-framework/tests/balance_tests.move +++ b/crates/sui-framework/packages/sui-framework/tests/balance_tests.move @@ -8,6 +8,8 @@ module sui::coin_balance_tests { use sui::coin; use sui::balance; use sui::sui::SUI; + use sui::test_utils; + #[test] fun type_morphing() { @@ -35,4 +37,28 @@ module sui::coin_balance_tests { pay::keep(coin, scenario.ctx()); scenario.end(); } + + #[test] + fun test_balance() { + let mut balance = balance::zero(); + let another = balance::create_for_testing(1000); + + balance.join(another); + + assert!(balance.value() == 1000); + + let balance1 = balance.split(333); + let balance2 = balance.split(333); + let balance3 = balance.split(334); + + balance.destroy_zero(); + + assert!(balance1.value() == 333); + assert!(balance2.value() == 333); + assert!(balance3.value() == 334); + + test_utils::destroy(balance1); + test_utils::destroy(balance2); + test_utils::destroy(balance3); + } } diff --git a/crates/sui-framework/packages/sui-framework/tests/display_tests.move b/crates/sui-framework/packages/sui-framework/tests/display_tests.move new file mode 100644 index 0000000000000..a704ce67e2c42 --- /dev/null +++ b/crates/sui-framework/packages/sui-framework/tests/display_tests.move @@ -0,0 +1,39 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module sui::display_tests { + use sui::test_scenario as test; + use std::string::String; + use sui::package; + use sui::display; + + #[allow(unused_field)] + /// An example object. + /// Purely for visibility. + public struct Capy has key { + id: UID, + name: String + } + + /// Test witness type to create a Publisher object. + public struct CAPY has drop {} + + #[test] + fun capy_init() { + let mut test = test::begin(@0x2); + let pub = package::test_claim(CAPY {}, test.ctx()); + + // create a new display object + let mut display = display::new(&pub, test.ctx()); + + display.add(b"name".to_string(), b"Capy {name}".to_string()); + display.add(b"link".to_string(), b"https://capy.art/capy/{id}".to_string()); + display.add(b"image".to_string(), b"https://api.capy.art/capy/{id}/svg".to_string()); + display.add(b"description".to_string(), b"A Lovely Capy".to_string()); + + pub.burn_publisher(); + transfer::public_transfer(display, @0x2); + test.end(); + } +} diff --git a/crates/sui-framework/packages/sui-framework/tests/hex_tests.move b/crates/sui-framework/packages/sui-framework/tests/hex_tests.move new file mode 100644 index 0000000000000..990bdb46d8b83 --- /dev/null +++ b/crates/sui-framework/packages/sui-framework/tests/hex_tests.move @@ -0,0 +1,62 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module sui::hex_tests { + use sui::hex; + + #[test] + fun test_hex_encode_string_literal() { + assert!(b"30" == hex::encode(b"0")); + assert!(b"61" == hex::encode(b"a")); + assert!(b"666666" == hex::encode(b"fff")); + } + + #[test] + fun test_hex_encode_hex_literal() { + assert!(b"ff" == hex::encode(x"ff")); + assert!(b"fe" == hex::encode(x"fe")); + assert!(b"00" == hex::encode(x"00")); + } + + #[test] + fun test_hex_decode_string_literal() { + assert!(x"ff" == hex::decode(b"ff")); + assert!(x"fe" == hex::decode(b"fe")); + assert!(x"00" == hex::decode(b"00")); + } + + #[test] + fun test_hex_decode_string_literal__lowercase_and_uppercase() { + assert!(x"ff" == hex::decode(b"Ff")); + assert!(x"ff" == hex::decode(b"fF")); + assert!(x"ff" == hex::decode(b"FF")); + } + + #[test] + fun test_hex_decode_string_literal__long_hex() { + assert!( + x"036d2416252ae1db8aedad59e14b007bee6ab94a3e77a3549a81137871604456f3" == hex::decode( + b"036d2416252ae1Db8aedAd59e14b007bee6aB94a3e77a3549a81137871604456f3" + ), + ); + } + + #[test] + #[expected_failure(abort_code = hex::EInvalidHexLength)] + fun test_hex_decode__invalid_length() { + hex::decode(b"0"); + } + + #[test] + #[expected_failure(abort_code = hex::ENotValidHexCharacter)] + fun test_hex_decode__hex_literal() { + hex::decode(x"ffff"); + } + + #[test] + #[expected_failure(abort_code = hex::ENotValidHexCharacter)] + fun test_hex_decode__invalid_string_literal() { + hex::decode(b"0g"); + } +} diff --git a/crates/sui-framework/packages/sui-system/sources/staking_pool.move b/crates/sui-framework/packages/sui-system/sources/staking_pool.move index 0295dbfa703a1..ba10693c3dccf 100644 --- a/crates/sui-framework/packages/sui-system/sources/staking_pool.move +++ b/crates/sui-framework/packages/sui-system/sources/staking_pool.move @@ -31,6 +31,8 @@ module sui_system::staking_pool { const EActivationOfInactivePool: u64 = 16; const EDelegationOfZeroSui: u64 = 17; const EStakedSuiBelowThreshold: u64 = 18; + const ECannotMintFungibleStakedSuiYet: u64 = 19; + const EInvariantFailure: u64 = 20; /// A staking pool embedded in each validator struct in the system state object. public struct StakingPool has key, store { @@ -80,6 +82,30 @@ module sui_system::staking_pool { principal: Balance, } + /// An alternative to `StakedSui` that holds the pool token amount instead of the SUI balance. + /// StakedSui objects can be converted to FungibleStakedSuis after the initial warmup period. + /// The advantage of this is that you can now merge multiple StakedSui objects from different + /// activation epochs into a single FungibleStakedSui object. + public struct FungibleStakedSui has key, store { + id: UID, + /// ID of the staking pool we are staking with. + pool_id: ID, + /// The pool token amount. + value: u64, + } + + /// Holds useful information + public struct FungibleStakedSuiData has key, store { + id: UID, + /// fungible_staked_sui supply + total_supply: u64, + /// principal balance. Rewards are withdrawn from the reward pool + principal: Balance, + } + + // === dynamic field keys === + public struct FungibleStakedSuiDataKey has copy, store, drop {} + // ==== initializer ==== /// Create a new, empty staking pool. @@ -158,6 +184,133 @@ module sui_system::staking_pool { principal_withdraw } + public(package) fun redeem_fungible_staked_sui( + pool: &mut StakingPool, + fungible_staked_sui: FungibleStakedSui, + ctx: &TxContext + ) : Balance { + let FungibleStakedSui { id, pool_id, value } = fungible_staked_sui; + assert!(pool_id == object::id(pool), EWrongPool); + + object::delete(id); + + let latest_exchange_rate = pool_token_exchange_rate_at_epoch(pool, tx_context::epoch(ctx)); + let fungible_staked_sui_data: &mut FungibleStakedSuiData = bag::borrow_mut( + &mut pool.extra_fields, + FungibleStakedSuiDataKey {} + ); + + let (principal_amount, rewards_amount) = calculate_fungible_staked_sui_withdraw_amount( + latest_exchange_rate, + value, + balance::value(&fungible_staked_sui_data.principal), + fungible_staked_sui_data.total_supply + ); + + fungible_staked_sui_data.total_supply = fungible_staked_sui_data.total_supply - value; + + let mut sui_out = balance::split(&mut fungible_staked_sui_data.principal, principal_amount); + balance::join( + &mut sui_out, + balance::split(&mut pool.rewards_pool, rewards_amount) + ); + + pool.pending_total_sui_withdraw = pool.pending_total_sui_withdraw + balance::value(&sui_out); + pool.pending_pool_token_withdraw = pool.pending_pool_token_withdraw + value; + + sui_out + } + + /// written in separate function so i can test with random values + /// returns (principal_withdraw_amount, rewards_withdraw_amount) + fun calculate_fungible_staked_sui_withdraw_amount( + latest_exchange_rate: PoolTokenExchangeRate, + fungible_staked_sui_value: u64, + fungible_staked_sui_data_principal_amount: u64, // fungible_staked_sui_data.principal.value() + fungible_staked_sui_data_total_supply: u64, // fungible_staked_sui_data.total_supply + ) : (u64, u64) { + // 1. if the entire FungibleStakedSuiData supply is redeemed, how much sui should we receive? + let total_sui_amount = get_sui_amount(&latest_exchange_rate, fungible_staked_sui_data_total_supply); + + // min with total_sui_amount to prevent underflow + let fungible_staked_sui_data_principal_amount = std::u64::min( + fungible_staked_sui_data_principal_amount, + total_sui_amount + ); + + // 2. how much do we need to withdraw from the rewards pool? + let total_rewards = total_sui_amount - fungible_staked_sui_data_principal_amount; + + // 3. proportionally withdraw from both wrt the fungible_staked_sui_value. + let principal_withdraw_amount = ((fungible_staked_sui_value as u128) + * (fungible_staked_sui_data_principal_amount as u128) + / (fungible_staked_sui_data_total_supply as u128)) as u64; + + let rewards_withdraw_amount = ((fungible_staked_sui_value as u128) + * (total_rewards as u128) + / (fungible_staked_sui_data_total_supply as u128)) as u64; + + // invariant check, just in case + let expected_sui_amount = get_sui_amount(&latest_exchange_rate, fungible_staked_sui_value); + assert!(principal_withdraw_amount + rewards_withdraw_amount <= expected_sui_amount, EInvariantFailure); + + (principal_withdraw_amount, rewards_withdraw_amount) + } + + /// Convert the given staked SUI to an FungibleStakedSui object + public(package) fun convert_to_fungible_staked_sui( + pool: &mut StakingPool, + staked_sui: StakedSui, + ctx: &mut TxContext + ) : FungibleStakedSui { + let StakedSui { id, pool_id, stake_activation_epoch, principal } = staked_sui; + + assert!(pool_id == object::id(pool), EWrongPool); + assert!( + tx_context::epoch(ctx) >= stake_activation_epoch, + ECannotMintFungibleStakedSuiYet + ); + + object::delete(id); + + + let exchange_rate_at_staking_epoch = pool_token_exchange_rate_at_epoch( + pool, + stake_activation_epoch + ); + + let pool_token_amount = get_token_amount( + &exchange_rate_at_staking_epoch, + balance::value(&principal) + ); + + if (!bag::contains(&pool.extra_fields, FungibleStakedSuiDataKey {})) { + bag::add( + &mut pool.extra_fields, + FungibleStakedSuiDataKey {}, + FungibleStakedSuiData { + id: object::new(ctx), + total_supply: pool_token_amount, + principal + } + ); + } + else { + let fungible_staked_sui_data: &mut FungibleStakedSuiData = bag::borrow_mut( + &mut pool.extra_fields, + FungibleStakedSuiDataKey {} + ); + fungible_staked_sui_data.total_supply = fungible_staked_sui_data.total_supply + pool_token_amount; + balance::join(&mut fungible_staked_sui_data.principal, principal); + }; + + FungibleStakedSui { + id: object::new(ctx), + pool_id, + value: pool_token_amount, + } + } + /// Withdraw the principal SUI stored in the StakedSui object, and calculate the corresponding amount of pool /// tokens using exchange rate at staking epoch. /// Returns values are amount of pool tokens withdrawn and withdrawn principal portion of SUI. @@ -293,6 +446,9 @@ module sui_system::staking_pool { public fun pool_id(staked_sui: &StakedSui): ID { staked_sui.pool_id } + public use fun fungible_staked_sui_pool_id as FungibleStakedSui.pool_id; + public fun fungible_staked_sui_pool_id(fungible_staked_sui: &FungibleStakedSui): ID { fungible_staked_sui.pool_id } + public fun staked_sui_amount(staked_sui: &StakedSui): u64 { staked_sui.principal.value() } /// Allows calling `.amount()` on `StakedSui` to invoke `staked_sui_amount` @@ -312,6 +468,36 @@ module sui_system::staking_pool { pool.deactivation_epoch.is_some() } + public use fun fungible_staked_sui_value as FungibleStakedSui.value; + public fun fungible_staked_sui_value(fungible_staked_sui: &FungibleStakedSui): u64 { fungible_staked_sui.value } + + public use fun split_fungible_staked_sui as FungibleStakedSui.split; + public fun split_fungible_staked_sui( + fungible_staked_sui: &mut FungibleStakedSui, + split_amount: u64, + ctx: &mut TxContext + ): FungibleStakedSui { + assert!(split_amount <= fungible_staked_sui.value, EInsufficientPoolTokenBalance); + + fungible_staked_sui.value = fungible_staked_sui.value - split_amount; + + FungibleStakedSui { + id: object::new(ctx), + pool_id: fungible_staked_sui.pool_id, + value: split_amount, + } + } + + public use fun join_fungible_staked_sui as FungibleStakedSui.join; + public fun join_fungible_staked_sui(self: &mut FungibleStakedSui, other: FungibleStakedSui) { + let FungibleStakedSui { id, pool_id, value } = other; + assert!(self.pool_id == pool_id, EWrongPool); + + object::delete(id); + + self.value = self.value + value; + } + /// Split StakedSui `self` to two parts, one with principal `split_amount`, /// and the remaining principal is left in `self`. /// All the other parameters of the StakedSui like `stake_activation_epoch` or `pool_id` remain the same. @@ -473,4 +659,104 @@ module sui_system::staking_pool { staked_amount + reward_withdraw_amount } + + #[test_only] + public(package) fun fungible_staked_sui_data(pool: &StakingPool): &FungibleStakedSuiData { + bag::borrow(&pool.extra_fields, FungibleStakedSuiDataKey {}) + } + + #[test_only] + public use fun fungible_staked_sui_data_total_supply as FungibleStakedSuiData.total_supply; + + #[test_only] + public(package) fun fungible_staked_sui_data_total_supply(fungible_staked_sui_data: &FungibleStakedSuiData): u64 { + fungible_staked_sui_data.total_supply + } + + #[test_only] + public use fun fungible_staked_sui_data_principal_value as FungibleStakedSuiData.principal_value; + + #[test_only] + public(package) fun fungible_staked_sui_data_principal_value(fungible_staked_sui_data: &FungibleStakedSuiData): u64 { + fungible_staked_sui_data.principal.value() + } + + #[test_only] + public(package) fun pending_pool_token_withdraw_amount(pool: &StakingPool): u64 { + pool.pending_pool_token_withdraw + } + + #[test_only] + public(package) fun create_fungible_staked_sui_for_testing( + self: &StakingPool, + value: u64, + ctx: &mut TxContext + ) : FungibleStakedSui { + FungibleStakedSui { + id: object::new(ctx), + pool_id: object::id(self), + value, + } + } + + // ==== tests ==== + + #[random_test] + fun test_calculate_fungible_staked_sui_withdraw_amount( + mut total_sui_amount: u64, + // these are all in basis points + mut pool_token_frac: u16, + mut fungible_staked_sui_data_total_supply_frac: u16, + mut fungible_staked_sui_data_principal_frac: u16, + mut fungible_staked_sui_value_bps: u16 + ) { + use std::u128::max; + + total_sui_amount = std::u64::max(total_sui_amount, 1); + + pool_token_frac = pool_token_frac % 10000; + fungible_staked_sui_data_total_supply_frac = fungible_staked_sui_data_total_supply_frac % 10000; + fungible_staked_sui_data_principal_frac = fungible_staked_sui_data_principal_frac % 10000; + fungible_staked_sui_value_bps = fungible_staked_sui_value_bps % 10000; + + + let total_pool_token_amount = max( + (total_sui_amount as u128) * (pool_token_frac as u128) / 10000, + 1 + ); + + let exchange_rate = PoolTokenExchangeRate { + sui_amount: total_sui_amount, + pool_token_amount: total_pool_token_amount as u64, + }; + + let fungible_staked_sui_data_total_supply = max( + total_pool_token_amount * (fungible_staked_sui_data_total_supply_frac as u128) / 10000, + 1 + ); + let fungible_staked_sui_value = fungible_staked_sui_data_total_supply + * (fungible_staked_sui_value_bps as u128) / 10000; + + let max_principal = get_sui_amount(&exchange_rate, fungible_staked_sui_data_total_supply as u64); + let fungible_staked_sui_data_principal_amount = max( + (max_principal as u128) * (fungible_staked_sui_data_principal_frac as u128) / 10000, + 1 + ); + + let (principal_amount, rewards_amount) = calculate_fungible_staked_sui_withdraw_amount( + exchange_rate, + fungible_staked_sui_value as u64, + fungible_staked_sui_data_principal_amount as u64, + fungible_staked_sui_data_total_supply as u64, + ); + + let expected_out = get_sui_amount(&exchange_rate, fungible_staked_sui_value as u64); + + assert!(principal_amount + rewards_amount <= expected_out, 0); + + let min_out = if (expected_out > 2) expected_out - 2 else 0; + assert!(principal_amount + rewards_amount >= min_out, 0); + } + + } diff --git a/crates/sui-framework/packages/sui-system/sources/sui_system.move b/crates/sui-framework/packages/sui-system/sources/sui_system.move index fe46fff592e6a..916c2cd55b33b 100644 --- a/crates/sui-framework/packages/sui-system/sources/sui_system.move +++ b/crates/sui-framework/packages/sui-system/sources/sui_system.move @@ -42,7 +42,7 @@ module sui_system::sui_system { use sui::balance::Balance; use sui::coin::Coin; - use sui_system::staking_pool::StakedSui; + use sui_system::staking_pool::{StakedSui, FungibleStakedSui}; use sui::sui::SUI; use sui::table::Table; use sui_system::validator::Validator; @@ -265,6 +265,26 @@ module sui_system::sui_system { transfer::public_transfer(withdrawn_stake.into_coin(ctx), ctx.sender()); } + /// Convert StakedSui into a FungibleStakedSui object. + public fun convert_to_fungible_staked_sui( + wrapper: &mut SuiSystemState, + staked_sui: StakedSui, + ctx: &mut TxContext, + ): FungibleStakedSui { + let self = load_system_state_mut(wrapper); + self.convert_to_fungible_staked_sui(staked_sui, ctx) + } + + /// Convert FungibleStakedSui into a StakedSui object. + public fun redeem_fungible_staked_sui( + wrapper: &mut SuiSystemState, + fungible_staked_sui: FungibleStakedSui, + ctx: &TxContext, + ): Balance { + let self = load_system_state_mut(wrapper); + self.redeem_fungible_staked_sui(fungible_staked_sui, ctx) + } + /// Non-entry version of `request_withdraw_stake` that returns the withdrawn SUI instead of transferring it to the sender. public fun request_withdraw_stake_non_entry( wrapper: &mut SuiSystemState, diff --git a/crates/sui-framework/packages/sui-system/sources/sui_system_state_inner.move b/crates/sui-framework/packages/sui-system/sources/sui_system_state_inner.move index ae23b97b4fd40..121a12fc75b94 100644 --- a/crates/sui-framework/packages/sui-system/sources/sui_system_state_inner.move +++ b/crates/sui-framework/packages/sui-system/sources/sui_system_state_inner.move @@ -4,7 +4,7 @@ module sui_system::sui_system_state_inner { use sui::balance::{Self, Balance}; use sui::coin::Coin; - use sui_system::staking_pool::StakedSui; + use sui_system::staking_pool::{StakedSui, FungibleStakedSui}; use sui::sui::SUI; use sui_system::validator::{Self, Validator}; use sui_system::validator_set::{Self, ValidatorSet}; @@ -518,6 +518,22 @@ module sui_system::sui_system_state_inner { self.validators.request_withdraw_stake(staked_sui, ctx) } + public(package) fun convert_to_fungible_staked_sui( + self: &mut SuiSystemStateInnerV2, + staked_sui: StakedSui, + ctx: &mut TxContext, + ) : FungibleStakedSui { + self.validators.convert_to_fungible_staked_sui(staked_sui, ctx) + } + + public(package) fun redeem_fungible_staked_sui( + self: &mut SuiSystemStateInnerV2, + fungible_staked_sui: FungibleStakedSui, + ctx: &TxContext, + ) : Balance { + self.validators.redeem_fungible_staked_sui(fungible_staked_sui, ctx) + } + /// Report a validator as a bad or non-performant actor in the system. /// Succeeds if all the following are satisfied: /// 1. both the reporter in `cap` and the input `reportee_addr` are active validators. diff --git a/crates/sui-framework/packages/sui-system/sources/validator.move b/crates/sui-framework/packages/sui-system/sources/validator.move index 5c0a453ea779f..da6157014541e 100644 --- a/crates/sui-framework/packages/sui-system/sources/validator.move +++ b/crates/sui-framework/packages/sui-system/sources/validator.move @@ -8,7 +8,7 @@ module sui_system::validator { use sui::balance::Balance; use sui::sui::SUI; use sui_system::validator_cap::{Self, ValidatorOperationCap}; - use sui_system::staking_pool::{Self, PoolTokenExchangeRate, StakedSui, StakingPool}; + use sui_system::staking_pool::{Self, PoolTokenExchangeRate, StakedSui, StakingPool, FungibleStakedSui}; use std::string::String; use sui::url::Url; use sui::url; @@ -160,6 +160,21 @@ module sui_system::validator { reward_amount: u64, } + /// Event emitted when a staked SUI is converted to a fungible staked SUI. + public struct ConvertingToFungibleStakedSuiEvent has copy, drop { + pool_id: ID, + stake_activation_epoch: u64, + staked_sui_principal_amount: u64, + fungible_staked_sui_amount: u64, + } + + /// Event emitted when a fungible staked SUI is redeemed. + public struct RedeemingFungibleStakedSuiEvent has copy, drop { + pool_id: ID, + fungible_staked_sui_amount: u64, + sui_amount: u64, + } + public(package) fun new_metadata( sui_address: address, protocol_pubkey_bytes: vector, @@ -306,6 +321,48 @@ module sui_system::validator { staked_sui } + public(package) fun convert_to_fungible_staked_sui( + self: &mut Validator, + staked_sui: StakedSui, + ctx: &mut TxContext, + ) : FungibleStakedSui { + let stake_activation_epoch = staked_sui.stake_activation_epoch(); + let staked_sui_principal_amount = staked_sui.staked_sui_amount(); + + let fungible_staked_sui = self.staking_pool.convert_to_fungible_staked_sui(staked_sui, ctx); + + event::emit( + ConvertingToFungibleStakedSuiEvent { + pool_id: self.staking_pool_id(), + stake_activation_epoch, + staked_sui_principal_amount, + fungible_staked_sui_amount: fungible_staked_sui.value(), + } + ); + + fungible_staked_sui + } + + public(package) fun redeem_fungible_staked_sui( + self: &mut Validator, + fungible_staked_sui: FungibleStakedSui, + ctx: &TxContext, + ) : Balance { + let fungible_staked_sui_amount = fungible_staked_sui.value(); + + let sui = self.staking_pool.redeem_fungible_staked_sui(fungible_staked_sui, ctx); + + event::emit( + RedeemingFungibleStakedSuiEvent { + pool_id: self.staking_pool_id(), + fungible_staked_sui_amount, + sui_amount: sui.value(), + } + ); + + sui + } + /// Request to add stake to the validator's staking pool at genesis public(package) fun request_add_stake_at_genesis( self: &mut Validator, diff --git a/crates/sui-framework/packages/sui-system/sources/validator_set.move b/crates/sui-framework/packages/sui-system/sources/validator_set.move index 2cc069f023b71..a5deb4e148a91 100644 --- a/crates/sui-framework/packages/sui-system/sources/validator_set.move +++ b/crates/sui-framework/packages/sui-system/sources/validator_set.move @@ -7,7 +7,7 @@ module sui_system::validator_set { use sui::sui::SUI; use sui_system::validator::{Validator, staking_pool_id, sui_address}; use sui_system::validator_cap::{Self, UnverifiedValidatorOperationCap, ValidatorOperationCap}; - use sui_system::staking_pool::{PoolTokenExchangeRate, StakedSui, pool_id}; + use sui_system::staking_pool::{PoolTokenExchangeRate, StakedSui, pool_id, FungibleStakedSui, fungible_staked_sui_pool_id}; use sui::priority_queue as pq; use sui::vec_map::{Self, VecMap}; use sui::vec_set::VecSet; @@ -309,6 +309,45 @@ module sui_system::validator_set { validator.request_withdraw_stake(staked_sui, ctx) } + public(package) fun convert_to_fungible_staked_sui( + self: &mut ValidatorSet, + staked_sui: StakedSui, + ctx: &mut TxContext, + ) : FungibleStakedSui { + let staking_pool_id = pool_id(&staked_sui); + let validator = + if (self.staking_pool_mappings.contains(staking_pool_id)) { // This is an active validator. + let validator_address = self.staking_pool_mappings[staking_pool_id]; + get_candidate_or_active_validator_mut(self, validator_address) + } else { // This is an inactive pool. + assert!(self.inactive_validators.contains(staking_pool_id), ENoPoolFound); + let wrapper = &mut self.inactive_validators[staking_pool_id]; + wrapper.load_validator_maybe_upgrade() + }; + + validator.convert_to_fungible_staked_sui(staked_sui, ctx) + } + + public(package) fun redeem_fungible_staked_sui( + self: &mut ValidatorSet, + fungible_staked_sui: FungibleStakedSui, + ctx: &TxContext, + ) : Balance { + let staking_pool_id = fungible_staked_sui_pool_id(&fungible_staked_sui); + + let validator = + if (self.staking_pool_mappings.contains(staking_pool_id)) { // This is an active validator. + let validator_address = self.staking_pool_mappings[staking_pool_id]; + get_candidate_or_active_validator_mut(self, validator_address) + } else { // This is an inactive pool. + assert!(self.inactive_validators.contains(staking_pool_id), ENoPoolFound); + let wrapper = &mut self.inactive_validators[staking_pool_id]; + wrapper.load_validator_maybe_upgrade() + }; + + validator.redeem_fungible_staked_sui(fungible_staked_sui, ctx) + } + // ==== validator config setting functions ==== public(package) fun request_set_commission_rate( diff --git a/crates/sui-framework/packages/sui-system/tests/staking_pool.move b/crates/sui-framework/packages/sui-system/tests/staking_pool.move new file mode 100644 index 0000000000000..2a38e5f249aa8 --- /dev/null +++ b/crates/sui-framework/packages/sui-system/tests/staking_pool.move @@ -0,0 +1,317 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module sui_system::staking_pool_tests { + use sui::test_scenario::{Self, Scenario}; + use sui_system::staking_pool::{StakingPool, Self}; + use sui::balance::{Self}; + + #[test] + fun test_join_fungible_staked_sui_happy() { + let mut scenario = test_scenario::begin(@0x0); + let staking_pool = staking_pool::new(scenario.ctx()); + + let mut fungible_staked_sui_1 = staking_pool.create_fungible_staked_sui_for_testing(100_000_000_000, scenario.ctx()); + let fungible_staked_sui_2 = staking_pool.create_fungible_staked_sui_for_testing(200_000_000_000, scenario.ctx()); + + fungible_staked_sui_1.join(fungible_staked_sui_2); + + assert!(fungible_staked_sui_1.value() == 300_000_000_000, 0); + + sui::test_utils::destroy(staking_pool); + sui::test_utils::destroy(fungible_staked_sui_1); + + scenario.end(); + } + + #[test] + #[expected_failure(abort_code = 1, location = sui_system::staking_pool)] + fun test_join_fungible_staked_sui_fail() { + let mut scenario = test_scenario::begin(@0x0); + let staking_pool_1 = staking_pool::new(scenario.ctx()); + let staking_pool_2 = staking_pool::new(scenario.ctx()); + + let mut fungible_staked_sui_1 = staking_pool_1.create_fungible_staked_sui_for_testing(100_000_000_000, scenario.ctx()); + let fungible_staked_sui_2 = staking_pool_2.create_fungible_staked_sui_for_testing(200_000_000_000, scenario.ctx()); + + fungible_staked_sui_1.join(fungible_staked_sui_2); + + sui::test_utils::destroy(staking_pool_1); + sui::test_utils::destroy(staking_pool_2); + sui::test_utils::destroy(fungible_staked_sui_1); + + scenario.end(); + } + + #[test] + fun test_split_fungible_staked_sui_happy() { + let mut scenario = test_scenario::begin(@0x0); + let staking_pool = staking_pool::new(scenario.ctx()); + + let mut fungible_staked_sui_1 = staking_pool.create_fungible_staked_sui_for_testing(100_000_000_000, scenario.ctx()); + + let fungible_staked_sui_2 = fungible_staked_sui_1.split(75_000_000_000, scenario.ctx()); + + assert!(fungible_staked_sui_1.value() == 25_000_000_000, 0); + assert!(fungible_staked_sui_2.value() == 75_000_000_000, 0); + + sui::test_utils::destroy(staking_pool); + sui::test_utils::destroy(fungible_staked_sui_1); + sui::test_utils::destroy(fungible_staked_sui_2); + + scenario.end(); + } + + #[test] + #[expected_failure(abort_code = 0, location = sui_system::staking_pool)] + fun test_split_fungible_staked_sui_fail_too_much() { + let mut scenario = test_scenario::begin(@0x0); + let staking_pool = staking_pool::new(scenario.ctx()); + + let mut fungible_staked_sui_1 = staking_pool.create_fungible_staked_sui_for_testing(100_000_000_000, scenario.ctx()); + + let fungible_staked_sui_2 = fungible_staked_sui_1.split(100_000_000_000 + 1, scenario.ctx()); + + sui::test_utils::destroy(staking_pool); + sui::test_utils::destroy(fungible_staked_sui_1); + sui::test_utils::destroy(fungible_staked_sui_2); + + scenario.end(); + } + + #[test] + #[expected_failure(abort_code = 19, location = sui_system::staking_pool)] + fun test_convert_to_fungible_staked_sui_fail_too_early() { + let mut scenario = test_scenario::begin(@0x0); + let mut staking_pool = staking_pool::new(scenario.ctx()); + + let sui = balance::create_for_testing(1_000_000_000); + let staked_sui = staking_pool.request_add_stake(sui, scenario.ctx().epoch() + 1, scenario.ctx()); + let fungible_staked_sui = staking_pool.convert_to_fungible_staked_sui(staked_sui, scenario.ctx()); + + sui::test_utils::destroy(staking_pool); + sui::test_utils::destroy(fungible_staked_sui); + + scenario.end(); + } + + #[test] + #[expected_failure(abort_code = 1, location = sui_system::staking_pool)] + fun test_convert_to_fungible_staked_sui_fail_wrong_pool() { + let mut scenario = test_scenario::begin(@0x0); + let mut staking_pool_1 = staking_pool::new(scenario.ctx()); + let mut staking_pool_2 = staking_pool::new(scenario.ctx()); + + let sui = balance::create_for_testing(1_000_000_000); + let staked_sui = staking_pool_1.request_add_stake(sui, scenario.ctx().epoch() + 1, scenario.ctx()); + + let fungible_staked_sui = staking_pool_2.convert_to_fungible_staked_sui(staked_sui, scenario.ctx()); + + sui::test_utils::destroy(staking_pool_1); + sui::test_utils::destroy(staking_pool_2); + sui::test_utils::destroy(fungible_staked_sui); + + scenario.end(); + } + + #[test] + fun test_convert_to_fungible_staked_sui_happy() { + let mut scenario = test_scenario::begin(@0x0); + let mut staking_pool = staking_pool::new(scenario.ctx()); + staking_pool.activate_staking_pool(0); + + // setup + + let sui = balance::create_for_testing(1_000_000_000); + let staked_sui_1 = staking_pool.request_add_stake(sui, scenario.ctx().epoch() + 1, scenario.ctx()); + + assert!(distribute_rewards_and_advance_epoch(&mut staking_pool, &mut scenario, 0) == 1, 0); + + let latest_exchange_rate = staking_pool.pool_token_exchange_rate_at_epoch(1); + assert!(latest_exchange_rate.sui_amount() == 1_000_000_000, 0); + assert!(latest_exchange_rate.pool_token_amount() == 1_000_000_000, 0); + + let sui = balance::create_for_testing(1_000_000_000); + let staked_sui_2 = staking_pool.request_add_stake(sui, scenario.ctx().epoch() + 1, scenario.ctx()); + + assert!(distribute_rewards_and_advance_epoch(&mut staking_pool, &mut scenario, 1_000_000_000) == 2, 0); + + let latest_exchange_rate = staking_pool.pool_token_exchange_rate_at_epoch(2); + assert!(latest_exchange_rate.sui_amount() == 3_000_000_000, 0); + assert!(latest_exchange_rate.pool_token_amount() == 1_500_000_000, 0); + + // test basically starts from here. + + let fungible_staked_sui_1 = staking_pool.convert_to_fungible_staked_sui(staked_sui_1, scenario.ctx()); + assert!(fungible_staked_sui_1.value() == 1_000_000_000, 0); + assert!(fungible_staked_sui_1.pool_id() == object::id(&staking_pool), 0); + + let fungible_staked_sui_data = staking_pool.fungible_staked_sui_data(); + assert!(fungible_staked_sui_data.total_supply() == 1_000_000_000, 0); + assert!(fungible_staked_sui_data.principal_value() == 1_000_000_000, 0); + + let fungible_staked_sui_2 = staking_pool.convert_to_fungible_staked_sui(staked_sui_2, scenario.ctx()); + assert!(fungible_staked_sui_2.value() == 500_000_000, 0); + assert!(fungible_staked_sui_2.pool_id() == object::id(&staking_pool), 0); + + let fungible_staked_sui_data = staking_pool.fungible_staked_sui_data(); + assert!(fungible_staked_sui_data.total_supply() == 1_500_000_000, 0); + assert!(fungible_staked_sui_data.principal_value() == 2_000_000_000, 0); + + sui::test_utils::destroy(staking_pool); + // sui::test_utils::destroy(fungible_staked_sui); + sui::test_utils::destroy(fungible_staked_sui_1); + sui::test_utils::destroy(fungible_staked_sui_2); + + scenario.end(); + } + + #[test] + fun test_redeem_fungible_staked_sui_happy() { + let mut scenario = test_scenario::begin(@0x0); + let mut staking_pool = staking_pool::new(scenario.ctx()); + staking_pool.activate_staking_pool(0); + + // setup + + let sui = balance::create_for_testing(1_000_000_000); + let staked_sui_1 = staking_pool.request_add_stake(sui, scenario.ctx().epoch() + 1, scenario.ctx()); + + assert!(distribute_rewards_and_advance_epoch(&mut staking_pool, &mut scenario, 0) == 1, 0); + + let latest_exchange_rate = staking_pool.pool_token_exchange_rate_at_epoch(1); + assert!(latest_exchange_rate.sui_amount() == 1_000_000_000, 0); + assert!(latest_exchange_rate.pool_token_amount() == 1_000_000_000, 0); + + let sui = balance::create_for_testing(1_000_000_000); + let staked_sui_2 = staking_pool.request_add_stake(sui, scenario.ctx().epoch() + 1, scenario.ctx()); + + assert!(distribute_rewards_and_advance_epoch(&mut staking_pool, &mut scenario, 1_000_000_000) == 2, 0); + + let latest_exchange_rate = staking_pool.pool_token_exchange_rate_at_epoch(2); + assert!(latest_exchange_rate.sui_amount() == 3_000_000_000, 0); + assert!(latest_exchange_rate.pool_token_amount() == 1_500_000_000, 0); + + let fungible_staked_sui_1 = staking_pool.convert_to_fungible_staked_sui(staked_sui_1, scenario.ctx()); + assert!(fungible_staked_sui_1.value() == 1_000_000_000, 0); + assert!(fungible_staked_sui_1.pool_id() == object::id(&staking_pool), 0); + + let fungible_staked_sui_data = staking_pool.fungible_staked_sui_data(); + assert!(fungible_staked_sui_data.total_supply() == 1_000_000_000, 0); + assert!(fungible_staked_sui_data.principal_value() == 1_000_000_000, 0); + + let fungible_staked_sui_2 = staking_pool.convert_to_fungible_staked_sui(staked_sui_2, scenario.ctx()); + assert!(fungible_staked_sui_2.value() == 500_000_000, 0); + assert!(fungible_staked_sui_2.pool_id() == object::id(&staking_pool), 0); + + let fungible_staked_sui_data = staking_pool.fungible_staked_sui_data(); + assert!(fungible_staked_sui_data.total_supply() == 1_500_000_000, 0); + assert!(fungible_staked_sui_data.principal_value() == 2_000_000_000, 0); + + // test starts here + assert!(distribute_rewards_and_advance_epoch(&mut staking_pool, &mut scenario, 3_000_000_000) == 3, 0); + + let latest_exchange_rate = staking_pool.pool_token_exchange_rate_at_epoch(3); + assert!(latest_exchange_rate.sui_amount() == 6_000_000_000, 0); + assert!(latest_exchange_rate.pool_token_amount() == 1_500_000_000, 0); + + assert!(staking_pool.pending_stake_withdraw_amount() == 0, 0); + assert!(staking_pool.pending_pool_token_withdraw_amount() == 0, 0); + + let sui_1 = staking_pool.redeem_fungible_staked_sui(fungible_staked_sui_1, scenario.ctx()); + assert!(sui_1.value() <= 4_000_000_000, 0); + assert!(sui_1.value() == 4_000_000_000 - 1, 0); + + let fungible_staked_sui_data = staking_pool.fungible_staked_sui_data(); + assert!(fungible_staked_sui_data.total_supply() == 500_000_000, 0); + assert!(fungible_staked_sui_data.principal_value() == 2_000_000_000 / 3 + 1, 0); // round against user + + assert!(staking_pool.pending_stake_withdraw_amount() == 4_000_000_000 - 1, 0); + assert!(staking_pool.pending_pool_token_withdraw_amount() == 1_000_000_000, 0); + + let sui_2 = staking_pool.redeem_fungible_staked_sui(fungible_staked_sui_2, scenario.ctx()); + assert!(sui_2.value() == 2_000_000_000, 0); + + let fungible_staked_sui_data = staking_pool.fungible_staked_sui_data(); + assert!(fungible_staked_sui_data.total_supply() == 0, 0); + assert!(fungible_staked_sui_data.principal_value() == 0, 0); + + assert!(staking_pool.pending_stake_withdraw_amount() == 6_000_000_000 - 1, 0); + assert!(staking_pool.pending_pool_token_withdraw_amount() == 1_500_000_000, 0); + + sui::test_utils::destroy(staking_pool); + sui::test_utils::destroy(sui_1); + sui::test_utils::destroy(sui_2); + + scenario.end(); + } + + #[test] + fun test_redeem_fungible_staked_sui_regression_rounding() { + let mut scenario = test_scenario::begin(@0x0); + let mut staking_pool = staking_pool::new(scenario.ctx()); + staking_pool.activate_staking_pool(0); + + // setup + + let sui = balance::create_for_testing(1_000_000_000); + let staked_sui_1 = staking_pool.request_add_stake(sui, scenario.ctx().epoch() + 1, scenario.ctx()); + + assert!(distribute_rewards_and_advance_epoch(&mut staking_pool, &mut scenario, 0) == 1, 0); + + let latest_exchange_rate = staking_pool.pool_token_exchange_rate_at_epoch(1); + assert!(latest_exchange_rate.sui_amount() == 1_000_000_000, 0); + assert!(latest_exchange_rate.pool_token_amount() == 1_000_000_000, 0); + + let sui = balance::create_for_testing(1_000_000_001); + let staked_sui_2 = staking_pool.request_add_stake(sui, scenario.ctx().epoch() + 1, scenario.ctx()); + + assert!(distribute_rewards_and_advance_epoch(&mut staking_pool, &mut scenario, 1_000_000_000) == 2, 0); + + let latest_exchange_rate = staking_pool.pool_token_exchange_rate_at_epoch(2); + assert!(latest_exchange_rate.sui_amount() == 3_000_000_001, 0); + assert!(latest_exchange_rate.pool_token_amount() == 1_500_000_000, 0); + + let fungible_staked_sui = staking_pool.convert_to_fungible_staked_sui(staked_sui_2, scenario.ctx()); + assert!(fungible_staked_sui.value() == 500_000_000, 0); // rounding! + assert!(fungible_staked_sui.pool_id() == object::id(&staking_pool), 0); + + let fungible_staked_sui_data = staking_pool.fungible_staked_sui_data(); + assert!(fungible_staked_sui_data.total_supply() == 500_000_000, 0); + assert!(fungible_staked_sui_data.principal_value() == 1_000_000_001, 0); + + // this line used to error + let sui = staking_pool.redeem_fungible_staked_sui(fungible_staked_sui, scenario.ctx()); + assert!(sui.value() == 1_000_000_000, 0); + + let fungible_staked_sui_data = staking_pool.fungible_staked_sui_data(); + assert!(fungible_staked_sui_data.total_supply() == 0, 0); + assert!(fungible_staked_sui_data.principal_value() == 1, 0); + + sui::test_utils::destroy(staking_pool); + sui::test_utils::destroy(staked_sui_1); + sui::test_utils::destroy(sui); + + scenario.end(); + } + + #[test_only] + fun distribute_rewards_and_advance_epoch( + staking_pool: &mut StakingPool, + scenario: &mut Scenario, + reward_amount: u64 + ): u64 { + use sui::tx_context::{epoch}; + use sui::coin::{Self}; + use sui::sui::SUI; + + let rewards = coin::mint_for_testing(reward_amount, scenario.ctx()); + staking_pool.deposit_rewards(coin::into_balance(rewards)); + + staking_pool.process_pending_stakes_and_withdraws(scenario.ctx()); + test_scenario::next_epoch(scenario, @0x0); + + scenario.ctx().epoch() + } +} diff --git a/crates/sui-framework/packages/sui-system/tests/sui_system_tests.move b/crates/sui-framework/packages/sui-system/tests/sui_system_tests.move index 25c963d2aa9ea..d333f4e64a642 100644 --- a/crates/sui-framework/packages/sui-system/tests/sui_system_tests.move +++ b/crates/sui-framework/packages/sui-system/tests/sui_system_tests.move @@ -9,6 +9,7 @@ module sui_system::sui_system_tests { use sui::test_scenario::{Self, Scenario}; use sui::sui::SUI; + use sui::coin::Self; use sui_system::governance_test_utils::{add_validator_full_flow, advance_epoch, remove_validator, set_up_sui_system_state, create_sui_system_state_for_testing, stake_with, unstake}; use sui_system::sui_system::SuiSystemState; use sui_system::sui_system_state_inner; @@ -1070,4 +1071,58 @@ module sui_system::sui_system_tests { scenario_val.end(); } + + #[test] + fun test_convert_to_fungible_staked_sui_and_redeem() { + let mut scenario_val = test_scenario::begin(@0x0); + let scenario = &mut scenario_val; + // Epoch duration is set to be 42 here. + set_up_sui_system_state(vector[@0x1, @0x2]); + + { + scenario.next_tx(@0x0); + let mut system_state = scenario.take_shared(); + let staking_pool = system_state.active_validator_by_address(@0x1).get_staking_pool_ref(); + + assert!(staking_pool.pending_stake_amount() == 0, 0); + assert!(staking_pool.pending_stake_withdraw_amount() == 0, 0); + assert!(staking_pool.sui_balance() == 100 * 1_000_000_000, 0); + + test_scenario::return_shared(system_state); + }; + + scenario.next_tx(@0x0); + let mut system_state = scenario.take_shared(); + + let staked_sui = system_state.request_add_stake_non_entry( + coin::mint_for_testing(100_000_000_000, scenario.ctx()), + @0x1, + scenario.ctx() + ); + + assert!(staked_sui.amount() == 100_000_000_000, 0); + + test_scenario::return_shared(system_state); + advance_epoch(scenario); + + let mut system_state = scenario.take_shared(); + let fungible_staked_sui = system_state.convert_to_fungible_staked_sui( + staked_sui, + scenario.ctx() + ); + + assert!(fungible_staked_sui.value() == 100_000_000_000, 0); + + let sui = system_state.redeem_fungible_staked_sui( + fungible_staked_sui, + scenario.ctx() + ); + + assert!(sui.value() == 100_000_000_000, 0); + + test_scenario::return_shared(system_state); + sui::test_utils::destroy(sui); + scenario_val.end(); + } + } diff --git a/crates/sui-framework/packages_compiled/move-stdlib b/crates/sui-framework/packages_compiled/move-stdlib index 8526133aab6a42f2b55fcc6b66147182fd964640..191c7bd74e552887d4ce54cbdffbabd73852fb75 100644 GIT binary patch delta 1127 zcmaJ>%}x_h6h8OdJOBMfP(mcRL5c~I6bdzQN0iD!q9N+W4H@VZCTZuEnJI`1>jSuw zC-4n4E_?u=02e04@B}V=0;A_n+fZ0Ai*L^T?sva)?&+Dn@1C8o8=u*C-v8!&y#3oV z0l;N|h=Q9>jO|$~QuaBM=3n*`-5TZ)wzFE}ezVD&;gQ{n zRc9xR(tH*i_EZ`MQ97Vw{}}fJ+)phM?2tNpcY1AX2iQ(IMWU;cWuEkQ%~WZdqJGmk zt9O{?IX$b*_4B#LMi8k^82PD+UA(urJ z{kQyBlh^sTwPeiydVAODH=Z2@ajUJ4U0kS)VyPQWMM^)Wn|FdV+>@#s#C{s8$i-5z zu@N4H2`wI;yrQPKST2nixqFf|3XtWwo*Zl0Wi$kXXHFP~WfBWZ0PpU)MX0Z=S96BP z>MOFE6Q`f>{tJ@>LOt8(wSU8Bp5sREQ> zvR+^sZ=SBtPxULJXp~J0OxVoMqCTsK*q+S9nRY=qhL?$+YSG2UEfNbYwldCRWs4Z& z5@QpiuQ$}J$mK1xXzfgTP^k?$r IoLATW1DAN*ZU6uP delta 15 Wcmcau*;BwMw2|==^X3^GmL>o)wFRL7 diff --git a/crates/sui-framework/packages_compiled/sui-system b/crates/sui-framework/packages_compiled/sui-system index d576a4d949d827f27ea732656c0f901d9e794308..2f5cc8064c3d9535e1ec7fc97f87d22e2eacf020 100644 GIT binary patch delta 17489 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 delta 15076 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 return Some(cur_ref); } - let compatibility = Compatibility { - check_datatype_and_pub_function_linking: true, - check_datatype_layout: true, - check_friend_linking: false, - // Checking `entry` linkage is required because system packages are updated in-place, and a - // transaction that was rolled back to make way for reconfiguration should still be runnable - // after a reconfiguration that upgraded the framework. - // - // A transaction that calls a system function that was previously `entry` and is now private - // will fail because its entrypoint became no longer callable. A transaction that calls a - // system function that was previously `public entry` and is now just `public` could also - // fail if one of its mutable inputs was being used in another private `entry` function. - check_private_entry_linking: true, - disallowed_new_abilities: AbilitySet::singleton(Ability::Key), - disallow_change_datatype_type_params: true, - disallow_new_variants: true, - }; + let compatibility = Compatibility::framework_upgrade_check(); let new_pkg = new_object .data diff --git a/crates/sui-graphql-e2e-tests/tests/stable/call/dynamic_fields.exp b/crates/sui-graphql-e2e-tests/tests/stable/call/dynamic_fields.exp index cb95142a98918..54894fbd9b26d 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/call/dynamic_fields.exp +++ b/crates/sui-graphql-e2e-tests/tests/stable/call/dynamic_fields.exp @@ -1,4 +1,4 @@ -processed 13 tasks +processed 12 tasks init: A: object(0,0) @@ -31,7 +31,7 @@ task 5, line 60: //# create-checkpoint Checkpoint created: 1 -task 6, lines 62-85: +task 6, lines 62-118: //# run-graphql Response: { "data": { @@ -49,7 +49,14 @@ Response: { "bcs": "AA==" }, "value": { - "__typename": "MoveValue" + "__typename": "MoveValue", + "type": { + "repr": "u64" + }, + "bcs": "AQAAAAAAAAA=", + "data": { + "Number": "1" + } } }, { @@ -63,7 +70,56 @@ Response: { "bcs": "AAAAAAAAAAA=" }, "value": { - "__typename": "MoveObject" + "__typename": "MoveObject", + "contents": { + "type": { + "repr": "0x314faae908e1e1e6f5df9f3a03a4692a27cb974776f13551472d3831f5a964d9::m::Child" + }, + "bcs": "79SK5tokongSfcWj2640StZoQwIktLuTsSSR/HV6Odc=", + "data": { + "Struct": [ + { + "name": "id", + "value": { + "UID": [ + 239, + 212, + 138, + 230, + 218, + 36, + 162, + 120, + 18, + 125, + 197, + 163, + 219, + 174, + 52, + 74, + 214, + 104, + 67, + 2, + 36, + 180, + 187, + 147, + 177, + 36, + 145, + 252, + 117, + 122, + 57, + 215 + ] + } + } + ] + } + } } }, { @@ -77,7 +133,14 @@ Response: { "bcs": "AA==" }, "value": { - "__typename": "MoveValue" + "__typename": "MoveValue", + "type": { + "repr": "u64" + }, + "bcs": "AgAAAAAAAAA=", + "data": { + "Number": "2" + } } }, { @@ -91,31 +154,20 @@ Response: { "bcs": "AAAAAAAAAAA=" }, "value": { - "__typename": "MoveValue" + "__typename": "MoveValue", + "type": { + "repr": "u64" + }, + "bcs": "AAAAAAAAAAA=", + "data": { + "Number": "0" + } } } ] } - } - } -} - -task 7, line 87: -//# run Test::m::wrap --sender A --args object(2,0) -created: object(7,0) -mutated: object(0,0) -wrapped: object(2,0) -gas summary: computation_cost: 1000000, storage_cost: 2485200, storage_rebate: 2212056, non_refundable_storage_fee: 22344 - -task 8, line 89: -//# create-checkpoint -Checkpoint created: 2 - -task 9, lines 91-114: -//# run-graphql -Response: { - "data": { - "object": { + }, + "owner": { "dynamicFields": { "nodes": [ { @@ -129,7 +181,14 @@ Response: { "bcs": "AA==" }, "value": { - "__typename": "MoveValue" + "__typename": "MoveValue", + "type": { + "repr": "u64" + }, + "bcs": "AQAAAAAAAAA=", + "data": { + "Number": "1" + } } }, { @@ -143,7 +202,56 @@ Response: { "bcs": "AAAAAAAAAAA=" }, "value": { - "__typename": "MoveObject" + "__typename": "MoveObject", + "contents": { + "type": { + "repr": "0x314faae908e1e1e6f5df9f3a03a4692a27cb974776f13551472d3831f5a964d9::m::Child" + }, + "bcs": "79SK5tokongSfcWj2640StZoQwIktLuTsSSR/HV6Odc=", + "data": { + "Struct": [ + { + "name": "id", + "value": { + "UID": [ + 239, + 212, + 138, + 230, + 218, + 36, + 162, + 120, + 18, + 125, + 197, + 163, + 219, + 174, + 52, + 74, + 214, + 104, + 67, + 2, + 36, + 180, + 187, + 147, + 177, + 36, + 145, + 252, + 117, + 122, + 57, + 215 + ] + } + } + ] + } + } } }, { @@ -157,7 +265,14 @@ Response: { "bcs": "AA==" }, "value": { - "__typename": "MoveValue" + "__typename": "MoveValue", + "type": { + "repr": "u64" + }, + "bcs": "AgAAAAAAAAA=", + "data": { + "Number": "2" + } } }, { @@ -171,7 +286,14 @@ Response: { "bcs": "AAAAAAAAAAA=" }, "value": { - "__typename": "MoveValue" + "__typename": "MoveValue", + "type": { + "repr": "u64" + }, + "bcs": "AAAAAAAAAAA=", + "data": { + "Number": "0" + } } } ] @@ -180,10 +302,22 @@ Response: { } } -task 10, lines 116-141: +task 7, line 120: +//# run Test::m::wrap --sender A --args object(2,0) +created: object(7,0) +mutated: object(0,0) +wrapped: object(2,0) +gas summary: computation_cost: 1000000, storage_cost: 2485200, storage_rebate: 2212056, non_refundable_storage_fee: 22344 + +task 8, line 122: +//# create-checkpoint +Checkpoint created: 2 + +task 9, lines 124-180: //# run-graphql Response: { "data": { + "object": null, "owner": { "dynamicFields": { "nodes": [ @@ -198,11 +332,14 @@ Response: { "bcs": "AA==" }, "value": { + "__typename": "MoveValue", + "type": { + "repr": "u64" + }, "bcs": "AQAAAAAAAAA=", "data": { "Number": "1" - }, - "__typename": "MoveValue" + } } }, { @@ -216,7 +353,56 @@ Response: { "bcs": "AAAAAAAAAAA=" }, "value": { - "__typename": "MoveObject" + "__typename": "MoveObject", + "contents": { + "type": { + "repr": "0x314faae908e1e1e6f5df9f3a03a4692a27cb974776f13551472d3831f5a964d9::m::Child" + }, + "bcs": "79SK5tokongSfcWj2640StZoQwIktLuTsSSR/HV6Odc=", + "data": { + "Struct": [ + { + "name": "id", + "value": { + "UID": [ + 239, + 212, + 138, + 230, + 218, + 36, + 162, + 120, + 18, + 125, + 197, + 163, + 219, + 174, + 52, + 74, + 214, + 104, + 67, + 2, + 36, + 180, + 187, + 147, + 177, + 36, + 145, + 252, + 117, + 122, + 57, + 215 + ] + } + } + ] + } + } } }, { @@ -230,11 +416,14 @@ Response: { "bcs": "AA==" }, "value": { + "__typename": "MoveValue", + "type": { + "repr": "u64" + }, "bcs": "AgAAAAAAAAA=", "data": { "Number": "2" - }, - "__typename": "MoveValue" + } } }, { @@ -248,11 +437,14 @@ Response: { "bcs": "AAAAAAAAAAA=" }, "value": { + "__typename": "MoveValue", + "type": { + "repr": "u64" + }, "bcs": "AAAAAAAAAAA=", "data": { "Number": "0" - }, - "__typename": "MoveValue" + } } } ] @@ -261,7 +453,7 @@ Response: { } } -task 11, lines 143-163: +task 10, lines 182-201: //# run-graphql Response: { "data": { @@ -278,6 +470,9 @@ Response: { }, "value": { "__typename": "MoveValue", + "type": { + "repr": "u64" + }, "bcs": "AAAAAAAAAAA=", "data": { "Number": "0" @@ -288,14 +483,63 @@ Response: { } } -task 12, lines 165-176: +task 11, lines 203-219: //# run-graphql Response: { "data": { "owner": { "dynamicObjectField": { "value": { - "__typename": "MoveObject" + "__typename": "MoveObject", + "contents": { + "type": { + "repr": "0x314faae908e1e1e6f5df9f3a03a4692a27cb974776f13551472d3831f5a964d9::m::Child" + }, + "bcs": "79SK5tokongSfcWj2640StZoQwIktLuTsSSR/HV6Odc=", + "data": { + "Struct": [ + { + "name": "id", + "value": { + "UID": [ + 239, + 212, + 138, + 230, + 218, + 36, + 162, + 120, + 18, + 125, + 197, + 163, + 219, + 174, + 52, + 74, + 214, + 104, + 67, + 2, + 36, + 180, + 187, + 147, + 177, + 36, + 145, + 252, + 117, + 122, + 57, + 215 + ] + } + } + ] + } + } } } } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/call/dynamic_fields.move b/crates/sui-graphql-e2e-tests/tests/stable/call/dynamic_fields.move index e9c5a46703ea8..f4113f39eb6a0 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/call/dynamic_fields.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/call/dynamic_fields.move @@ -60,23 +60,56 @@ module Test::m { //# create-checkpoint //# run-graphql -{ +{ # Initially, the parent object should be accessible directly and as an + # "Owner". object(address: "@{obj_2_0}") { dynamicFields { nodes { name { - type { - repr - } + type { repr } data bcs } value { + __typename + ... on MoveValue { + type { repr } + bcs + data + } ... on MoveObject { - __typename + contents { + type { repr } + bcs + data + } } + } + } + } + } + + owner(address: "@{obj_2_0}") { + dynamicFields { + nodes { + name { + type { repr } + data + bcs + } + value { + __typename ... on MoveValue { - __typename + type { repr } + bcs + data + } + ... on MoveObject { + contents { + type { repr } + bcs + data + } } } } @@ -89,50 +122,56 @@ module Test::m { //# create-checkpoint //# run-graphql -{ +{ # After it is wrapped, we can no longer fetch its latest version via `object` + # but we can still refer to it as an "Owner" and fetch its dynamic fields. object(address: "@{obj_2_0}") { dynamicFields { nodes { name { - type { - repr - } + type { repr } data bcs } value { - ... on MoveObject { - __typename - } + __typename ... on MoveValue { - __typename + type { repr } + bcs + data + } + ... on MoveObject { + contents { + type { repr } + bcs + data + } } } } } } -} -//# run-graphql -{ owner(address: "@{obj_2_0}") { dynamicFields { nodes { name { - type { - repr - } + type { repr } data bcs } value { - ... on MoveObject { - __typename - } + __typename ... on MoveValue { + type { repr } bcs data - __typename + } + ... on MoveObject { + contents { + type { repr } + bcs + data + } } } } @@ -145,15 +184,14 @@ module Test::m { owner(address: "@{obj_2_0}") { dynamicField(name: {type: "u64", bcs: "AAAAAAAAAAA="}) { name { - type { - repr - } + type { repr } data bcs } value { ... on MoveValue { __typename + type { repr } bcs data } @@ -169,6 +207,11 @@ module Test::m { value { ... on MoveObject { __typename + contents { + type { repr } + bcs + data + } } } } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/consistency/balances.move b/crates/sui-graphql-e2e-tests/tests/stable/consistency/balances.move index 2f524ac0e7d3c..d45a225849419 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/consistency/balances.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/consistency/balances.move @@ -78,7 +78,7 @@ module P0::fake { //# run-graphql --cursors {"c":2,"t":1,"i":false} # Emulating viewing transaction blocks at checkpoint 2. Fake coin balance should be 700. { - transactionBlocks(first: 1, after: "@{cursor_0}", filter: {signAddress: "@{A}"}) { + transactionBlocks(first: 1, after: "@{cursor_0}", filter: {sentAddress: "@{A}"}) { nodes { sender { fakeCoinBalance: balance(type: "@{P0}::fake::FAKE") { @@ -101,7 +101,7 @@ module P0::fake { //# run-graphql --cursors {"c":3,"t":1,"i":false} # Emulating viewing transaction blocks at checkpoint 3. Fake coin balance should be 500. { - transactionBlocks(first: 1, after: "@{cursor_0}", filter: {signAddress: "@{A}"}) { + transactionBlocks(first: 1, after: "@{cursor_0}", filter: {sentAddress: "@{A}"}) { nodes { sender { fakeCoinBalance: balance(type: "@{P0}::fake::FAKE") { @@ -124,7 +124,7 @@ module P0::fake { //# run-graphql --cursors {"c":4,"t":1,"i":false} # Emulating viewing transaction blocks at checkpoint 4. Fake coin balance should be 400. { - transactionBlocks(first: 1, after: "@{cursor_0}", filter: {signAddress: "@{A}"}) { + transactionBlocks(first: 1, after: "@{cursor_0}", filter: {sentAddress: "@{A}"}) { nodes { sender { fakeCoinBalance: balance(type: "@{P0}::fake::FAKE") { @@ -151,7 +151,7 @@ module P0::fake { //# run-graphql --cursors {"c":2,"t":1,"i":false} # Emulating viewing transaction blocks at checkpoint 2. Fake coin balance should be 700. { - transactionBlocks(first: 1, after: "@{cursor_0}", filter: {signAddress: "@{A}"}) { + transactionBlocks(first: 1, after: "@{cursor_0}", filter: {sentAddress: "@{A}"}) { nodes { sender { fakeCoinBalance: balance(type: "@{P0}::fake::FAKE") { @@ -174,7 +174,7 @@ module P0::fake { //# run-graphql --cursors {"c":3,"t":1,"i":false} # Emulating viewing transaction blocks at checkpoint 3. Fake coin balance should be 500. { - transactionBlocks(first: 1, after: "@{cursor_0}", filter: {signAddress: "@{A}"}) { + transactionBlocks(first: 1, after: "@{cursor_0}", filter: {sentAddress: "@{A}"}) { nodes { sender { fakeCoinBalance: balance(type: "@{P0}::fake::FAKE") { @@ -197,7 +197,7 @@ module P0::fake { //# run-graphql --cursors {"c":4,"t":1,"i":false} # Emulating viewing transaction blocks at checkpoint 4. Fake coin balance should be 400. { - transactionBlocks(first: 1, after: "@{cursor_0}", filter: {signAddress: "@{A}"}) { + transactionBlocks(first: 1, after: "@{cursor_0}", filter: {sentAddress: "@{A}"}) { nodes { sender { fakeCoinBalance: balance(type: "@{P0}::fake::FAKE") { @@ -236,7 +236,7 @@ module P0::fake { sequenceNumber } } - transactionBlocks(first: 1, after: "@{cursor_0}", filter: {signAddress: "@{A}"}) { + transactionBlocks(first: 1, after: "@{cursor_0}", filter: {sentAddress: "@{A}"}) { nodes { sender { fakeCoinBalance: balance(type: "@{P0}::fake::FAKE") { @@ -259,7 +259,7 @@ module P0::fake { //# run-graphql --cursors {"c":3,"t":1,"i":false} # Emulating viewing transaction blocks at checkpoint 3. Fake coin balance should be 500. { - transactionBlocks(first: 1, after: "@{cursor_0}", filter: {signAddress: "@{A}"}) { + transactionBlocks(first: 1, after: "@{cursor_0}", filter: {sentAddress: "@{A}"}) { nodes { sender { fakeCoinBalance: balance(type: "@{P0}::fake::FAKE") { @@ -282,7 +282,7 @@ module P0::fake { //# run-graphql --cursors {"c":4,"t":1,"i":false} # Emulating viewing transaction blocks at checkpoint 4. Fake coin balance should be 400. { - transactionBlocks(first: 1, after: "@{cursor_0}", filter: {signAddress: "@{A}"}) { + transactionBlocks(first: 1, after: "@{cursor_0}", filter: {sentAddress: "@{A}"}) { nodes { sender { fakeCoinBalance: balance(type: "@{P0}::fake::FAKE") { @@ -329,7 +329,7 @@ module P0::fake { sequenceNumber } } - transactionBlocks(first: 1, after: "@{cursor_0}", filter: {signAddress: "@{A}"}) { + transactionBlocks(first: 1, after: "@{cursor_0}", filter: {sentAddress: "@{A}"}) { nodes { sender { fakeCoinBalance: balance(type: "@{P0}::fake::FAKE") { @@ -360,7 +360,7 @@ module P0::fake { sequenceNumber } } - transactionBlocks(first: 1, after: "@{cursor_0}", filter: {signAddress: "@{A}"}) { + transactionBlocks(first: 1, after: "@{cursor_0}", filter: {sentAddress: "@{A}"}) { nodes { sender { fakeCoinBalance: balance(type: "@{P0}::fake::FAKE") { @@ -391,7 +391,7 @@ module P0::fake { sequenceNumber } } - transactionBlocks(first: 1, after: "@{cursor_0}", filter: {signAddress: "@{A}"}) { + transactionBlocks(first: 1, after: "@{cursor_0}", filter: {sentAddress: "@{A}"}) { nodes { sender { fakeCoinBalance: balance(type: "@{P0}::fake::FAKE") { diff --git a/crates/sui-graphql-e2e-tests/tests/stable/consistency/checkpoints/transaction_blocks.move b/crates/sui-graphql-e2e-tests/tests/stable/consistency/checkpoints/transaction_blocks.move index c505e86cccd0c..ef8a78bd82599 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/consistency/checkpoints/transaction_blocks.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/consistency/checkpoints/transaction_blocks.move @@ -56,7 +56,7 @@ module Test::M1 { checkpoints { nodes { sequenceNumber - transactionBlocks(filter: { signAddress: "@{A}"}) { + transactionBlocks(filter: { sentAddress: "@{A}"}) { edges { cursor node { diff --git a/crates/sui-graphql-e2e-tests/tests/stable/consistency/epochs/transaction_blocks.move b/crates/sui-graphql-e2e-tests/tests/stable/consistency/epochs/transaction_blocks.move index 425849aef9e16..f6e1f31b4777a 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/consistency/epochs/transaction_blocks.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/consistency/epochs/transaction_blocks.move @@ -249,7 +249,7 @@ module Test::M1 { checkpoint { sequenceNumber } - with_cursor: transactionBlocks(after: "@{cursor_0}", filter: {signAddress: "@{A}"}) { + with_cursor: transactionBlocks(after: "@{cursor_0}", filter: {sentAddress: "@{A}"}) { edges { cursor node { @@ -264,7 +264,7 @@ module Test::M1 { } } } - without_cursor: transactionBlocks(filter: {signAddress: "@{A}"}) { + without_cursor: transactionBlocks(filter: {sentAddress: "@{A}"}) { edges { cursor node { diff --git a/crates/sui-graphql-e2e-tests/tests/stable/consistency/object_at_version.exp b/crates/sui-graphql-e2e-tests/tests/stable/consistency/object_at_version.exp index 4cfc609655cfe..d45f731c7e1d6 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/consistency/object_at_version.exp +++ b/crates/sui-graphql-e2e-tests/tests/stable/consistency/object_at_version.exp @@ -93,11 +93,7 @@ task 10, lines 111-137: //# run-graphql Response: { "data": { - "latest_wrapped": { - "status": "WRAPPED_OR_DELETED", - "version": 5, - "asMoveObject": null - }, + "latest_wrapped": null, "previous_version": { "status": "INDEXED", "version": 4, @@ -140,11 +136,7 @@ Response: { } } }, - "previous_version": { - "status": "WRAPPED_OR_DELETED", - "version": 5, - "asMoveObject": null - }, + "previous_version": null, "first_version": { "status": "INDEXED", "version": 3, @@ -174,16 +166,8 @@ task 16, lines 187-213: //# run-graphql Response: { "data": { - "latest_deleted": { - "status": "WRAPPED_OR_DELETED", - "version": 7, - "asMoveObject": null - }, - "version_specified": { - "status": "WRAPPED_OR_DELETED", - "version": 7, - "asMoveObject": null - } + "latest_deleted": null, + "version_specified": null } } @@ -275,11 +259,7 @@ Response: { } } }, - "wrapped_or_deleted_object": { - "status": "WRAPPED_OR_DELETED", - "version": 5, - "asMoveObject": null - }, + "wrapped_or_deleted_object": null, "object_not_in_snapshot": { "status": "INDEXED", "version": 3, diff --git a/crates/sui-graphql-e2e-tests/tests/stable/consistency/tx_address_objects.move b/crates/sui-graphql-e2e-tests/tests/stable/consistency/tx_address_objects.move index 4f7bf42061ed0..e4dc118588e3b 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/consistency/tx_address_objects.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/consistency/tx_address_objects.move @@ -71,7 +71,7 @@ module Test::M1 { } } } - latest_tx_at_checkpoint_3: transactionBlocks(last: 1, filter: {signAddress: "@{A}"}) { + latest_tx_at_checkpoint_3: transactionBlocks(last: 1, filter: {sentAddress: "@{A}"}) { nodes { sender { objects_consistent_with_address_at_latest_checkpoint_4: objects(filter: {type: "@{Test}"}) { @@ -135,7 +135,7 @@ module Test::M1 { //# run-graphql { - all_transactions: transactionBlocks(first: 4, filter: {signAddress: "@{A}"}) { + all_transactions: transactionBlocks(first: 4, filter: {sentAddress: "@{A}"}) { nodes { sender { objects(filter: {type: "@{Test}"}) { @@ -195,7 +195,7 @@ module Test::M1 { } } } - latest_tx_at_checkpoint_3: transactionBlocks(last: 1, filter: {signAddress: "@{A}"}) { + latest_tx_at_checkpoint_3: transactionBlocks(last: 1, filter: {sentAddress: "@{A}"}) { nodes { sender { objects(filter: {type: "@{Test}"}) { @@ -262,7 +262,7 @@ module Test::M1 { //# run-graphql # Regardless of the transaction block, the nested fields should yield the same data. { - all_transactions: transactionBlocks(first: 4, filter: {signAddress: "@{A}"}) { + all_transactions: transactionBlocks(first: 4, filter: {sentAddress: "@{A}"}) { nodes { sender { objects(filter: {type: "@{Test}"}) { diff --git a/crates/sui-graphql-e2e-tests/tests/stable/epoch/epoch_start_to_end.exp b/crates/sui-graphql-e2e-tests/tests/stable/epoch/epoch_start_to_end.exp new file mode 100644 index 0000000000000..d2c454ee4e5a7 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/stable/epoch/epoch_start_to_end.exp @@ -0,0 +1,231 @@ +processed 13 tasks + +init: +C: object(0,0) + +task 1, line 6: +//# create-checkpoint +Checkpoint created: 1 + +task 2, line 8: +//# advance-epoch +Epoch advanced: 0 + +task 3, lines 10-12: +//# programmable --sender C --inputs 10000000000 @C +//> SplitCoins(Gas, [Input(0)]); +//> TransferObjects([Result(0)], Input(1)); +created: object(3,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 1976000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 4, lines 14-16: +//# programmable --sender C --inputs 5000000000 @C +//> SplitCoins(Gas, [Input(0)]); +//> TransferObjects([Result(0)], Input(1)); +created: object(4,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 1976000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 5, line 18: +//# run 0x3::sui_system::request_add_stake --args object(0x5) object(3,0) @validator_0 --sender C +events: Event { package_id: sui_system, transaction_module: Identifier("sui_system"), sender: C, type_: StructTag { address: sui_system, module: Identifier("validator"), name: Identifier("StakingRequestEvent"), type_params: [] }, contents: [135, 141, 242, 35, 38, 24, 124, 195, 86, 219, 178, 127, 110, 40, 201, 151, 112, 169, 166, 183, 93, 180, 71, 210, 141, 37, 35, 151, 110, 94, 69, 29, 218, 131, 22, 109, 1, 175, 215, 221, 207, 138, 245, 248, 68, 244, 90, 170, 83, 244, 133, 72, 229, 17, 124, 35, 245, 162, 151, 140, 253, 66, 34, 68, 252, 204, 154, 66, 27, 187, 19, 193, 166, 106, 26, 169, 143, 10, 215, 80, 41, 237, 233, 72, 87, 119, 156, 105, 21, 180, 79, 148, 6, 139, 146, 30, 1, 0, 0, 0, 0, 0, 0, 0, 0, 228, 11, 84, 2, 0, 0, 0] } +created: object(5,0) +mutated: object(_), 0x0000000000000000000000000000000000000000000000000000000000000005, object(0,0) +deleted: object(3,0) +gas summary: computation_cost: 1000000, storage_cost: 15078400, storage_rebate: 1956240, non_refundable_storage_fee: 19760 + +task 6, line 20: +//# create-checkpoint +Checkpoint created: 3 + +task 7, lines 22-72: +//# run-graphql +Response: { + "data": { + "epoch": { + "epochId": 1, + "referenceGasPrice": "1000", + "validatorSet": { + "totalStake": "20000000000000000", + "activeValidators": { + "nodes": [ + { + "name": "validator-0" + } + ] + } + }, + "startTimestamp": "1970-01-01T00:00:00Z", + "totalCheckpoints": 0, + "totalTransactions": null, + "totalGasFees": null, + "totalStakeRewards": null, + "totalStakeSubsidies": null, + "fundSize": "0", + "netInflow": null, + "fundInflow": null, + "fundOutflow": null, + "storageFund": { + "totalObjectStorageRebates": "0", + "nonRefundableBalance": "0" + }, + "safeMode": { + "enabled": false + }, + "systemStateVersion": 2, + "systemParameters": { + "stakeSubsidyStartEpoch": 0 + }, + "systemStakeSubsidy": { + "balance": "9949400000000000000", + "currentDistributionAmount": "1000000000000000" + }, + "checkpoints": { + "nodes": [ + { + "sequenceNumber": 3 + } + ] + }, + "transactionBlocks": { + "nodes": [ + { + "digest": "HqhN74u19oG1oVeEjLJx9Z5RdYxT7XWoGYHMmM1QNQ2q" + } + ] + }, + "endTimestamp": null + } + } +} + +task 8, line 74: +//# create-checkpoint +Checkpoint created: 4 + +task 9, line 76: +//# advance-epoch +Epoch advanced: 1 + +task 10, line 78: +//# create-checkpoint +Checkpoint created: 6 + +task 11, lines 80-130: +//# run-graphql +Response: { + "data": { + "epoch": { + "epochId": 1, + "referenceGasPrice": "1000", + "validatorSet": { + "totalStake": "20000000000000000", + "activeValidators": { + "nodes": [ + { + "name": "validator-0" + } + ] + } + }, + "startTimestamp": "1970-01-01T00:00:00Z", + "totalCheckpoints": 2, + "totalTransactions": 4, + "totalGasFees": "3000000", + "totalStakeRewards": "3000000", + "totalStakeSubsidies": "0", + "fundSize": "0", + "netInflow": "16096040", + "fundInflow": "19030400", + "fundOutflow": "2934360", + "storageFund": { + "totalObjectStorageRebates": "0", + "nonRefundableBalance": "0" + }, + "safeMode": { + "enabled": false + }, + "systemStateVersion": 2, + "systemParameters": { + "stakeSubsidyStartEpoch": 0 + }, + "systemStakeSubsidy": { + "balance": "9949400000000000000", + "currentDistributionAmount": "1000000000000000" + }, + "checkpoints": { + "nodes": [ + { + "sequenceNumber": 5 + } + ] + }, + "transactionBlocks": { + "nodes": [ + { + "digest": "BjA5MhqTJWEkp2gyPhBBBYcER2nYoYevdGSmagrrvVRb" + } + ] + }, + "endTimestamp": "1970-01-01T00:00:00Z" + } + } +} + +task 12, lines 132-182: +//# run-graphql +Response: { + "data": { + "epoch": { + "epochId": 2, + "referenceGasPrice": "1000", + "validatorSet": { + "totalStake": "20000010003000000", + "activeValidators": { + "nodes": [ + { + "name": "validator-0" + } + ] + } + }, + "startTimestamp": "1970-01-01T00:00:00Z", + "totalCheckpoints": 0, + "totalTransactions": null, + "totalGasFees": null, + "totalStakeRewards": null, + "totalStakeSubsidies": null, + "fundSize": "16096040", + "netInflow": null, + "fundInflow": null, + "fundOutflow": null, + "storageFund": { + "totalObjectStorageRebates": "16066400", + "nonRefundableBalance": "29640" + }, + "safeMode": { + "enabled": false + }, + "systemStateVersion": 2, + "systemParameters": { + "stakeSubsidyStartEpoch": 0 + }, + "systemStakeSubsidy": { + "balance": "9949400000000000000", + "currentDistributionAmount": "1000000000000000" + }, + "checkpoints": { + "nodes": [ + { + "sequenceNumber": 6 + } + ] + }, + "transactionBlocks": { + "nodes": [] + }, + "endTimestamp": null + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/stable/epoch/epoch_start_to_end.move b/crates/sui-graphql-e2e-tests/tests/stable/epoch/epoch_start_to_end.move new file mode 100644 index 0000000000000..28b0b82822c92 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/stable/epoch/epoch_start_to_end.move @@ -0,0 +1,182 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --protocol-version 51 --simulator --accounts C + +//# create-checkpoint + +//# advance-epoch + +//# programmable --sender C --inputs 10000000000 @C +//> SplitCoins(Gas, [Input(0)]); +//> TransferObjects([Result(0)], Input(1)); + +//# programmable --sender C --inputs 5000000000 @C +//> SplitCoins(Gas, [Input(0)]); +//> TransferObjects([Result(0)], Input(1)); + +//# run 0x3::sui_system::request_add_stake --args object(0x5) object(3,0) @validator_0 --sender C + +//# create-checkpoint + +//# run-graphql +{ + epoch(id: 1) { + epochId + referenceGasPrice + validatorSet { + totalStake + activeValidators { + nodes { + name + } + } + } + startTimestamp + totalCheckpoints + totalTransactions + totalGasFees + totalStakeRewards + totalStakeSubsidies + fundSize + netInflow + fundInflow + fundOutflow + storageFund { + totalObjectStorageRebates + nonRefundableBalance + } + safeMode { + enabled + } + systemStateVersion + systemParameters { + stakeSubsidyStartEpoch + } + systemStakeSubsidy { + balance + currentDistributionAmount + } + checkpoints(last: 1) { + nodes { + sequenceNumber + } + } + transactionBlocks(last: 1) { + nodes { + digest + } + } + endTimestamp + } +} + +//# create-checkpoint + +//# advance-epoch + +//# create-checkpoint + +//# run-graphql +{ + epoch(id: 1) { + epochId + referenceGasPrice + validatorSet { + totalStake + activeValidators { + nodes { + name + } + } + } + startTimestamp + totalCheckpoints + totalTransactions + totalGasFees + totalStakeRewards + totalStakeSubsidies + fundSize + netInflow + fundInflow + fundOutflow + storageFund { + totalObjectStorageRebates + nonRefundableBalance + } + safeMode { + enabled + } + systemStateVersion + systemParameters { + stakeSubsidyStartEpoch + } + systemStakeSubsidy { + balance + currentDistributionAmount + } + checkpoints(last: 1) { + nodes { + sequenceNumber + } + } + transactionBlocks(last: 1) { + nodes { + digest + } + } + endTimestamp + } +} + +//# run-graphql +{ + epoch(id: 2) { + epochId + referenceGasPrice + validatorSet { + totalStake + activeValidators { + nodes { + name + } + } + } + startTimestamp + totalCheckpoints + totalTransactions + totalGasFees + totalStakeRewards + totalStakeSubsidies + fundSize + netInflow + fundInflow + fundOutflow + storageFund { + totalObjectStorageRebates + nonRefundableBalance + } + safeMode { + enabled + } + systemStateVersion + systemParameters { + stakeSubsidyStartEpoch + } + systemStakeSubsidy { + balance + currentDistributionAmount + } + checkpoints(last: 1) { + nodes { + sequenceNumber + } + } + transactionBlocks(last: 1) { + nodes { + digest + } + } + endTimestamp + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/stable/epoch/pagination.exp b/crates/sui-graphql-e2e-tests/tests/stable/epoch/pagination.exp new file mode 100644 index 0000000000000..8086291fc3762 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/stable/epoch/pagination.exp @@ -0,0 +1,194 @@ +processed 15 tasks + +init: +C: object(0,0) + +task 1, line 6: +//# advance-epoch +Epoch advanced: 0 + +task 2, line 8: +//# advance-epoch +Epoch advanced: 1 + +task 3, line 10: +//# advance-epoch +Epoch advanced: 2 + +task 4, line 12: +//# advance-epoch +Epoch advanced: 3 + +task 5, line 14: +//# advance-epoch +Epoch advanced: 4 + +task 6, line 16: +//# advance-epoch +Epoch advanced: 5 + +task 7, lines 18-29: +//# run-graphql +Response: { + "data": { + "epochs": { + "pageInfo": { + "hasPreviousPage": true, + "hasNextPage": false + }, + "nodes": [ + { + "epochId": 4 + }, + { + "epochId": 5 + } + ] + } + } +} + +task 8, lines 31-42: +//# run-graphql +Response: { + "data": { + "epochs": { + "pageInfo": { + "hasPreviousPage": false, + "hasNextPage": true + }, + "nodes": [ + { + "epochId": 0 + }, + { + "epochId": 1 + }, + { + "epochId": 2 + } + ] + } + } +} + +task 9, lines 44-55: +//# run-graphql --cursors {"c":5,"e":2} +Response: { + "data": { + "epochs": { + "pageInfo": { + "hasPreviousPage": false, + "hasNextPage": true + }, + "nodes": [ + { + "epochId": 0 + }, + { + "epochId": 1 + } + ] + } + } +} + +task 10, lines 57-68: +//# run-graphql --cursors {"c":3,"e":4} +Response: { + "data": { + "epochs": { + "pageInfo": { + "hasPreviousPage": false, + "hasNextPage": false + }, + "nodes": [ + { + "epochId": 0 + }, + { + "epochId": 1 + }, + { + "epochId": 2 + } + ] + } + } +} + +task 11, lines 70-81: +//# run-graphql --cursors {"c":11,"e":1} +Response: { + "data": { + "epochs": { + "pageInfo": { + "hasPreviousPage": true, + "hasNextPage": false + }, + "nodes": [ + { + "epochId": 2 + }, + { + "epochId": 3 + }, + { + "epochId": 4 + }, + { + "epochId": 5 + }, + { + "epochId": 6 + } + ] + } + } +} + +task 12, lines 83-94: +//# run-graphql --cursors {"c":0,"e":5} +Response: { + "data": { + "epochs": { + "pageInfo": { + "hasPreviousPage": false, + "hasNextPage": false + }, + "nodes": [ + { + "epochId": 0 + } + ] + } + } +} + +task 13, lines 96-107: +//# run-graphql --cursors {"c":3,"e":4} +Response: { + "data": { + "epochs": { + "pageInfo": { + "hasPreviousPage": false, + "hasNextPage": false + }, + "nodes": [] + } + } +} + +task 14, lines 109-120: +//# run-graphql --cursors {"c":0,"e":0} +Response: { + "data": { + "epochs": { + "pageInfo": { + "hasPreviousPage": false, + "hasNextPage": false + }, + "nodes": [] + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/stable/epoch/pagination.move b/crates/sui-graphql-e2e-tests/tests/stable/epoch/pagination.move new file mode 100644 index 0000000000000..fea4bf3701126 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/stable/epoch/pagination.move @@ -0,0 +1,120 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --protocol-version 51 --simulator --accounts C + +//# advance-epoch + +//# advance-epoch + +//# advance-epoch + +//# advance-epoch + +//# advance-epoch + +//# advance-epoch + +//# run-graphql +{ + epochs(last:2) { + pageInfo { + hasPreviousPage + hasNextPage + } + nodes { + epochId + } + } +} + +//# run-graphql +{ + epochs(first:3) { + pageInfo { + hasPreviousPage + hasNextPage + } + nodes { + epochId + } + } +} + +//# run-graphql --cursors {"c":5,"e":2} +{ + epochs(before: "@{cursor_0}") { + pageInfo { + hasPreviousPage + hasNextPage + } + nodes { + epochId + } + } +} + +//# run-graphql --cursors {"c":3,"e":4} +{ + epochs(before: "@{cursor_0}") { + pageInfo { + hasPreviousPage + hasNextPage + } + nodes { + epochId + } + } +} + +//# run-graphql --cursors {"c":11,"e":1} +{ + epochs(after: "@{cursor_0}") { + pageInfo { + hasPreviousPage + hasNextPage + } + nodes { + epochId + } + } +} + +//# run-graphql --cursors {"c":0,"e":5} +{ + epochs(before: "@{cursor_0}") { + pageInfo { + hasPreviousPage + hasNextPage + } + nodes { + epochId + } + } +} + +//# run-graphql --cursors {"c":3,"e":4} +{ + epochs(after: "@{cursor_0}") { + pageInfo { + hasPreviousPage + hasNextPage + } + nodes { + epochId + } + } +} + +//# run-graphql --cursors {"c":0,"e":0} +{ + epochs(after: "@{cursor_0}") { + pageInfo { + hasPreviousPage + hasNextPage + } + nodes { + epochId + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/stable/epoch/system_state.exp b/crates/sui-graphql-e2e-tests/tests/stable/epoch/system_state.exp index 25c9da026cd9c..257225f55b87b 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/epoch/system_state.exp +++ b/crates/sui-graphql-e2e-tests/tests/stable/epoch/system_state.exp @@ -99,8 +99,8 @@ Response: { "epochId": 4, "systemStateVersion": 2, "storageFund": { - "totalObjectStorageRebates": "15762400", - "nonRefundableBalance": "180424" + "totalObjectStorageRebates": "16066400", + "nonRefundableBalance": "29640" } } } @@ -114,8 +114,8 @@ Response: { "epochId": 3, "systemStateVersion": 2, "storageFund": { - "totalObjectStorageRebates": "16066400", - "nonRefundableBalance": "29640" + "totalObjectStorageRebates": "15078400", + "nonRefundableBalance": "19760" } } } @@ -144,8 +144,8 @@ Response: { "epochId": 1, "systemStateVersion": 2, "storageFund": { - "totalObjectStorageRebates": "15078400", - "nonRefundableBalance": "19760" + "totalObjectStorageRebates": "0", + "nonRefundableBalance": "0" } } } @@ -159,8 +159,8 @@ Response: { "epochId": 4, "systemStateVersion": 2, "storageFund": { - "totalObjectStorageRebates": "15762400", - "nonRefundableBalance": "180424" + "totalObjectStorageRebates": "16066400", + "nonRefundableBalance": "29640" } } } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/combo_filter_error.exp b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/combo_filter_error.exp index 2e134867efbdc..ad9d34aea15c9 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/combo_filter_error.exp +++ b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/combo_filter_error.exp @@ -19,7 +19,7 @@ task 3, line 32: //# create-checkpoint Checkpoint created: 1 -task 4, lines 34-51: +task 4, lines 34-53: //# run-graphql Response: { "data": null, diff --git a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/combo_filter_error.move b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/combo_filter_error.move index ad38316463e76..8ef5c475c5b62 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/combo_filter_error.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/combo_filter_error.move @@ -38,14 +38,16 @@ module Test::M2 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + bcs + json + } } } } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/event_connection.exp b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/event_connection.exp index eb00b535469f7..139415a5a1001 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/event_connection.exp +++ b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/event_connection.exp @@ -49,7 +49,7 @@ task 8, line 95: //# create-checkpoint Checkpoint created: 1 -task 9, lines 97-117: +task 9, lines 97-119: //# run-graphql Response: { "data": { @@ -61,16 +61,18 @@ Response: { "sendingModule": { "name": "M1" }, - "type": { - "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "0" - }, - "bcs": "AAAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::EventA" + }, + "json": { + "new_value": "0" + }, + "bcs": "AAAAAAAAAAA=" + } } }, { @@ -79,16 +81,18 @@ Response: { "sendingModule": { "name": "M1" }, - "type": { - "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::EventB<0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::Object>" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "1" - }, - "bcs": "AQAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::EventB<0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::Object>" + }, + "json": { + "new_value": "1" + }, + "bcs": "AQAAAAAAAAA=" + } } }, { @@ -97,16 +101,18 @@ Response: { "sendingModule": { "name": "M2" }, - "type": { - "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M2::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "2" - }, - "bcs": "AgAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M2::EventA" + }, + "json": { + "new_value": "2" + }, + "bcs": "AgAAAAAAAAA=" + } } }, { @@ -115,16 +121,18 @@ Response: { "sendingModule": { "name": "M2" }, - "type": { - "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M2::EventB<0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M2::Object>" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "3" - }, - "bcs": "AwAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M2::EventB<0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M2::Object>" + }, + "json": { + "new_value": "3" + }, + "bcs": "AwAAAAAAAAA=" + } } } ] @@ -132,7 +140,7 @@ Response: { } } -task 10, lines 119-139: +task 10, lines 121-143: //# run-graphql Response: { "data": { @@ -144,16 +152,18 @@ Response: { "sendingModule": { "name": "M1" }, - "type": { - "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "0" - }, - "bcs": "AAAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::EventA" + }, + "json": { + "new_value": "0" + }, + "bcs": "AAAAAAAAAAA=" + } } }, { @@ -162,16 +172,18 @@ Response: { "sendingModule": { "name": "M1" }, - "type": { - "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::EventB<0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::Object>" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "1" - }, - "bcs": "AQAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::EventB<0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::Object>" + }, + "json": { + "new_value": "1" + }, + "bcs": "AQAAAAAAAAA=" + } } }, { @@ -180,16 +192,18 @@ Response: { "sendingModule": { "name": "M2" }, - "type": { - "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M2::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "2" - }, - "bcs": "AgAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M2::EventA" + }, + "json": { + "new_value": "2" + }, + "bcs": "AgAAAAAAAAA=" + } } }, { @@ -198,16 +212,18 @@ Response: { "sendingModule": { "name": "M2" }, - "type": { - "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M2::EventB<0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M2::Object>" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "3" - }, - "bcs": "AwAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M2::EventB<0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M2::Object>" + }, + "json": { + "new_value": "3" + }, + "bcs": "AwAAAAAAAAA=" + } } } ] @@ -215,7 +231,7 @@ Response: { } } -task 11, lines 141-161: +task 11, lines 145-168: //# run-graphql Response: { "data": { @@ -227,16 +243,18 @@ Response: { "sendingModule": { "name": "M1" }, - "type": { - "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "0" - }, - "bcs": "AAAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::EventA" + }, + "json": { + "new_value": "0" + }, + "bcs": "AAAAAAAAAAA=" + } } }, { @@ -245,16 +263,18 @@ Response: { "sendingModule": { "name": "M1" }, - "type": { - "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::EventB<0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::Object>" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "1" - }, - "bcs": "AQAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::EventB<0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::Object>" + }, + "json": { + "new_value": "1" + }, + "bcs": "AQAAAAAAAAA=" + } } } ] @@ -262,7 +282,7 @@ Response: { } } -task 12, lines 163-183: +task 12, lines 170-193: //# run-graphql Response: { "data": { @@ -274,16 +294,18 @@ Response: { "sendingModule": { "name": "M1" }, - "type": { - "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "0" - }, - "bcs": "AAAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::EventA" + }, + "json": { + "new_value": "0" + }, + "bcs": "AAAAAAAAAAA=" + } } } ] @@ -291,7 +313,7 @@ Response: { } } -task 13, lines 185-205: +task 13, lines 195-218: //# run-graphql Response: { "data": { @@ -303,16 +325,18 @@ Response: { "sendingModule": { "name": "M1" }, - "type": { - "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::EventB<0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::Object>" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "1" - }, - "bcs": "AQAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::EventB<0x08c5d7466382add5a3962c5fbe295af8fc8331bd3140f4f23b031cc1439416ab::M1::Object>" + }, + "json": { + "new_value": "1" + }, + "bcs": "AQAAAAAAAAA=" + } } } ] @@ -320,7 +344,7 @@ Response: { } } -task 14, lines 207-227: +task 14, lines 220-243: //# run-graphql Response: { "data": null, @@ -340,7 +364,7 @@ Response: { ] } -task 15, lines 229-249: +task 15, lines 245-267: //# run-graphql Response: { "data": null, @@ -360,7 +384,7 @@ Response: { ] } -task 16, lines 251-271: +task 16, lines 269-291: //# run-graphql Response: { "data": null, diff --git a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/event_connection.move b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/event_connection.move index 4e5dc82048984..20c8216720c0d 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/event_connection.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/event_connection.move @@ -103,14 +103,16 @@ module Test::M2 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + json + bcs + } } } } @@ -125,14 +127,16 @@ module Test::M2 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + json + bcs + } } } } @@ -147,14 +151,17 @@ module Test::M2 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + json + bcs + } + } } } @@ -169,14 +176,17 @@ module Test::M2 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + json + bcs + } + } } } @@ -191,14 +201,17 @@ module Test::M2 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + json + bcs + } + } } } @@ -213,14 +226,17 @@ module Test::M2 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + json + bcs + } + } } } @@ -235,14 +251,16 @@ module Test::M2 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + json + bcs + } } } } @@ -257,14 +275,16 @@ module Test::M2 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + json + bcs + } } } } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/nested_emit_event.exp b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/nested_emit_event.exp index 97c97d609d009..09231bfdea419 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/nested_emit_event.exp +++ b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/nested_emit_event.exp @@ -19,7 +19,7 @@ task 3, line 41: //# create-checkpoint Checkpoint created: 1 -task 4, lines 43-60: +task 4, lines 43-62: //# run-graphql Response: { "data": { @@ -29,23 +29,25 @@ Response: { "sendingModule": { "name": "M3" }, - "type": { - "repr": "0x0d72d40a7520bc67f48a16f325a5f189cb79788025784c7f41bf02598d02d2d0::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "2" - }, - "bcs": "AgAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x0d72d40a7520bc67f48a16f325a5f189cb79788025784c7f41bf02598d02d2d0::M1::EventA" + }, + "bcs": "AgAAAAAAAAA=", + "json": { + "new_value": "2" + } + } } ] } } } -task 5, lines 62-79: +task 5, lines 64-83: //# run-graphql Response: { "data": { @@ -55,23 +57,25 @@ Response: { "sendingModule": { "name": "M3" }, - "type": { - "repr": "0x0d72d40a7520bc67f48a16f325a5f189cb79788025784c7f41bf02598d02d2d0::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "2" - }, - "bcs": "AgAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x0d72d40a7520bc67f48a16f325a5f189cb79788025784c7f41bf02598d02d2d0::M1::EventA" + }, + "bcs": "AgAAAAAAAAA=", + "json": { + "new_value": "2" + } + } } ] } } } -task 6, lines 81-98: +task 6, lines 85-104: //# run-graphql Response: { "data": { @@ -81,7 +85,7 @@ Response: { } } -task 7, lines 100-117: +task 7, lines 106-125: //# run-graphql Response: { "data": { @@ -91,7 +95,7 @@ Response: { } } -task 8, lines 119-136: +task 8, lines 127-146: //# run-graphql Response: { "data": { @@ -101,16 +105,18 @@ Response: { "sendingModule": { "name": "M3" }, - "type": { - "repr": "0x0d72d40a7520bc67f48a16f325a5f189cb79788025784c7f41bf02598d02d2d0::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "2" - }, - "bcs": "AgAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x0d72d40a7520bc67f48a16f325a5f189cb79788025784c7f41bf02598d02d2d0::M1::EventA" + }, + "bcs": "AgAAAAAAAAA=", + "json": { + "new_value": "2" + } + } } ] } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/nested_emit_event.move b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/nested_emit_event.move index 62290d4c2ed47..5052283aa94b2 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/nested_emit_event.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/nested_emit_event.move @@ -47,14 +47,16 @@ module Test::M3 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + bcs + json + } } } } @@ -66,14 +68,16 @@ module Test::M3 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + bcs + json + } } } } @@ -85,14 +89,16 @@ module Test::M3 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + bcs + json + } } } } @@ -104,14 +110,16 @@ module Test::M3 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + bcs + json + } } } } @@ -123,14 +131,16 @@ module Test::M3 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + bcs + json + } } } } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/no_filter.exp b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/no_filter.exp index 3d916fe5b9508..043c30f732dd0 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/no_filter.exp +++ b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/no_filter.exp @@ -19,7 +19,7 @@ task 3, line 29: //# create-checkpoint Checkpoint created: 1 -task 4, lines 31-44: +task 4, lines 31-46: //# run-graphql Response: { "data": { @@ -32,103 +32,143 @@ Response: { }, "nodes": [ { - "json": { - "new_value": "0" + "contents": { + "json": { + "new_value": "0" + } } }, { - "json": { - "new_value": "1" + "contents": { + "json": { + "new_value": "1" + } } }, { - "json": { - "new_value": "2" + "contents": { + "json": { + "new_value": "2" + } } }, { - "json": { - "new_value": "3" + "contents": { + "json": { + "new_value": "3" + } } }, { - "json": { - "new_value": "4" + "contents": { + "json": { + "new_value": "4" + } } }, { - "json": { - "new_value": "5" + "contents": { + "json": { + "new_value": "5" + } } }, { - "json": { - "new_value": "6" + "contents": { + "json": { + "new_value": "6" + } } }, { - "json": { - "new_value": "7" + "contents": { + "json": { + "new_value": "7" + } } }, { - "json": { - "new_value": "8" + "contents": { + "json": { + "new_value": "8" + } } }, { - "json": { - "new_value": "9" + "contents": { + "json": { + "new_value": "9" + } } }, { - "json": { - "new_value": "10" + "contents": { + "json": { + "new_value": "10" + } } }, { - "json": { - "new_value": "11" + "contents": { + "json": { + "new_value": "11" + } } }, { - "json": { - "new_value": "12" + "contents": { + "json": { + "new_value": "12" + } } }, { - "json": { - "new_value": "13" + "contents": { + "json": { + "new_value": "13" + } } }, { - "json": { - "new_value": "14" + "contents": { + "json": { + "new_value": "14" + } } }, { - "json": { - "new_value": "15" + "contents": { + "json": { + "new_value": "15" + } } }, { - "json": { - "new_value": "16" + "contents": { + "json": { + "new_value": "16" + } } }, { - "json": { - "new_value": "17" + "contents": { + "json": { + "new_value": "17" + } } }, { - "json": { - "new_value": "18" + "contents": { + "json": { + "new_value": "18" + } } }, { - "json": { - "new_value": "19" + "contents": { + "json": { + "new_value": "19" + } } } ] @@ -136,7 +176,7 @@ Response: { } } -task 5, lines 46-59: +task 5, lines 48-63: //# run-graphql --cursors {"tx":2,"e":19,"c":1} Response: { "data": { @@ -149,103 +189,143 @@ Response: { }, "nodes": [ { - "json": { - "new_value": "20" + "contents": { + "json": { + "new_value": "20" + } } }, { - "json": { - "new_value": "21" + "contents": { + "json": { + "new_value": "21" + } } }, { - "json": { - "new_value": "22" + "contents": { + "json": { + "new_value": "22" + } } }, { - "json": { - "new_value": "23" + "contents": { + "json": { + "new_value": "23" + } } }, { - "json": { - "new_value": "24" + "contents": { + "json": { + "new_value": "24" + } } }, { - "json": { - "new_value": "25" + "contents": { + "json": { + "new_value": "25" + } } }, { - "json": { - "new_value": "26" + "contents": { + "json": { + "new_value": "26" + } } }, { - "json": { - "new_value": "27" + "contents": { + "json": { + "new_value": "27" + } } }, { - "json": { - "new_value": "28" + "contents": { + "json": { + "new_value": "28" + } } }, { - "json": { - "new_value": "29" + "contents": { + "json": { + "new_value": "29" + } } }, { - "json": { - "new_value": "30" + "contents": { + "json": { + "new_value": "30" + } } }, { - "json": { - "new_value": "31" + "contents": { + "json": { + "new_value": "31" + } } }, { - "json": { - "new_value": "32" + "contents": { + "json": { + "new_value": "32" + } } }, { - "json": { - "new_value": "33" + "contents": { + "json": { + "new_value": "33" + } } }, { - "json": { - "new_value": "34" + "contents": { + "json": { + "new_value": "34" + } } }, { - "json": { - "new_value": "35" + "contents": { + "json": { + "new_value": "35" + } } }, { - "json": { - "new_value": "36" + "contents": { + "json": { + "new_value": "36" + } } }, { - "json": { - "new_value": "37" + "contents": { + "json": { + "new_value": "37" + } } }, { - "json": { - "new_value": "38" + "contents": { + "json": { + "new_value": "38" + } } }, { - "json": { - "new_value": "39" + "contents": { + "json": { + "new_value": "39" + } } } ] diff --git a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/no_filter.move b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/no_filter.move index 657b666bfb53e..815674f93799a 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/no_filter.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/no_filter.move @@ -38,7 +38,9 @@ module Test::M1 { endCursor } nodes { + contents { json + } } } } @@ -53,7 +55,9 @@ module Test::M1 { endCursor } nodes { - json + contents { + json + } } } } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/pagination.exp b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/pagination.exp index 6eabbcf2d106c..8cb858caeb73e 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/pagination.exp +++ b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/pagination.exp @@ -25,7 +25,7 @@ task 4, line 28: //# create-checkpoint Checkpoint created: 1 -task 5, lines 30-54: +task 5, lines 30-56: //# run-graphql Response: { "data": { @@ -41,16 +41,18 @@ Response: { "sendingModule": { "name": "M1" }, - "type": { - "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "0" - }, - "bcs": "AAAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" + }, + "json": { + "new_value": "0" + }, + "bcs": "AAAAAAAAAAA=" + } } }, { @@ -59,16 +61,18 @@ Response: { "sendingModule": { "name": "M1" }, - "type": { - "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "1" - }, - "bcs": "AQAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" + }, + "json": { + "new_value": "1" + }, + "bcs": "AQAAAAAAAAA=" + } } }, { @@ -77,16 +81,18 @@ Response: { "sendingModule": { "name": "M1" }, - "type": { - "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "2" - }, - "bcs": "AgAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" + }, + "json": { + "new_value": "2" + }, + "bcs": "AgAAAAAAAAA=" + } } } ] @@ -94,7 +100,7 @@ Response: { } } -task 6, lines 56-80: +task 6, lines 58-84: //# run-graphql --cursors {"tx":2,"e":0,"c":1} Response: { "data": { @@ -110,16 +116,18 @@ Response: { "sendingModule": { "name": "M1" }, - "type": { - "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "1" - }, - "bcs": "AQAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" + }, + "json": { + "new_value": "1" + }, + "bcs": "AQAAAAAAAAA=" + } } }, { @@ -128,16 +136,18 @@ Response: { "sendingModule": { "name": "M1" }, - "type": { - "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "2" - }, - "bcs": "AgAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" + }, + "json": { + "new_value": "2" + }, + "bcs": "AgAAAAAAAAA=" + } } } ] @@ -145,7 +155,7 @@ Response: { } } -task 7, lines 82-106: +task 7, lines 86-113: //# run-graphql --cursors {"tx":3,"e":1,"c":1} Response: { "data": { @@ -161,16 +171,18 @@ Response: { "sendingModule": { "name": "M1" }, - "type": { - "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "0" - }, - "bcs": "AAAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" + }, + "json": { + "new_value": "0" + }, + "bcs": "AAAAAAAAAAA=" + } } }, { @@ -179,16 +191,18 @@ Response: { "sendingModule": { "name": "M1" }, - "type": { - "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "1" - }, - "bcs": "AQAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" + }, + "json": { + "new_value": "1" + }, + "bcs": "AQAAAAAAAAA=" + } } } ] @@ -196,7 +210,7 @@ Response: { } } -task 8, lines 108-132: +task 8, lines 115-141: //# run-graphql Response: { "data": { @@ -212,16 +226,18 @@ Response: { "sendingModule": { "name": "M1" }, - "type": { - "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "1" - }, - "bcs": "AQAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" + }, + "json": { + "new_value": "1" + }, + "bcs": "AQAAAAAAAAA=" + } } }, { @@ -230,16 +246,18 @@ Response: { "sendingModule": { "name": "M1" }, - "type": { - "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "2" - }, - "bcs": "AgAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x5ba7fdbca31dfa8d9642b26cd297043adba83d87276b248ef9feecd2fe80f9ec::M1::EventA" + }, + "json": { + "new_value": "2" + }, + "bcs": "AgAAAAAAAAA=" + } } } ] diff --git a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/pagination.move b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/pagination.move index 560c08d090ca6..d590867837991 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/pagination.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/pagination.move @@ -40,14 +40,16 @@ module Test::M1 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + json + bcs + } } } } @@ -66,14 +68,16 @@ module Test::M1 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + json + bcs + } } } } @@ -92,14 +96,17 @@ module Test::M1 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + json + bcs + } + } } } @@ -118,14 +125,16 @@ module Test::M1 { sendingModule { name } - type { - repr - } sender { address + } + contents { + type { + repr + } + json + bcs } - json - bcs } } } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/tx_digest.exp b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/tx_digest.exp index 8e5a7df7a6c2e..51ea925c086ac 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/tx_digest.exp +++ b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/tx_digest.exp @@ -56,7 +56,7 @@ Response: { } } -task 7, lines 45-55: +task 7, lines 45-57: //# run-graphql Response: { "data": { @@ -65,16 +65,20 @@ Response: { { "cursor": "eyJ0eCI6MywiZSI6MCwiYyI6MX0", "node": { - "json": { - "new_value": "2" + "contents": { + "json": { + "new_value": "2" + } } } }, { "cursor": "eyJ0eCI6MywiZSI6MSwiYyI6MX0", "node": { - "json": { - "new_value": "3" + "contents": { + "json": { + "new_value": "3" + } } } } @@ -83,7 +87,7 @@ Response: { } } -task 8, lines 57-68: +task 8, lines 59-72: //# run-graphql --cursors {"tx":3,"e":1,"c":1} Response: { "data": { @@ -93,7 +97,7 @@ Response: { } } -task 9, lines 70-83: +task 9, lines 74-89: //# run-graphql --cursors {"tx":1,"e":1,"c":1} Response: { "data": { @@ -103,7 +107,7 @@ Response: { } } -task 10, lines 86-96: +task 10, lines 92-104: //# run-graphql Response: { "data": { @@ -112,16 +116,20 @@ Response: { { "cursor": "eyJ0eCI6NCwiZSI6MCwiYyI6MX0", "node": { - "json": { - "new_value": "4" + "contents": { + "json": { + "new_value": "4" + } } } }, { "cursor": "eyJ0eCI6NCwiZSI6MSwiYyI6MX0", "node": { - "json": { - "new_value": "5" + "contents": { + "json": { + "new_value": "5" + } } } } @@ -130,7 +138,7 @@ Response: { } } -task 11, lines 98-108: +task 11, lines 106-118: //# run-graphql --cursors {"tx":4,"e":0,"c":1} Response: { "data": { @@ -139,8 +147,10 @@ Response: { { "cursor": "eyJ0eCI6NCwiZSI6MSwiYyI6MX0", "node": { - "json": { - "new_value": "5" + "contents": { + "json": { + "new_value": "5" + } } } } @@ -149,7 +159,7 @@ Response: { } } -task 12, lines 111-121: +task 12, lines 121-133: //# run-graphql Response: { "data": { @@ -158,16 +168,20 @@ Response: { { "cursor": "eyJ0eCI6MywiZSI6MCwiYyI6MX0", "node": { - "json": { - "new_value": "2" + "contents": { + "json": { + "new_value": "2" + } } } }, { "cursor": "eyJ0eCI6MywiZSI6MSwiYyI6MX0", "node": { - "json": { - "new_value": "3" + "contents": { + "json": { + "new_value": "3" + } } } } @@ -176,7 +190,7 @@ Response: { } } -task 13, lines 123-134: +task 13, lines 135-148: //# run-graphql --cursors {"tx":3,"e":1,"c":1} Response: { "data": { @@ -185,8 +199,10 @@ Response: { { "cursor": "eyJ0eCI6MywiZSI6MCwiYyI6MX0", "node": { - "json": { - "new_value": "2" + "contents": { + "json": { + "new_value": "2" + } } } } @@ -195,7 +211,7 @@ Response: { } } -task 14, lines 136-149: +task 14, lines 150-165: //# run-graphql --cursors {"tx":4,"e":1,"c":1} Response: { "data": { @@ -205,7 +221,7 @@ Response: { } } -task 15, lines 152-162: +task 15, lines 168-180: //# run-graphql Response: { "data": { @@ -214,16 +230,20 @@ Response: { { "cursor": "eyJ0eCI6NCwiZSI6MCwiYyI6MX0", "node": { - "json": { - "new_value": "4" + "contents": { + "json": { + "new_value": "4" + } } } }, { "cursor": "eyJ0eCI6NCwiZSI6MSwiYyI6MX0", "node": { - "json": { - "new_value": "5" + "contents": { + "json": { + "new_value": "5" + } } } } @@ -232,26 +252,24 @@ Response: { } } -task 16, lines 164-174: +task 16, lines 182-195: //# run-graphql --cursors {"tx":4,"e":1,"c":1} Response: { - "data": { - "events": { - "edges": [ + "data": null, + "errors": [ + { + "message": "Unknown field \"json\" on type \"Event\".", + "locations": [ { - "cursor": "eyJ0eCI6NCwiZSI6MCwiYyI6MX0", - "node": { - "json": { - "new_value": "4" - } - } + "line": 9, + "column": 17 } ] } - } + ] } -task 17, lines 176-187: +task 17, lines 197-210: //# run-graphql Response: { "data": { @@ -260,16 +278,20 @@ Response: { { "cursor": "eyJ0eCI6MywiZSI6MCwiYyI6MX0", "node": { - "json": { - "new_value": "2" + "contents": { + "json": { + "new_value": "2" + } } } }, { "cursor": "eyJ0eCI6MywiZSI6MSwiYyI6MX0", "node": { - "json": { - "new_value": "3" + "contents": { + "json": { + "new_value": "3" + } } } } @@ -278,7 +300,7 @@ Response: { } } -task 18, lines 189-200: +task 18, lines 212-225: //# run-graphql Response: { "data": { @@ -287,16 +309,20 @@ Response: { { "cursor": "eyJ0eCI6NCwiZSI6MCwiYyI6MX0", "node": { - "json": { - "new_value": "4" + "contents": { + "json": { + "new_value": "4" + } } } }, { "cursor": "eyJ0eCI6NCwiZSI6MSwiYyI6MX0", "node": { - "json": { - "new_value": "5" + "contents": { + "json": { + "new_value": "5" + } } } } @@ -305,7 +331,7 @@ Response: { } } -task 19, lines 202-213: +task 19, lines 227-240: //# run-graphql Response: { "data": { @@ -315,7 +341,7 @@ Response: { } } -task 20, lines 215-226: +task 20, lines 242-255: //# run-graphql Response: { "data": { diff --git a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/tx_digest.move b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/tx_digest.move index a41c52353bf37..32c6f14ff5995 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/tx_digest.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/tx_digest.move @@ -48,7 +48,9 @@ module Test::M1 { edges { cursor node { - json + contents { + json + } } } } @@ -61,7 +63,9 @@ module Test::M1 { edges { cursor node { - json + contents { + json + } } } } @@ -76,7 +80,9 @@ module Test::M1 { edges { cursor node { - json + contents { + json + } } } } @@ -89,7 +95,9 @@ module Test::M1 { edges { cursor node { - json + contents { + json + } } } } @@ -101,7 +109,9 @@ module Test::M1 { edges { cursor node { - json + contents { + json + } } } } @@ -114,7 +124,9 @@ module Test::M1 { edges { cursor node { - json + contents { + json + } } } } @@ -127,7 +139,9 @@ module Test::M1 { edges { cursor node { - json + contents { + json + } } } } @@ -142,7 +156,9 @@ module Test::M1 { edges { cursor node { - json + contents { + json + } } } } @@ -155,7 +171,9 @@ module Test::M1 { edges { cursor node { - json + contents { + json + } } } } @@ -167,6 +185,9 @@ module Test::M1 { edges { cursor node { + contents { + json + } json } } @@ -180,7 +201,9 @@ module Test::M1 { edges { cursor node { - json + contents { + json + } } } } @@ -193,7 +216,9 @@ module Test::M1 { edges { cursor node { - json + contents { + json + } } } } @@ -206,7 +231,9 @@ module Test::M1 { edges { cursor node { - json + contents { + json + } } } } @@ -219,7 +246,9 @@ module Test::M1 { edges { cursor node { - json + contents { + json + } } } } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/type_filter.exp b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/type_filter.exp index 3afb31848780e..6436b50366103 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/type_filter.exp +++ b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/type_filter.exp @@ -19,7 +19,7 @@ task 3, line 38: //# create-checkpoint Checkpoint created: 1 -task 4, lines 40-57: +task 4, lines 40-59: //# run-graphql Response: { "data": { @@ -29,39 +29,41 @@ Response: { "sendingModule": { "name": "M2" }, - "type": { - "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "20" - }, - "bcs": "FAAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M1::EventA" + }, + "bcs": "FAAAAAAAAAA=", + "json": { + "new_value": "20" + } + } } ] } } } -task 5, line 59: +task 5, line 61: //# run Test::M2::emit_b --sender A --args 42 events: Event { package_id: Test, transaction_module: Identifier("M2"), sender: A, type_: StructTag { address: Test, module: Identifier("M2"), name: Identifier("EventB"), type_params: [] }, contents: [42, 0, 0, 0, 0, 0, 0, 0] } mutated: object(0,0) gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 978120, non_refundable_storage_fee: 9880 -task 6, line 61: +task 6, line 63: //# run Test::M2::emit_b --sender B --args 43 events: Event { package_id: Test, transaction_module: Identifier("M2"), sender: B, type_: StructTag { address: Test, module: Identifier("M2"), name: Identifier("EventB"), type_params: [] }, contents: [43, 0, 0, 0, 0, 0, 0, 0] } mutated: object(0,1) gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 0, non_refundable_storage_fee: 0 -task 7, line 63: +task 7, line 65: //# create-checkpoint Checkpoint created: 2 -task 8, lines 65-82: +task 8, lines 67-86: //# run-graphql Response: { "data": { @@ -71,23 +73,25 @@ Response: { "sendingModule": { "name": "M2" }, - "type": { - "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "20" - }, - "bcs": "FAAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M1::EventA" + }, + "bcs": "FAAAAAAAAAA=", + "json": { + "new_value": "20" + } + } } ] } } } -task 9, lines 84-101: +task 9, lines 88-107: //# run-graphql Response: { "data": { @@ -97,23 +101,25 @@ Response: { "sendingModule": { "name": "M2" }, - "type": { - "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M2::EventB" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "42" - }, - "bcs": "KgAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M2::EventB" + }, + "bcs": "KgAAAAAAAAA=", + "json": { + "new_value": "42" + } + } } ] } } } -task 10, lines 103-120: +task 10, lines 109-128: //# run-graphql Response: { "data": { @@ -123,38 +129,42 @@ Response: { "sendingModule": { "name": "M2" }, - "type": { - "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "20" - }, - "bcs": "FAAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M1::EventA" + }, + "bcs": "FAAAAAAAAAA=", + "json": { + "new_value": "20" + } + } }, { "sendingModule": { "name": "M2" }, - "type": { - "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M2::EventB" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "42" - }, - "bcs": "KgAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M2::EventB" + }, + "bcs": "KgAAAAAAAAA=", + "json": { + "new_value": "42" + } + } } ] } } } -task 11, lines 122-139: +task 11, lines 130-149: //# run-graphql Response: { "data": { @@ -164,46 +174,52 @@ Response: { "sendingModule": { "name": "M2" }, - "type": { - "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M1::EventA" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "20" - }, - "bcs": "FAAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M1::EventA" + }, + "bcs": "FAAAAAAAAAA=", + "json": { + "new_value": "20" + } + } }, { "sendingModule": { "name": "M2" }, - "type": { - "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M2::EventB" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "new_value": "42" - }, - "bcs": "KgAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M2::EventB" + }, + "bcs": "KgAAAAAAAAA=", + "json": { + "new_value": "42" + } + } }, { "sendingModule": { "name": "M2" }, - "type": { - "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M2::EventB" - }, "sender": { "address": "0xa7b032703878aa74c3126935789fd1d4d7e111d5911b09247d6963061c312b5a" }, - "json": { - "new_value": "43" - }, - "bcs": "KwAAAAAAAAA=" + "contents": { + "type": { + "repr": "0x6edb181eb03cea19a3c4b09d2d6b5de8d0a741df186d072d18b2030eb36faee1::M2::EventB" + }, + "bcs": "KwAAAAAAAAA=", + "json": { + "new_value": "43" + } + } } ] } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/type_filter.move b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/type_filter.move index 0ca546a3a94ef..96dcb441b6df6 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/type_filter.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/type_filter.move @@ -44,14 +44,16 @@ module Test::M2 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + bcs + json + } } } } @@ -69,14 +71,16 @@ module Test::M2 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + bcs + json + } } } } @@ -88,14 +92,16 @@ module Test::M2 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + bcs + json + } } } } @@ -107,14 +113,16 @@ module Test::M2 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + bcs + json + } } } } @@ -126,14 +134,16 @@ module Test::M2 { sendingModule { name } - type { - repr - } sender { address } - json - bcs + contents { + type { + repr + } + bcs + json + } } } } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/type_param_filter.exp b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/type_param_filter.exp index 383af1455f8f3..935089505017b 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/type_param_filter.exp +++ b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/type_param_filter.exp @@ -57,61 +57,69 @@ Response: { } } -task 7, lines 49-62: +task 7, lines 49-64: //# run-graphql Response: { "data": { "events": { "nodes": [ { - "type": { - "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T1>" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "value": { - "dummy_field": false + "contents": { + "type": { + "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T1>" + }, + "json": { + "value": { + "dummy_field": false + } } } }, { - "type": { - "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T2>" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "value": { - "dummy_field": false + "contents": { + "type": { + "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T2>" + }, + "json": { + "value": { + "dummy_field": false + } } } }, { - "type": { - "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T1>" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "value": { - "dummy_field": false + "contents": { + "type": { + "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T1>" + }, + "json": { + "value": { + "dummy_field": false + } } } }, { - "type": { - "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T2>" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "value": { - "dummy_field": false + "contents": { + "type": { + "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T2>" + }, + "json": { + "value": { + "dummy_field": false + } } } } @@ -120,35 +128,39 @@ Response: { } } -task 8, lines 64-77: +task 8, lines 66-81: //# run-graphql Response: { "data": { "events": { "nodes": [ { - "type": { - "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T1>" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "value": { - "dummy_field": false + "contents": { + "type": { + "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T1>" + }, + "json": { + "value": { + "dummy_field": false + } } } }, { - "type": { - "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T1>" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "value": { - "dummy_field": false + "contents": { + "type": { + "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T1>" + }, + "json": { + "value": { + "dummy_field": false + } } } } @@ -157,22 +169,24 @@ Response: { } } -task 9, lines 79-92: +task 9, lines 83-98: //# run-graphql Response: { "data": { "events": { "nodes": [ { - "type": { - "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T2>" - }, "sender": { "address": "0xfccc9a421bbb13c1a66a1aa98f0ad75029ede94857779c6915b44f94068b921e" }, - "json": { - "value": { - "dummy_field": false + "contents": { + "type": { + "repr": "0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::EventA<0xe722de9e58a9bab3a202b769b7518f91f852460d3d2c6d6743c301d08b9e614a::M1::T2>" + }, + "json": { + "value": { + "dummy_field": false + } } } } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/type_param_filter.move b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/type_param_filter.move index 128362999b50b..93a00742dff9a 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/event_connection/type_param_filter.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/event_connection/type_param_filter.move @@ -50,13 +50,15 @@ module Test::M1 { { events(filter: {eventType: "@{Test}::M1::EventA"}) { nodes { - type { - repr - } sender { address } - json + contents { + type { + repr + } + json + } } } } @@ -65,13 +67,15 @@ module Test::M1 { { events(filter: {eventType: "@{Test}::M1::EventA<@{Test}::M1::T1>"}) { nodes { - type { - repr - } sender { address } - json + contents { + type { + repr + } + json + } } } } @@ -80,13 +84,15 @@ module Test::M1 { { events(filter: {eventType: "@{Test}::M1::EventA<@{Test}::M1::T2>", transactionDigest: "9nu1ivpL9hHcbJ9GwGfmD3Kuet5w74t2GBp8f1Ggy3UD"}) { nodes { - type { - repr - } sender { address } - json + contents { + type { + repr + } + json + } } } } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/events/sending_module.exp b/crates/sui-graphql-e2e-tests/tests/stable/events/sending_module.exp new file mode 100644 index 0000000000000..db4feb33237ab --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/stable/events/sending_module.exp @@ -0,0 +1,45 @@ +processed 6 tasks + +init: +A: object(0,0) + +task 1, lines 6-11: +//# publish --upgradeable --sender A +created: object(1,0), object(1,1) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 5327600, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2, lines 13-28: +//# upgrade --package P --upgrade-capability 1,1 --sender A +created: object(2,0) +mutated: object(0,0), object(1,1) +gas summary: computation_cost: 1000000, storage_cost: 6802000, storage_rebate: 2595780, non_refundable_storage_fee: 26220 + +task 3, line 30: +//# run P::M1::emit --sender A +events: Event { package_id: P, transaction_module: Identifier("M1"), sender: A, type_: StructTag { address: fake(1,0), module: Identifier("M0"), name: Identifier("Event"), type_params: [] }, contents: [42, 0, 0, 0, 0, 0, 0, 0] } +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 4, line 32: +//# create-checkpoint +Checkpoint created: 1 + +task 5, lines 34-44: +//# run-graphql +Response: { + "data": { + "events": { + "nodes": [ + { + "sendingModule": { + "package": { + "address": "0xf847f63b795fdbc978a23fc181ea5180f55755e446fae1f1ce7865234cac7984" + }, + "name": "M1" + } + } + ] + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/stable/events/sending_module.move b/crates/sui-graphql-e2e-tests/tests/stable/events/sending_module.move new file mode 100644 index 0000000000000..c1b1bafb593f6 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/stable/events/sending_module.move @@ -0,0 +1,44 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --protocol-version 62 --addresses P=0x0 --accounts A --simulator + +//# publish --upgradeable --sender A +module P::M0 { + public struct Event has copy, drop { + value: u64 + } +} + +//# upgrade --package P --upgrade-capability 1,1 --sender A +module P::M0 { + public struct Event has copy, drop { + value: u64 + } + + public fun emit() { + sui::event::emit(Event { value: 42 }) + } +} + +module P::M1 { + public fun emit() { + P::M0::emit() + } +} + +//# run P::M1::emit --sender A + +//# create-checkpoint + +//# run-graphql +{ + events { + nodes { + sendingModule { + package { address } + name + } + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/stable/objects/full_objects_history.exp b/crates/sui-graphql-e2e-tests/tests/stable/objects/full_objects_history.exp index 72a71621db604..023398284d7a0 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/objects/full_objects_history.exp +++ b/crates/sui-graphql-e2e-tests/tests/stable/objects/full_objects_history.exp @@ -34,7 +34,7 @@ task 7, lines 35-40: Response: { "data": { "object": { - "digest": "ig3beP5kc79atHUitXDvwpmZfH5AM5ZaKzaySVdJxL3" + "digest": "5SXEg3CCA8xgEzDmyyqtpFjrhn3m53ziW4gaDuCgdSjt" } } } @@ -44,7 +44,7 @@ task 8, lines 42-47: Response: { "data": { "object": { - "digest": "HzGF2DsFWkXbAm9bhRwDC8ams4VaxqxVYEw5zwEmVyPj" + "digest": "7VWN2PsjurHdwiHhSYsrH7RVJPmS6Lrmqw9hNcGkqPWv" } } } @@ -62,7 +62,7 @@ task 11, lines 54-59: Response: { "data": { "object": { - "digest": "ig3beP5kc79atHUitXDvwpmZfH5AM5ZaKzaySVdJxL3" + "digest": "5SXEg3CCA8xgEzDmyyqtpFjrhn3m53ziW4gaDuCgdSjt" } } } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/objects/full_objects_history.move b/crates/sui-graphql-e2e-tests/tests/stable/objects/full_objects_history.move index e2776c348bd3b..697e61845c031 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/objects/full_objects_history.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/objects/full_objects_history.move @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -//# init --addresses Test=0x0 --simulator --epochs-to-keep 1 +//# init --protocol-version 51 --addresses Test=0x0 --simulator --epochs-to-keep 1 //# publish module Test::M1 { diff --git a/crates/sui-graphql-e2e-tests/tests/stable/objects/wrap_unwrap.exp b/crates/sui-graphql-e2e-tests/tests/stable/objects/wrap_unwrap.exp new file mode 100644 index 0000000000000..0d74390873867 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/stable/objects/wrap_unwrap.exp @@ -0,0 +1,107 @@ +processed 14 tasks + +init: +A: object(0,0) + +task 1, lines 6-30: +//# publish +created: object(1,0) +mutated: object(0,1) +gas summary: computation_cost: 1000000, storage_cost: 5631600, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2, lines 32-34: +//# programmable --sender A --inputs @A +//> 0: P0::m::foo(); +//> TransferObjects([Result(0)], Input(0)) +created: object(2,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 2211600, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 3, lines 36-38: +//# programmable --sender A --inputs @A object(2,0) +//> 0: P0::m::from_foo(Input(1)); +//> TransferObjects([Result(0)], Input(0)) +created: object(3,0) +mutated: object(0,0) +wrapped: object(2,0) +gas summary: computation_cost: 1000000, storage_cost: 2454800, storage_rebate: 2189484, non_refundable_storage_fee: 22116 + +task 4, lines 40-42: +//# programmable --sender A --inputs @A object(3,0) +//> 0: P0::m::into_foo(Input(1)); +//> TransferObjects([Result(0)], Input(0)) +mutated: object(0,0) +unwrapped: object(2,0) +deleted: object(3,0) +gas summary: computation_cost: 1000000, storage_cost: 2211600, storage_rebate: 2430252, non_refundable_storage_fee: 24548 + +task 5, line 44: +//# create-checkpoint +Checkpoint created: 1 + +task 6, lines 46-54: +//# run-graphql +Response: { + "data": { + "object1": { + "digest": "4ZQVjpKKr9Et5qZNWvYQvpmydf2j7ohU1wB8bZ9LFKrG" + }, + "object2": null + } +} + +task 7, lines 56-58: +//# programmable --sender A --inputs @A +//> 0: P0::m::foo(); +//> TransferObjects([Result(0)], Input(0)) +created: object(7,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 2211600, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 8, lines 60-62: +//# programmable --sender A --inputs @A object(7,0) +//> 0: P0::m::from_foo(Input(1)); +//> TransferObjects([Result(0)], Input(0)) +created: object(8,0) +mutated: object(0,0) +wrapped: object(7,0) +gas summary: computation_cost: 1000000, storage_cost: 2454800, storage_rebate: 2189484, non_refundable_storage_fee: 22116 + +task 9, line 64: +//# create-checkpoint +Checkpoint created: 2 + +task 10, lines 66-74: +//# run-graphql +Response: { + "data": { + "object1": null, + "object2": { + "digest": "jVRpzwEAGgYKYJ5rQfpW2zjMajazCAF9VVGUXEACpN1" + } + } +} + +task 11, lines 76-78: +//# programmable --sender A --inputs @A object(8,0) +//> 0: P0::m::into_foo(Input(1)); +//> TransferObjects([Result(0)], Input(0)) +mutated: object(0,0) +unwrapped: object(7,0) +deleted: object(8,0) +gas summary: computation_cost: 1000000, storage_cost: 2211600, storage_rebate: 2430252, non_refundable_storage_fee: 24548 + +task 12, line 80: +//# create-checkpoint +Checkpoint created: 3 + +task 13, lines 82-90: +//# run-graphql +Response: { + "data": { + "object1": { + "digest": "8B8PFHkbEB8w99ZevdLkb6CBuR2gZyPsE7zUWyXJZwDw" + }, + "object2": null + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/stable/objects/wrap_unwrap.move b/crates/sui-graphql-e2e-tests/tests/stable/objects/wrap_unwrap.move new file mode 100644 index 0000000000000..903e44d8e80f9 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/stable/objects/wrap_unwrap.move @@ -0,0 +1,90 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --protocol-version 51 --addresses P0=0x0 --accounts A --simulator + +//# publish +module P0::m { + public struct Foo has key, store { + id: UID, + } + + public struct Bar has key, store { + id: UID, + foo: Foo, + } + + public fun foo(ctx: &mut TxContext): Foo { + Foo { id: object::new(ctx) } + } + + public fun from_foo(foo: Foo, ctx: &mut TxContext): Bar { + Bar { id: object::new(ctx), foo } + } + + public fun into_foo(bar: Bar): Foo { + let Bar { id, foo } = bar; + object::delete(id); + foo + } +} + +//# programmable --sender A --inputs @A +//> 0: P0::m::foo(); +//> TransferObjects([Result(0)], Input(0)) + +//# programmable --sender A --inputs @A object(2,0) +//> 0: P0::m::from_foo(Input(1)); +//> TransferObjects([Result(0)], Input(0)) + +//# programmable --sender A --inputs @A object(3,0) +//> 0: P0::m::into_foo(Input(1)); +//> TransferObjects([Result(0)], Input(0)) + +//# create-checkpoint + +//# run-graphql +{ + object1: object(address: "@{obj_2_0}") { + digest + } + object2: object(address: "@{obj_3_0}") { + digest + } +} + +//# programmable --sender A --inputs @A +//> 0: P0::m::foo(); +//> TransferObjects([Result(0)], Input(0)) + +//# programmable --sender A --inputs @A object(7,0) +//> 0: P0::m::from_foo(Input(1)); +//> TransferObjects([Result(0)], Input(0)) + +//# create-checkpoint + +//# run-graphql +{ + object1: object(address: "@{obj_7_0}") { + digest + } + object2: object(address: "@{obj_8_0}") { + digest + } +} + +//# programmable --sender A --inputs @A object(8,0) +//> 0: P0::m::into_foo(Input(1)); +//> TransferObjects([Result(0)], Input(0)) + +//# create-checkpoint + +//# run-graphql +{ + object1: object(address: "@{obj_7_0}") { + digest + } + object2: object(address: "@{obj_8_0}") { + digest + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/stable/packages/bcs.exp b/crates/sui-graphql-e2e-tests/tests/stable/packages/bcs.exp new file mode 100644 index 0000000000000..f0e668bcd3410 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/stable/packages/bcs.exp @@ -0,0 +1,23 @@ +processed 4 tasks + +task 1, lines 6-9: +//# publish +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 3442800, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2, line 11: +//# create-checkpoint +Checkpoint created: 1 + +task 3, lines 13-20: +//# run-graphql +Response: { + "data": { + "package": { + "bcs": "Adz1CN2mPNeBZVYbv5RqhGYkv1DQpl31VsxGSdfjr8viAQAAAAAAAAABAW1aoRzrCwYAAAAGAQACAwIFBQcDBwoECA4gDC4QAAEAAAABAAABAwFmAW3c9QjdpjzXgWVWG7+UaoRmJL9Q0KZd9VbMRknX46/L4gABAAAAAgYqAAAAAAAAAAIAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAQAAAAAAAAADIDoneo4Y+M2qKottIwlwrhAoW7myDDdD4qGyoq6coLMGEHUlAAAAAAA=", + "packageBcs": "3PUI3aY814FlVhu/lGqEZiS/UNCmXfVWzEZJ1+Ovy+IBAAAAAAAAAAEBbVqhHOsLBgAAAAYBAAIDAgUFBwMHCgQIDiAMLhAAAQAAAAEAAAEDAWYBbdz1CN2mPNeBZVYbv5RqhGYkv1DQpl31VsxGSdfjr8viAAEAAAACBioAAAAAAAAAAgAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIBAAAAAAAAAA==", + "moduleBcs": "AQFtWqEc6wsGAAAABgEAAgMCBQUHAwcKBAgOIAwuEAABAAAAAQAAAQMBZgFt3PUI3aY814FlVhu/lGqEZiS/UNCmXfVWzEZJ1+Ovy+IAAQAAAAIGKgAAAAAAAAACAA==" + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/stable/packages/bcs.move b/crates/sui-graphql-e2e-tests/tests/stable/packages/bcs.move new file mode 100644 index 0000000000000..1f80bd62d0dde --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/stable/packages/bcs.move @@ -0,0 +1,20 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --protocol-version 51 --addresses P=0x0 --simulator + +//# publish +module P::m { + public fun f(): u64 { 42 } +} + +//# create-checkpoint + +//# run-graphql +{ + package(address: "@{P}") { + bcs + packageBcs + moduleBcs + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transaction_block_effects/events.exp b/crates/sui-graphql-e2e-tests/tests/stable/transaction_block_effects/events.exp index a50b27ee922c0..681819a6ed0d7 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/transaction_block_effects/events.exp +++ b/crates/sui-graphql-e2e-tests/tests/stable/transaction_block_effects/events.exp @@ -1,4 +1,4 @@ -processed 10 tasks +processed 11 tasks init: A: object(0,0) @@ -31,7 +31,7 @@ task 5, line 36: //# create-checkpoint Checkpoint created: 1 -task 6, lines 38-57: +task 6, lines 38-59: //# run-graphql Response: { "data": { @@ -46,10 +46,12 @@ Response: { "sendingModule": { "name": "M1" }, - "json": { - "new_value": "1" - }, - "bcs": "AQAAAAAAAAA=" + "contents": { + "json": { + "new_value": "1" + }, + "bcs": "AQAAAAAAAAA=" + } } } ] @@ -65,10 +67,12 @@ Response: { "sendingModule": { "name": "M1" }, - "json": { - "new_value": "10" - }, - "bcs": "CgAAAAAAAAA=" + "contents": { + "json": { + "new_value": "10" + }, + "bcs": "CgAAAAAAAAA=" + } } }, { @@ -76,10 +80,12 @@ Response: { "sendingModule": { "name": "M1" }, - "json": { - "new_value": "11" - }, - "bcs": "CwAAAAAAAAA=" + "contents": { + "json": { + "new_value": "11" + }, + "bcs": "CwAAAAAAAAA=" + } } } ] @@ -95,10 +101,12 @@ Response: { "sendingModule": { "name": "M1" }, - "json": { - "new_value": "100" - }, - "bcs": "ZAAAAAAAAAA=" + "contents": { + "json": { + "new_value": "100" + }, + "bcs": "ZAAAAAAAAAA=" + } } }, { @@ -106,10 +114,12 @@ Response: { "sendingModule": { "name": "M1" }, - "json": { - "new_value": "101" - }, - "bcs": "ZQAAAAAAAAA=" + "contents": { + "json": { + "new_value": "101" + }, + "bcs": "ZQAAAAAAAAA=" + } } }, { @@ -117,10 +127,12 @@ Response: { "sendingModule": { "name": "M1" }, - "json": { - "new_value": "102" - }, - "bcs": "ZgAAAAAAAAA=" + "contents": { + "json": { + "new_value": "102" + }, + "bcs": "ZgAAAAAAAAA=" + } } } ] @@ -132,7 +144,7 @@ Response: { } } -task 7, lines 59-70: +task 7, lines 61-74: //# run-graphql Response: { "data": { @@ -142,62 +154,74 @@ Response: { "sendingModule": { "name": "M1" }, - "json": { - "new_value": "1" - }, - "bcs": "AQAAAAAAAAA=" + "contents": { + "json": { + "new_value": "1" + }, + "bcs": "AQAAAAAAAAA=" + } }, { "sendingModule": { "name": "M1" }, - "json": { - "new_value": "10" - }, - "bcs": "CgAAAAAAAAA=" + "contents": { + "json": { + "new_value": "10" + }, + "bcs": "CgAAAAAAAAA=" + } }, { "sendingModule": { "name": "M1" }, - "json": { - "new_value": "11" - }, - "bcs": "CwAAAAAAAAA=" + "contents": { + "json": { + "new_value": "11" + }, + "bcs": "CwAAAAAAAAA=" + } }, { "sendingModule": { "name": "M1" }, - "json": { - "new_value": "100" - }, - "bcs": "ZAAAAAAAAAA=" + "contents": { + "json": { + "new_value": "100" + }, + "bcs": "ZAAAAAAAAAA=" + } }, { "sendingModule": { "name": "M1" }, - "json": { - "new_value": "101" - }, - "bcs": "ZQAAAAAAAAA=" + "contents": { + "json": { + "new_value": "101" + }, + "bcs": "ZQAAAAAAAAA=" + } }, { "sendingModule": { "name": "M1" }, - "json": { - "new_value": "102" - }, - "bcs": "ZgAAAAAAAAA=" + "contents": { + "json": { + "new_value": "102" + }, + "bcs": "ZgAAAAAAAAA=" + } } ] } } } -task 8, lines 72-91: +task 8, lines 76-97: //# run-graphql Response: { "data": { @@ -212,10 +236,12 @@ Response: { "sendingModule": { "name": "M1" }, - "json": { - "new_value": "1" - }, - "bcs": "AQAAAAAAAAA=" + "contents": { + "json": { + "new_value": "1" + }, + "bcs": "AQAAAAAAAAA=" + } } } ] @@ -227,7 +253,7 @@ Response: { } } -task 9, lines 93-112: +task 9, lines 99-120: //# run-graphql --cursors {"i":0,"c":1} Response: { "data": { @@ -242,10 +268,12 @@ Response: { "sendingModule": { "name": "M1" }, - "json": { - "new_value": "101" - }, - "bcs": "ZQAAAAAAAAA=" + "contents": { + "json": { + "new_value": "101" + }, + "bcs": "ZQAAAAAAAAA=" + } } }, { @@ -253,10 +281,96 @@ Response: { "sendingModule": { "name": "M1" }, - "json": { - "new_value": "102" - }, - "bcs": "ZgAAAAAAAAA=" + "contents": { + "json": { + "new_value": "102" + }, + "bcs": "ZgAAAAAAAAA=" + } + } + } + ] + } + } + } + ] + } + } +} + +task 10, lines 122-138: +//# run-graphql +Response: { + "data": { + "transactionBlocks": { + "nodes": [ + { + "digest": "FPhSSzT7tHmrPhs3H9GT1n4Dqj3eyCgaFLkQSc9FEDVV", + "effects": { + "events": { + "nodes": [] + } + } + }, + { + "digest": "GJMTYHH46d31ohELwH3ZfvGvbiDZ7GCqNcyg4fGGFJJQ", + "effects": { + "events": { + "nodes": [] + } + } + }, + { + "digest": "48LNnMV9MFXiXfXwDRWzu6SndwxY3ZfKUNEKJiDqJqM7", + "effects": { + "events": { + "nodes": [ + { + "transactionBlock": { + "digest": "48LNnMV9MFXiXfXwDRWzu6SndwxY3ZfKUNEKJiDqJqM7" + } + } + ] + } + } + }, + { + "digest": "FhpR5hSSP2wf8ABXKcDVVTzo7H1DZYUUgABWPtADHcQA", + "effects": { + "events": { + "nodes": [ + { + "transactionBlock": { + "digest": "FhpR5hSSP2wf8ABXKcDVVTzo7H1DZYUUgABWPtADHcQA" + } + }, + { + "transactionBlock": { + "digest": "FhpR5hSSP2wf8ABXKcDVVTzo7H1DZYUUgABWPtADHcQA" + } + } + ] + } + } + }, + { + "digest": "2CdTttmE8ciZggrK8qnyjgLaDoDNzT6UCzFsn4834UWX", + "effects": { + "events": { + "nodes": [ + { + "transactionBlock": { + "digest": "2CdTttmE8ciZggrK8qnyjgLaDoDNzT6UCzFsn4834UWX" + } + }, + { + "transactionBlock": { + "digest": "2CdTttmE8ciZggrK8qnyjgLaDoDNzT6UCzFsn4834UWX" + } + }, + { + "transactionBlock": { + "digest": "2CdTttmE8ciZggrK8qnyjgLaDoDNzT6UCzFsn4834UWX" } } ] diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transaction_block_effects/events.move b/crates/sui-graphql-e2e-tests/tests/stable/transaction_block_effects/events.move index 332923f207378..5eb35f3a6e6b9 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/transaction_block_effects/events.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/transaction_block_effects/events.move @@ -37,7 +37,7 @@ module Test::M1 { //# run-graphql { - transactionBlocks(filter: { signAddress: "@{A}" }) { + transactionBlocks(filter: { sentAddress: "@{A}" }) { nodes { effects{ events { @@ -46,8 +46,10 @@ module Test::M1 { sendingModule { name } - json - bcs + contents { + json + bcs + } } } } @@ -63,15 +65,17 @@ module Test::M1 { sendingModule { name } - json - bcs + contents { + json + bcs + } } } } //# run-graphql { - transactionBlocks(first: 1, filter: { signAddress: "@{A}" }) { + transactionBlocks(first: 1, filter: { sentAddress: "@{A}" }) { nodes { effects { events(last: 1) { @@ -80,8 +84,10 @@ module Test::M1 { sendingModule { name } - json - bcs + contents { + json + bcs + } } } } @@ -92,7 +98,7 @@ module Test::M1 { //# run-graphql --cursors {"i":0,"c":1} { - transactionBlocks(last: 1, filter: { signAddress: "@{A}" }) { + transactionBlocks(last: 1, filter: { sentAddress: "@{A}" }) { nodes { effects { events(first: 2, after: "@{cursor_0}") { @@ -101,8 +107,28 @@ module Test::M1 { sendingModule { name } - json - bcs + contents { + json + bcs + } + } + } + } + } + } + } +} + +//# run-graphql +{ + transactionBlocks { + nodes { + digest + effects { + events { + nodes { + transactionBlock { + digest } } } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/affected_address.exp b/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/affected_address.exp new file mode 100644 index 0000000000000..afa4dc65590a9 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/affected_address.exp @@ -0,0 +1,749 @@ +processed 9 tasks + +init: +A: object(0,0), B: object(0,1), C: object(0,2) + +task 1, lines 26-28: +//# programmable --sender A --inputs 1000000 @B +//> SplitCoins(Gas, [Input(0)]); +//> TransferObjects([Result(0)], Input(1)) +created: object(1,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 1976000, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2, lines 30-32: +//# programmable --sponsor A --sender B --inputs 2000000 @C +//> SplitCoins(Gas, [Input(0)]); +//> TransferObjects([Result(0)], Input(1)) +created: object(2,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 1976000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 3, lines 34-36: +//# programmable --sender A --inputs 3000000 @A +//> SplitCoins(Gas, [Input(0)]); +//> TransferObjects([Result(0)], Input(1)) +created: object(3,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 1976000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 4, lines 38-40: +//# programmable --sender A --inputs 4000000 @A +//> SplitCoins(Gas, [Input(0)]); +//> TransferObjects([Result(0)], Input(1)) +created: object(4,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 1976000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 5, lines 42-43: +//# programmable --sender A --inputs @B --gas-payment 3,0 --gas-budget 3000000 +//> TransferObjects([Gas], Input(0)) +mutated: object(3,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 6, lines 45-46: +//# programmable --sponsor A --sender B --inputs @C --gas-payment 4,0 --gas-budget 4000000 +//> TransferObjects([Gas], Input(0)) +mutated: object(4,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 7, line 48: +//# create-checkpoint +Checkpoint created: 1 + +task 8, lines 50-92: +//# run-graphql +Response: { + "data": { + "affectsA": { + "nodes": [ + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": null, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "1000000" + } + } + } + }, + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "300000000000000" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999996024000" + } + } + } + } + ] + } + } + }, + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999996024000" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999992026120" + } + } + } + }, + { + "inputState": null, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "2000000" + } + } + } + } + ] + } + } + }, + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999992026120" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999987028240" + } + } + } + }, + { + "inputState": null, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "3000000" + } + } + } + } + ] + } + } + }, + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999987028240" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999981030360" + } + } + } + }, + { + "inputState": null, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "4000000" + } + } + } + } + ] + } + } + }, + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "3000000" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "1990120" + } + } + } + } + ] + } + } + }, + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "4000000" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "2990120" + } + } + } + } + ] + } + } + } + ] + }, + "affectsAViaAddress": { + "transactionBlocks": { + "nodes": [ + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": null, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "1000000" + } + } + } + }, + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "300000000000000" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999996024000" + } + } + } + } + ] + } + } + }, + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999996024000" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999992026120" + } + } + } + }, + { + "inputState": null, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "2000000" + } + } + } + } + ] + } + } + }, + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999992026120" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999987028240" + } + } + } + }, + { + "inputState": null, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "3000000" + } + } + } + } + ] + } + } + }, + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999987028240" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999981030360" + } + } + } + }, + { + "inputState": null, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "4000000" + } + } + } + } + ] + } + } + }, + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "3000000" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "1990120" + } + } + } + } + ] + } + } + }, + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "4000000" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "2990120" + } + } + } + } + ] + } + } + } + ] + } + }, + "sentByA": { + "nodes": [ + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": null, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "1000000" + } + } + } + }, + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "300000000000000" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999996024000" + } + } + } + } + ] + } + } + }, + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999992026120" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999987028240" + } + } + } + }, + { + "inputState": null, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "3000000" + } + } + } + } + ] + } + } + }, + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999987028240" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999981030360" + } + } + } + }, + { + "inputState": null, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "4000000" + } + } + } + } + ] + } + } + }, + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "3000000" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "1990120" + } + } + } + } + ] + } + } + } + ] + }, + "affectsB": { + "nodes": [ + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": null, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "1000000" + } + } + } + }, + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "300000000000000" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999996024000" + } + } + } + } + ] + } + } + }, + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999996024000" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999992026120" + } + } + } + }, + { + "inputState": null, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "2000000" + } + } + } + } + ] + } + } + }, + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "3000000" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "1990120" + } + } + } + } + ] + } + } + }, + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "4000000" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "2990120" + } + } + } + } + ] + } + } + } + ] + }, + "affectsC": { + "nodes": [ + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999996024000" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "299999992026120" + } + } + } + }, + { + "inputState": null, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "2000000" + } + } + } + } + ] + } + } + }, + { + "effects": { + "objectChanges": { + "nodes": [ + { + "inputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "4000000" + } + } + }, + "outputState": { + "asMoveObject": { + "asCoin": { + "coinBalance": "2990120" + } + } + } + } + ] + } + } + } + ] + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/affected_address.move b/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/affected_address.move new file mode 100644 index 0000000000000..22b386346369d --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/affected_address.move @@ -0,0 +1,92 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --protocol-version 51 --addresses Test=0x0 --accounts A B C --simulator + +// This test involves address A being affected by transactions in various ways: +// +// 1. A splits off a coin from its gas and sends it to B. +// +// 2. A sponsors a transaction where B splits off the a coin from the gas coin +// and sends it to C. +// +// 3. (A splits off a coin to use as gas in future transactions where the gas +// coin will be consumed). +// +// 4. (A splits off a coin to use as gas in future transactions where the gas +// coin will be consumed). +// +// 5. A sends its gas coin to B. +// +// 6. A sponsors a transaction where B sends the gas coin to C. +// +// Then we run a number of GraphQL queries to see whether various addresses are +// considered the sender, recipient or affected by a transaction. + +//# programmable --sender A --inputs 1000000 @B +//> SplitCoins(Gas, [Input(0)]); +//> TransferObjects([Result(0)], Input(1)) + +//# programmable --sponsor A --sender B --inputs 2000000 @C +//> SplitCoins(Gas, [Input(0)]); +//> TransferObjects([Result(0)], Input(1)) + +//# programmable --sender A --inputs 3000000 @A +//> SplitCoins(Gas, [Input(0)]); +//> TransferObjects([Result(0)], Input(1)) + +//# programmable --sender A --inputs 4000000 @A +//> SplitCoins(Gas, [Input(0)]); +//> TransferObjects([Result(0)], Input(1)) + +//# programmable --sender A --inputs @B --gas-payment 3,0 --gas-budget 3000000 +//> TransferObjects([Gas], Input(0)) + +//# programmable --sponsor A --sender B --inputs @C --gas-payment 4,0 --gas-budget 4000000 +//> TransferObjects([Gas], Input(0)) + +//# create-checkpoint + +//# run-graphql +{ # A should be affected by all the transactions above + affectsA: transactionBlocks(filter: { kind: PROGRAMMABLE_TX, affectedAddress: "@{A}" }, scanLimit: 1000) { + nodes { ...CoinBalances } + } + + # Same as above, but nesting the transaction block query inside an address query + affectsAViaAddress: address(address: "@{A}") { + transactionBlocks(filter: { kind: PROGRAMMABLE_TX }, relation: AFFECTED, scanLimit: 1000) { + nodes { ...CoinBalances } + } + } + + # For contrast, the transactions that A is the sender for + sentByA: transactionBlocks(filter: { kind: PROGRAMMABLE_TX, sentAddress: "@{A}" }) { + nodes { ...CoinBalances } + } + + # B was not affected by all the transactions + affectsB: transactionBlocks(filter: { kind: PROGRAMMABLE_TX, affectedAddress: "@{B}" }, scanLimit: 1000) { + nodes { ...CoinBalances } + } + + # And neither was C + affectsC: transactionBlocks(filter: { kind: PROGRAMMABLE_TX, affectedAddress: "@{C}" }, scanLimit: 1000) { + nodes { ...CoinBalances } + } +} + +fragment CoinBalances on TransactionBlock { + effects { + objectChanges { + nodes { + inputState { ...CoinBalance } + outputState { ...CoinBalance } + } + } + } +} + +fragment CoinBalance on Object { + asMoveObject { asCoin { coinBalance } } +} diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/kind.move b/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/kind.move index cc15acea59813..6b17cdd8c5dd9 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/kind.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/kind.move @@ -54,7 +54,7 @@ module Test::M1 { //# run-graphql { - transactionBlocks(first: 50 filter: {kind: PROGRAMMABLE_TX atCheckpoint: 2 signAddress: "@{A}"}) { + transactionBlocks(first: 50 filter: {kind: PROGRAMMABLE_TX atCheckpoint: 2 sentAddress: "@{A}"}) { pageInfo { hasNextPage hasPreviousPage @@ -74,7 +74,7 @@ module Test::M1 { //# run-graphql { - transactionBlocks(first: 50 filter: {kind: PROGRAMMABLE_TX atCheckpoint: 2 signAddress: "@{B}"}) { + transactionBlocks(first: 50 filter: {kind: PROGRAMMABLE_TX atCheckpoint: 2 sentAddress: "@{B}"}) { pageInfo { hasNextPage hasPreviousPage @@ -94,7 +94,7 @@ module Test::M1 { //# run-graphql { - transactionBlocks(first: 50 filter: {kind: PROGRAMMABLE_TX atCheckpoint: 2 signAddress: "@{C}"}) { + transactionBlocks(first: 50 filter: {kind: PROGRAMMABLE_TX atCheckpoint: 2 sentAddress: "@{C}"}) { pageInfo { hasNextPage hasPreviousPage @@ -114,7 +114,7 @@ module Test::M1 { //# run-graphql { - transactionBlocks(first: 50 filter: {kind: PROGRAMMABLE_TX atCheckpoint: 2 signAddress: "@{D}"}) { + transactionBlocks(first: 50 filter: {kind: PROGRAMMABLE_TX atCheckpoint: 2 sentAddress: "@{D}"}) { pageInfo { hasNextPage hasPreviousPage @@ -134,7 +134,7 @@ module Test::M1 { //# run-graphql { - transactionBlocks(first: 50 filter: {kind: PROGRAMMABLE_TX atCheckpoint: 2 signAddress: "@{E}"}) { + transactionBlocks(first: 50 filter: {kind: PROGRAMMABLE_TX atCheckpoint: 2 sentAddress: "@{E}"}) { pageInfo { hasNextPage hasPreviousPage diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/sent.exp b/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/sent.exp index 03ca9cd780032..d1bc5b1a5a98d 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/sent.exp +++ b/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/sent.exp @@ -3,7 +3,7 @@ processed 5 tasks init: A: object(0,0), B: object(0,1) -task 1, lines 9-11: +task 1, lines 6-8: //# programmable --sender A --inputs 1000000 @B //> SplitCoins(Gas, [Input(0)]); //> TransferObjects([Result(0)], Input(1)) @@ -11,7 +11,7 @@ created: object(1,0) mutated: object(0,0) gas summary: computation_cost: 1000000, storage_cost: 1976000, storage_rebate: 0, non_refundable_storage_fee: 0 -task 2, lines 13-15: +task 2, lines 10-12: //# programmable --sender B --inputs 2000000 @A //> SplitCoins(Gas, [Input(0)]); //> TransferObjects([Result(0)], Input(1)) @@ -19,11 +19,11 @@ created: object(2,0) mutated: object(0,1) gas summary: computation_cost: 1000000, storage_cost: 1976000, storage_rebate: 0, non_refundable_storage_fee: 0 -task 3, line 17: +task 3, line 14: //# create-checkpoint Checkpoint created: 1 -task 4, lines 19-87: +task 4, lines 16-46: //# run-graphql Response: { "data": { @@ -65,85 +65,6 @@ Response: { } ] }, - "bySignAddress": { - "nodes": [ - { - "effects": { - "objectChanges": { - "nodes": [ - { - "inputState": null, - "outputState": { - "asMoveObject": { - "asCoin": { - "coinBalance": "1000000" - } - } - } - }, - { - "inputState": { - "asMoveObject": { - "asCoin": { - "coinBalance": "300000000000000" - } - } - }, - "outputState": { - "asMoveObject": { - "asCoin": { - "coinBalance": "299999996024000" - } - } - } - } - ] - } - } - } - ] - }, - "bothAddresses": { - "nodes": [ - { - "effects": { - "objectChanges": { - "nodes": [ - { - "inputState": null, - "outputState": { - "asMoveObject": { - "asCoin": { - "coinBalance": "1000000" - } - } - } - }, - { - "inputState": { - "asMoveObject": { - "asCoin": { - "coinBalance": "300000000000000" - } - } - }, - "outputState": { - "asMoveObject": { - "asCoin": { - "coinBalance": "299999996024000" - } - } - } - } - ] - } - } - } - ] - }, - "differentAddresses": { - "nodes": [] - }, "compoundBySentAddress": { "nodes": [ { @@ -182,85 +103,6 @@ Response: { } ] }, - "compoundBySignAddress": { - "nodes": [ - { - "effects": { - "objectChanges": { - "nodes": [ - { - "inputState": null, - "outputState": { - "asMoveObject": { - "asCoin": { - "coinBalance": "1000000" - } - } - } - }, - { - "inputState": { - "asMoveObject": { - "asCoin": { - "coinBalance": "300000000000000" - } - } - }, - "outputState": { - "asMoveObject": { - "asCoin": { - "coinBalance": "299999996024000" - } - } - } - } - ] - } - } - } - ] - }, - "compoundBothAddresses": { - "nodes": [ - { - "effects": { - "objectChanges": { - "nodes": [ - { - "inputState": null, - "outputState": { - "asMoveObject": { - "asCoin": { - "coinBalance": "1000000" - } - } - } - }, - { - "inputState": { - "asMoveObject": { - "asCoin": { - "coinBalance": "300000000000000" - } - } - }, - "outputState": { - "asMoveObject": { - "asCoin": { - "coinBalance": "299999996024000" - } - } - } - } - ] - } - } - } - ] - }, - "compoundDifferentAddresses": { - "nodes": [] - }, "sentViaAddress": { "transactionBlocks": { "nodes": [ @@ -300,46 +142,6 @@ Response: { } ] } - }, - "signViaAddress": { - "transactionBlocks": { - "nodes": [ - { - "effects": { - "objectChanges": { - "nodes": [ - { - "inputState": null, - "outputState": { - "asMoveObject": { - "asCoin": { - "coinBalance": "1000000" - } - } - } - }, - { - "inputState": { - "asMoveObject": { - "asCoin": { - "coinBalance": "300000000000000" - } - } - }, - "outputState": { - "asMoveObject": { - "asCoin": { - "coinBalance": "299999996024000" - } - } - } - } - ] - } - } - } - ] - } } } } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/sent.move b/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/sent.move index 0f779510891a0..283d3c3a13cc2 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/sent.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/sent.move @@ -3,9 +3,6 @@ //# init --protocol-version 51 --addresses Test=0x0 --accounts A B --simulator -// Confirm that the new `sentAddress` behaves like `signAddress`, and how the -// two filters interact with each other. - //# programmable --sender A --inputs 1000000 @B //> SplitCoins(Gas, [Input(0)]); //> TransferObjects([Result(0)], Input(1)) @@ -22,53 +19,15 @@ query { nodes { ...CoinBalances } } - bySignAddress: transactionBlocks(filter: { signAddress: "@{A}" }) { - nodes { ...CoinBalances } - } - - bothAddresses: transactionBlocks(filter: { sentAddress: "@{A}", signAddress: "@{A}" }) { - nodes { ...CoinBalances } - } - - differentAddresses: transactionBlocks(filter: { sentAddress: "@{A}", signAddress: "@{B}" }) { - nodes { ...CoinBalances } - } - compoundBySentAddress: transactionBlocks(filter: { sentAddress: "@{A}", kind: PROGRAMMABLE_TX }) { nodes { ...CoinBalances } } - compoundBySignAddress: transactionBlocks(filter: { signAddress: "@{A}", kind: PROGRAMMABLE_TX }) { - nodes { ...CoinBalances } - } - - compoundBothAddresses: transactionBlocks(filter: { - sentAddress: "@{A}", - signAddress: "@{A}", - kind: PROGRAMMABLE_TX, - }) { - nodes { ...CoinBalances } - } - - compoundDifferentAddresses: transactionBlocks(filter: { - sentAddress: "@{A}", - signAddress: "@{B}", - kind: PROGRAMMABLE_TX, - }) { - nodes { ...CoinBalances } - } - sentViaAddress: address(address: "@{A}") { transactionBlocks(relation: SENT) { nodes { ...CoinBalances } } } - - signViaAddress: address(address: "@{A}") { - transactionBlocks(relation: SIGN) { - nodes { ...CoinBalances } - } - } } fragment CoinBalances on TransactionBlock { diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/transaction_ids.move b/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/transaction_ids.move index a217465798b11..690fdb2503705 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/transaction_ids.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/transactions/filters/transaction_ids.move @@ -46,7 +46,7 @@ module Test::M1 { //# run-graphql { - transactionBlocks(filter: {signAddress: "@{A}" transactionIds: []}) { + transactionBlocks(filter: {sentAddress: "@{A}" transactionIds: []}) { pageInfo { hasNextPage hasPreviousPage @@ -66,7 +66,7 @@ module Test::M1 { //# run-graphql { - transactionBlocks(filter: {recvAddress: "@{A}" transactionIds: []}) { + transactionBlocks(filter: {affectedAddress: "@{A}" transactionIds: []}) { pageInfo { hasNextPage hasPreviousPage @@ -86,7 +86,7 @@ module Test::M1 { //# run-graphql { - transactionBlocks(scanLimit: 10 filter: {recvAddress: "@{A}" transactionIds: []}) { + transactionBlocks(scanLimit: 10 filter: {affectedAddress: "@{A}" transactionIds: []}) { pageInfo { hasNextPage hasPreviousPage diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transactions/programmable.move b/crates/sui-graphql-e2e-tests/tests/stable/transactions/programmable.move index 39bba91810a74..af8c48664a9bf 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/transactions/programmable.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/transactions/programmable.move @@ -831,7 +831,7 @@ fragment ComprehensivePTB on ProgrammableTransactionBlock { //# run-graphql { # Conflicting filter and context address(address: "@{A}") { - transactionBlocks(last: 10, filter: { signAddress: "0x0" }) { + transactionBlocks(last: 10, filter: { sentAddress: "0x0" }) { nodes { kind { __typename } } } } diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/alternating.move b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/alternating.move index 8aaaba3b27c08..2c1232e6157b8 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/alternating.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/alternating.move @@ -56,7 +56,7 @@ module Test::M1 { //# run-graphql { - transactionBlocks(first: 2 scanLimit: 2 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 2 scanLimit: 2 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -82,7 +82,7 @@ module Test::M1 { # so tx 4 and 6. the boundary cursors should wrap the response set, # and both should have isScanLimited set to false { - transactionBlocks(first: 2 after: "@{cursor_0}" filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 2 after: "@{cursor_0}" filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -107,7 +107,7 @@ module Test::M1 { # Meanwhile, because of the scanLimit of 2, the boundary cursors are # startCursor: 4, endCursor: 5, and both are scan limited { - transactionBlocks(first: 2 after: "@{cursor_0}" scanLimit: 2 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 2 after: "@{cursor_0}" scanLimit: 2 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -133,7 +133,7 @@ module Test::M1 { # startCursor: 7, endCursor: 8, both scan limited # response set consists of single tx 8 { - transactionBlocks(first: 2 after: "@{cursor_0}" scanLimit: 2 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 2 after: "@{cursor_0}" scanLimit: 2 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -162,7 +162,7 @@ module Test::M1 { # consequently, the endCursor wraps the result set # startCursor: 6, endCursor: 8, endCursor is not scan limited { - transactionBlocks(first: 2 after: "@{cursor_0}" scanLimit: 6 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 2 after: "@{cursor_0}" scanLimit: 6 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -187,7 +187,7 @@ module Test::M1 { # fetch the last tx without scan limit # startCursor = endCursor = 10, wrapping the response set { - transactionBlocks(first: 2 after: "@{cursor_0}" filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 2 after: "@{cursor_0}" filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -213,7 +213,7 @@ module Test::M1 { # unlike the not-scan-limited query, the start and end cursors # are expanded out to the scanned window, instead of wrapping the response set { - transactionBlocks(first: 2 after: "@{cursor_0}" scanLimit: 6 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 2 after: "@{cursor_0}" scanLimit: 6 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/both_cursors.move b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/both_cursors.move index ab23179dbfa3e..68c126e25e24a 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/both_cursors.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/both_cursors.move @@ -59,7 +59,7 @@ module Test::M1 { # startCursor is at 3 + scanLimited, endCursor at 4 + not scanLimited # this is because between (2, 7), txs 4 and 6 match, and thus endCursor snaps to last of result { - transactionBlocks(first: 1 scanLimit: 4 after: "@{cursor_0}" before: "@{cursor_1}" filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 1 scanLimit: 4 after: "@{cursor_0}" before: "@{cursor_1}" filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -86,7 +86,7 @@ module Test::M1 { # and scan-limit logic will override this as there are still more txs to scan # note that we're scanning txs [3, 6] { - transactionBlocks(first: 3 scanLimit: 4 after: "@{cursor_0}" before: "@{cursor_1}" filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 3 scanLimit: 4 after: "@{cursor_0}" before: "@{cursor_1}" filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -111,7 +111,7 @@ module Test::M1 { # txs 5 and 7 match, but due to page size of `first: 1`, we only return tx 5 # startCursor is 5 + scan limited, endCursor is also 5 + scan limited { - transactionBlocks(first: 1 scanLimit: 3 after: "@{cursor_0}" before: "@{cursor_1}" filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 1 scanLimit: 3 after: "@{cursor_0}" before: "@{cursor_1}" filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/equal/first.move b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/equal/first.move index 997ebb0bb953f..0d6eeea40a29d 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/equal/first.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/equal/first.move @@ -60,7 +60,7 @@ module Test::M1 { # [2, 3, 4, 5, 6, 7, 8, 9, 10, 11] <- tx_sequence_number # [B, A, B, A, B, A, A, A, B, B] { - transactionBlocks(first: 50 filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 50 filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -89,7 +89,7 @@ module Test::M1 { # The cursor for the node will have `is_scan_limited` flag set to false, because we know for sure there is # a corresponding element for the cursor in the result set. { - transactionBlocks(first: 2 scanLimit: 2 filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 2 scanLimit: 2 filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -115,7 +115,7 @@ module Test::M1 { # Still paginating with `scanLimit`, both the start and end cursors should have `is_scan_limited` flag to true # because of the scanLimit of 4, startCursor = endCursor = 4 { - transactionBlocks(first: 1 scanLimit: 1 after: "@{cursor_0}" filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 1 scanLimit: 1 after: "@{cursor_0}" filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -143,7 +143,7 @@ module Test::M1 { # instead of wrapping around the result set, the boundary cursors are pushed out # to the first and last transaction scanned in this query { - transactionBlocks(first: 3 scanLimit: 3 after: "@{cursor_0}" filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 3 scanLimit: 3 after: "@{cursor_0}" filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -171,7 +171,7 @@ module Test::M1 { # instead of returninng None, we set the boundary cursors # to the first and last transaction scanned in this query { - transactionBlocks(first: 2 scanLimit: 2 after: "@{cursor_0}" filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 2 scanLimit: 2 after: "@{cursor_0}" filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -198,7 +198,7 @@ module Test::M1 { # startCursor at 10, endCursor at 11 # correctly detects we've reached the end of the upper bound { - transactionBlocks(first: 2 scanLimit: 2 after: "@{cursor_0}" filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 2 scanLimit: 2 after: "@{cursor_0}" filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -230,7 +230,7 @@ module Test::M1 { # regardless of the specified scanLimit # correctly detects we've reached the end of the upper bound { - transactionBlocks(first: 2 scanLimit: 2 after: "@{cursor_0}" filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 5}) { + transactionBlocks(first: 2 scanLimit: 2 after: "@{cursor_0}" filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 5}) { pageInfo { hasPreviousPage hasNextPage @@ -259,7 +259,7 @@ module Test::M1 { # there is a next page, which is the 12th tx, which should yield an empty set # per the filtering criteria { - transactionBlocks(last: 2 scanLimit: 2 before: "@{cursor_0}" filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 5}) { + transactionBlocks(last: 2 scanLimit: 2 before: "@{cursor_0}" filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 5}) { pageInfo { hasPreviousPage hasNextPage diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/equal/last.move b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/equal/last.move index a37c7976d1fc1..d917b925e27dc 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/equal/last.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/equal/last.move @@ -57,7 +57,7 @@ module Test::M1 { //# run-graphql # Expect ten results { - transactionBlocks(last: 50 filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(last: 50 filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -84,7 +84,7 @@ module Test::M1 { # startCursor: 10, endCursor: 11 # result is single element with cursor: 11 { - transactionBlocks(last: 2 scanLimit: 2 filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(last: 2 scanLimit: 2 filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -110,7 +110,7 @@ module Test::M1 { # startCursor: 9, endCursor: 9 # result is single element with cursor: 9 { - transactionBlocks(last: 1 scanLimit: 1 before: "@{cursor_0}" filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(last: 1 scanLimit: 1 before: "@{cursor_0}" filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -136,7 +136,7 @@ module Test::M1 { # startCursor: 6, endCursor: 8 # result is single element with cursor: 7 { - transactionBlocks(last: 3 scanLimit: 3 before: "@{cursor_0}" filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(last: 3 scanLimit: 3 before: "@{cursor_0}" filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -162,7 +162,7 @@ module Test::M1 { # startCursor: 4, endCursor: 5 # expect empty set { - transactionBlocks(last: 2 scanLimit: 2 before: "@{cursor_0}" filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(last: 2 scanLimit: 2 before: "@{cursor_0}" filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -187,7 +187,7 @@ module Test::M1 { # Returns the first two matching transactions, boundary cursors both have `is_scan_limited: true` # startCursor: 2, endCursor: 3 { - transactionBlocks(last: 2 scanLimit: 2 before: "@{cursor_0}" filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(last: 2 scanLimit: 2 before: "@{cursor_0}" filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -213,7 +213,7 @@ module Test::M1 { # Since we know from the previous query that there is not a previous page at this cursor, # Expect false for page flags and null for cursors { - transactionBlocks(last: 2 scanLimit: 2 before: "@{cursor_0}" filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(last: 2 scanLimit: 2 before: "@{cursor_0}" filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/ge_page/first.move b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/ge_page/first.move index caac3936e49f2..a6113ca543cce 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/ge_page/first.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/ge_page/first.move @@ -78,7 +78,7 @@ module Test::M1 { //# run-graphql { - transactionBlocks(filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { + transactionBlocks(filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { pageInfo { hasPreviousPage hasNextPage @@ -107,7 +107,7 @@ module Test::M1 { # this is so we don't end up skipping any other matches in the scan range # but beyond the scope of the `limit` { - transactionBlocks(first: 1 scanLimit: 5 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { + transactionBlocks(first: 1 scanLimit: 5 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { pageInfo { hasPreviousPage hasNextPage @@ -133,7 +133,7 @@ module Test::M1 { # endCursor ends at 7, not 3, because we've exhausted all the matches # within the window of scanning range, and will overwrite the endCursor to 7. { - transactionBlocks(first: 1 after: "@{cursor_0}" scanLimit: 5 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { + transactionBlocks(first: 1 after: "@{cursor_0}" scanLimit: 5 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { pageInfo { hasPreviousPage hasNextPage @@ -158,7 +158,7 @@ module Test::M1 { # startCursor: 8, endCursor: 12, both are scan-limited # expect an empty set { - transactionBlocks(first: 1 after: "@{cursor_0}" scanLimit: 5 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { + transactionBlocks(first: 1 after: "@{cursor_0}" scanLimit: 5 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { pageInfo { hasPreviousPage hasNextPage @@ -183,7 +183,7 @@ module Test::M1 { # startCursor: 13, endCursor: 17, both are scan-limited # single element returned, coincidentally also the last scanned transaction { - transactionBlocks(first: 1 after: "@{cursor_0}" scanLimit: 5 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { + transactionBlocks(first: 1 after: "@{cursor_0}" scanLimit: 5 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { pageInfo { hasPreviousPage hasNextPage @@ -210,7 +210,7 @@ module Test::M1 { # but due to the `first` limit, we return a subset. # we don't want to skip over other matches, so we don't push the endCursor out { - transactionBlocks(first: 1 after: "@{cursor_0}" scanLimit: 5 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { + transactionBlocks(first: 1 after: "@{cursor_0}" scanLimit: 5 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { pageInfo { hasPreviousPage hasNextPage @@ -236,7 +236,7 @@ module Test::M1 { # single element returned, coincidentally also the last scanned transaction # note that the startCursor is 19, not 18 or 21, since we can use the scan-limited behavior { - transactionBlocks(first: 1 after: "@{cursor_0}" scanLimit: 5 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { + transactionBlocks(first: 1 after: "@{cursor_0}" scanLimit: 5 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { pageInfo { hasPreviousPage hasNextPage diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/ge_page/last.move b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/ge_page/last.move index ddbce16557c6a..c6211042f38e3 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/ge_page/last.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/ge_page/last.move @@ -78,7 +78,7 @@ module Test::M1 { //# run-graphql { - transactionBlocks(filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { + transactionBlocks(filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { pageInfo { hasPreviousPage hasNextPage @@ -103,7 +103,7 @@ module Test::M1 { //# run-graphql # startCursor 21 not scan limited, endCursor 21 is scan limited { - transactionBlocks(last: 1 scanLimit: 5 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { + transactionBlocks(last: 1 scanLimit: 5 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { pageInfo { hasPreviousPage hasNextPage @@ -130,7 +130,7 @@ module Test::M1 { # `scanLimit` is 5 - if we set the `startCursor` to 17, then we will never yield tx 17 # when paginating the other way, since the cursors are exclusive. { - transactionBlocks(last: 1 before: "@{cursor_0}" scanLimit: 5 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { + transactionBlocks(last: 1 before: "@{cursor_0}" scanLimit: 5 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { pageInfo { hasPreviousPage hasNextPage @@ -155,7 +155,7 @@ module Test::M1 { # continuing paginating backwards with scan limit # startCursor 11, endCursor 15, both scan limited { - transactionBlocks(last: 1 before: "@{cursor_0}" scanLimit: 5 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { + transactionBlocks(last: 1 before: "@{cursor_0}" scanLimit: 5 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { pageInfo { hasPreviousPage hasNextPage @@ -179,7 +179,7 @@ module Test::M1 { //# run-graphql --cursors {"c":7,"t":11,"i":true} # startCursor is 7, endCursor is 10, both scan limited { - transactionBlocks(last: 1 before: "@{cursor_0}" scanLimit: 5 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { + transactionBlocks(last: 1 before: "@{cursor_0}" scanLimit: 5 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { pageInfo { hasPreviousPage hasNextPage @@ -205,7 +205,7 @@ module Test::M1 { # this is because we found a matching element at tx 3, but within # the scanned window there is another tx that we need to return for { - transactionBlocks(last: 1 before: "@{cursor_0}" scanLimit: 5 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { + transactionBlocks(last: 1 before: "@{cursor_0}" scanLimit: 5 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { pageInfo { hasPreviousPage hasNextPage @@ -229,7 +229,7 @@ module Test::M1 { //# run-graphql --cursors {"c":7,"t":3,"i":false} # Reached the end { - transactionBlocks(last: 1 before: "@{cursor_0}" scanLimit: 5 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { + transactionBlocks(last: 1 before: "@{cursor_0}" scanLimit: 5 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 6}) { pageInfo { hasPreviousPage hasNextPage diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/invalid_limits.move b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/invalid_limits.move index bd8e0515508a5..4be8a80e255e8 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/invalid_limits.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/invalid_limits.move @@ -44,7 +44,7 @@ module Test::M1 { //# run-graphql { - transactionBlocks(first: 0 scanLimit: 2 filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 0 scanLimit: 2 filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -67,7 +67,7 @@ module Test::M1 { //# run-graphql { - transactionBlocks(first: 2 scanLimit: 0 filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 2 scanLimit: 0 filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -90,7 +90,7 @@ module Test::M1 { //# run-graphql { - transactionBlocks(first: 0 scanLimit: 0 filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 0 scanLimit: 0 filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -113,7 +113,7 @@ module Test::M1 { //# run-graphql { - transactionBlocks(first: 0 filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 0 filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/le_page/first.move b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/le_page/first.move index c08e311648fc6..18f82f6a0d83e 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/le_page/first.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/le_page/first.move @@ -84,7 +84,7 @@ module Test::M1 { //# run-graphql # startCursor 2, endCursor 3, both scan limited { - transactionBlocks(first: 3 scanLimit: 2 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 3 scanLimit: 2 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -108,7 +108,7 @@ module Test::M1 { //# run-graphql --cursors {"c":4,"t":3,"i":true} # startCursor: 4, endCursor 5, both scan limited { - transactionBlocks(first: 3 scanLimit: 2 after: "@{cursor_0}" filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 3 scanLimit: 2 after: "@{cursor_0}" filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -136,7 +136,7 @@ module Test::M1 { # we set the endCursor to the final tx scanned, rather than snapping # to the last matched tx { - transactionBlocks(first: 4 scanLimit: 3 after: "@{cursor_0}" filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 4 scanLimit: 3 after: "@{cursor_0}" filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -160,7 +160,7 @@ module Test::M1 { //# run-graphql --cursors {"c":4,"t":8,"i":true} # startCursor: 9, endCursor: 11 both scanLimited { - transactionBlocks(first: 4 scanLimit: 3 after: "@{cursor_0}" filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 4 scanLimit: 3 after: "@{cursor_0}" filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -185,7 +185,7 @@ module Test::M1 { # using the last element's cursor from the previous query # will yield an empty set, fixed on the last scannable tx { - transactionBlocks(first: 4 scanLimit: 3 after: "@{cursor_0}" filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 4 scanLimit: 3 after: "@{cursor_0}" filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -210,7 +210,7 @@ module Test::M1 { # trying to paginate on the `endCursor` even though hasNextPage is false # cursors are null, both page flags are false { - transactionBlocks(first: 4 scanLimit: 3 after: "@{cursor_0}" filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(first: 4 scanLimit: 3 after: "@{cursor_0}" filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/le_page/last.move b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/le_page/last.move index d6ee06795a67c..c78b67522f4bd 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/le_page/last.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/le_page/last.move @@ -83,7 +83,7 @@ module Test::M1 { //# run-graphql # startCursor: 10, endCursor: 11, both scan limited { - transactionBlocks(last: 3 scanLimit: 2 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(last: 3 scanLimit: 2 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -107,7 +107,7 @@ module Test::M1 { //# run-graphql --cursors {"c":4,"t":10,"i":true} # startCursor: 8, endCursor: 9, both scan limited { - transactionBlocks(last: 3 before: "@{cursor_0}" scanLimit: 2 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(last: 3 before: "@{cursor_0}" scanLimit: 2 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -132,7 +132,7 @@ module Test::M1 { # use result's cursor instead of boundary cursor # startCursor: 6, endCursor: 7, both scan limited { - transactionBlocks(last: 3 before: "@{cursor_0}" scanLimit: 2 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(last: 3 before: "@{cursor_0}" scanLimit: 2 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -156,7 +156,7 @@ module Test::M1 { //# run-graphql --cursors {"c":4,"t":6,"i":true} # startCursor: 4, endCursor: 5, both scan limited { - transactionBlocks(last: 3 before: "@{cursor_0}" scanLimit: 2 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(last: 3 before: "@{cursor_0}" scanLimit: 2 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -180,7 +180,7 @@ module Test::M1 { //# run-graphql --cursors {"c":4,"t":4,"i":false} # reached the end with this query { - transactionBlocks(last: 3 before: "@{cursor_0}" scanLimit: 2 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(last: 3 before: "@{cursor_0}" scanLimit: 2 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage @@ -205,7 +205,7 @@ module Test::M1 { //# run-graphql --cursors {"c":4,"t":2,"i":true} # cursors are null, and page flags are both false { - transactionBlocks(last: 3 before: "@{cursor_0}" scanLimit: 2 filter: {recvAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(last: 3 before: "@{cursor_0}" scanLimit: 2 filter: {affectedAddress: "@{A}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasPreviousPage hasNextPage diff --git a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/require.move b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/require.move index a4b3e2249f3d0..c7de79724e4a1 100644 --- a/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/require.move +++ b/crates/sui-graphql-e2e-tests/tests/stable/transactions/scan_limit/require.move @@ -55,7 +55,7 @@ module Test::M1 { //# run-graphql # Expect ten results { - transactionBlocks(filter: {recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(filter: {affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasNextPage hasPreviousPage @@ -76,7 +76,7 @@ module Test::M1 { //# run-graphql # Don't need scanLimit with sender { - transactionBlocks(filter: {signAddress: "@{A}" recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { + transactionBlocks(filter: {sentAddress: "@{A}" affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4}) { pageInfo { hasNextPage hasPreviousPage @@ -97,7 +97,7 @@ module Test::M1 { //# run-graphql # scanLimit required { - transactionBlocks(filter: {signAddress: "@{A}" recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4 function: "@{Test}::M1::create"}) { + transactionBlocks(filter: {sentAddress: "@{A}" affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4 function: "@{Test}::M1::create"}) { pageInfo { hasNextPage hasPreviousPage @@ -118,7 +118,7 @@ module Test::M1 { //# run-graphql # valid { - transactionBlocks(scanLimit: 50 filter: {signAddress: "@{A}" recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4 function: "@{Test}::M1::create"}) { + transactionBlocks(scanLimit: 50 filter: {sentAddress: "@{A}" affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4 function: "@{Test}::M1::create"}) { pageInfo { hasNextPage hasPreviousPage @@ -139,7 +139,7 @@ module Test::M1 { //# run-graphql # scanLimit required { - transactionBlocks(filter: {signAddress: "@{A}" recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4 kind: PROGRAMMABLE_TX}) { + transactionBlocks(filter: {sentAddress: "@{A}" affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4 kind: PROGRAMMABLE_TX}) { pageInfo { hasNextPage hasPreviousPage @@ -160,7 +160,7 @@ module Test::M1 { //# run-graphql # valid { - transactionBlocks(scanLimit: 50 filter: {signAddress: "@{A}" recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4 kind: PROGRAMMABLE_TX}) { + transactionBlocks(scanLimit: 50 filter: {sentAddress: "@{A}" affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4 kind: PROGRAMMABLE_TX}) { pageInfo { hasNextPage hasPreviousPage @@ -181,7 +181,7 @@ module Test::M1 { //# run-graphql # scanLimit required { - transactionBlocks(filter: {signAddress: "@{A}" recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4 inputObject: "@{obj_3_0}"}) { + transactionBlocks(filter: {sentAddress: "@{A}" affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4 inputObject: "@{obj_3_0}"}) { pageInfo { hasNextPage hasPreviousPage @@ -202,7 +202,7 @@ module Test::M1 { //# run-graphql # valid { - transactionBlocks(scanLimit: 50 filter: {signAddress: "@{A}" recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4 inputObject: "@{obj_3_0}"}) { + transactionBlocks(scanLimit: 50 filter: {sentAddress: "@{A}" affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4 inputObject: "@{obj_3_0}"}) { pageInfo { hasNextPage hasPreviousPage @@ -224,7 +224,7 @@ module Test::M1 { //# run-graphql # scanLimit required { - transactionBlocks(filter: {signAddress: "@{A}" recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4 changedObject: "@{obj_3_0}"}) { + transactionBlocks(filter: {sentAddress: "@{A}" affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4 changedObject: "@{obj_3_0}"}) { pageInfo { hasNextPage hasPreviousPage @@ -247,7 +247,7 @@ module Test::M1 { # Because scanLimit is specified, the boundary cursors should be at 2 and 11, # and both will indicate is_scan_limited { - transactionBlocks(scanLimit: 50 filter: {signAddress: "@{A}" recvAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4 changedObject: "@{obj_3_0}"}) { + transactionBlocks(scanLimit: 50 filter: {sentAddress: "@{A}" affectedAddress: "@{B}" afterCheckpoint: 1 beforeCheckpoint: 4 changedObject: "@{obj_3_0}"}) { pageInfo { hasPreviousPage hasNextPage diff --git a/crates/sui-graphql-e2e-tests/tests/staging/transactions/filters/affected_object.exp b/crates/sui-graphql-e2e-tests/tests/staging/transactions/filters/affected_object.exp new file mode 100644 index 0000000000000..4e90459ba674f --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/staging/transactions/filters/affected_object.exp @@ -0,0 +1,390 @@ +processed 23 tasks + +init: +A: object(0,0) + +task 1, lines 6-56: +//# publish +created: object(1,0) +mutated: object(0,1) +gas summary: computation_cost: 1000000, storage_cost: 8147200, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 2, lines 58-60: +//# programmable --sender A --inputs 1 @A +//> P::M::new(Input(0)); +//> TransferObjects([Result(0)], Input(1)) +created: object(2,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 2295200, storage_rebate: 0, non_refundable_storage_fee: 0 + +task 3, lines 62-64: +//# programmable --sender A --inputs 2 @A +//> P::M::new(Input(0)); +//> TransferObjects([Result(0)], Input(1)) +created: object(3,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 2295200, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 4, lines 66-68: +//# programmable --sender A --inputs 3 @A +//> P::M::new(Input(0)); +//> TransferObjects([Result(0)], Input(1)) +created: object(4,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 2295200, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 5, lines 70-73: +//# programmable --sender A --inputs 4 @A +//> P::M::new(Input(0)); +//> P::M::wrap(Result(0)); +//> TransferObjects([Result(1)], Input(1)) +created: object(5,0) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 2546000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 6, lines 75-76: +//# programmable --sender A --inputs object(2,0) +//> P::M::inc(Input(0)) +mutated: object(0,0), object(2,0) +gas summary: computation_cost: 1000000, storage_cost: 2295200, storage_rebate: 2272248, non_refundable_storage_fee: 22952 + +task 7, lines 78-79: +//# programmable --sender A --inputs object(5,0) +//> P::M::incw(Input(0)) +mutated: object(0,0), object(5,0) +gas summary: computation_cost: 1000000, storage_cost: 2546000, storage_rebate: 2520540, non_refundable_storage_fee: 25460 + +task 8, lines 81-83: +//# programmable --sender A --inputs object(2,0) @A +//> P::M::wrap(Input(0)); +//> TransferObjects([Result(0)], Input(1)) +created: object(8,0) +mutated: object(0,0) +wrapped: object(2,0) +gas summary: computation_cost: 1000000, storage_cost: 2546000, storage_rebate: 2272248, non_refundable_storage_fee: 22952 + +task 9, lines 85-86: +//# programmable --sender A --inputs object(8,0) +//> P::M::incw(Input(0)) +mutated: object(0,0), object(8,0) +gas summary: computation_cost: 1000000, storage_cost: 2546000, storage_rebate: 2520540, non_refundable_storage_fee: 25460 + +task 10, lines 88-90: +//# programmable --sender A --inputs object(8,0) @A +//> P::M::unwrap(Input(0)); +//> TransferObjects([Result(0)], Input(1)) +mutated: object(0,0) +unwrapped: object(2,0) +deleted: object(8,0) +gas summary: computation_cost: 1000000, storage_cost: 2295200, storage_rebate: 2520540, non_refundable_storage_fee: 25460 + +task 11, lines 92-93: +//# programmable --sender A --inputs object(3,0) object(2,0) +//> P::M::transfer(Input(0), Input(1)) +mutated: object(0,0), object(2,0), object(3,0) +gas summary: computation_cost: 1000000, storage_cost: 3602400, storage_rebate: 3566376, non_refundable_storage_fee: 36024 + +task 12, lines 95-96: +//# programmable --sender A --inputs receiving(2,0) +//> P::M::drop_receiving(Input(0)) +mutated: object(0,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 978120, non_refundable_storage_fee: 9880 + +task 13, lines 98-100: +//# programmable --sender A --inputs object(4,0) receiving(2,0) @A +//> P::M::receive(Input(0), Input(1)); +//> TransferObjects([Result(0)], Input(2)) +Error: Transaction Effects Status: Move Runtime Abort. Location: sui::transfer::receive_impl (function index 12) at offset 0, Abort Code: 3 +Execution Error: ExecutionError: ExecutionError { inner: ExecutionErrorInner { kind: MoveAbort(MoveLocation { module: ModuleId { address: sui, name: Identifier("transfer") }, function: 12, instruction: 0, function_name: Some("receive_impl") }, 3), source: Some(VMError { major_status: ABORTED, sub_status: Some(3), message: None, exec_state: None, location: Module(ModuleId { address: sui, name: Identifier("transfer") }), indices: [], offsets: [(FunctionDefinitionIndex(12), 0)] }), command: Some(0) } } + +task 14, lines 102-104: +//# programmable --sender A --inputs object(3,0) receiving(2,0) @A +//> P::M::receive(Input(0), Input(1)); +//> TransferObjects([Result(0)], Input(2)) +mutated: object(0,0), object(2,0), object(3,0) +gas summary: computation_cost: 1000000, storage_cost: 3602400, storage_rebate: 3566376, non_refundable_storage_fee: 36024 + +task 15, lines 106-107: +//# programmable --sender A --inputs object(2,0) +//> P::M::destroy(Input(0)) +mutated: object(0,0) +deleted: object(2,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 2272248, non_refundable_storage_fee: 22952 + +task 16, lines 109-110: +//# programmable --sender A --inputs object(3,0) +//> P::M::destroy(Input(0)) +mutated: object(0,0) +deleted: object(3,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 2272248, non_refundable_storage_fee: 22952 + +task 17, lines 112-113: +//# programmable --sender A --inputs object(4,0) +//> P::M::destroy(Input(0)) +mutated: object(0,0) +deleted: object(4,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 2272248, non_refundable_storage_fee: 22952 + +task 18, lines 115-117: +//# programmable --sender A --inputs object(5,0) @A +//> P::M::unwrap(Input(0)); +//> TransferObjects([Result(0)], Input(1)) +mutated: object(0,0) +unwrapped: object(18,0) +deleted: object(5,0) +gas summary: computation_cost: 1000000, storage_cost: 2295200, storage_rebate: 2520540, non_refundable_storage_fee: 25460 + +task 19, lines 119-121: +//# programmable --sender A --inputs object(18,0) @A +//> P::M::wrap(Input(0)); +//> TransferObjects([Result(0)], Input(1)) +created: object(19,0) +mutated: object(0,0) +wrapped: object(18,0) +gas summary: computation_cost: 1000000, storage_cost: 2546000, storage_rebate: 2272248, non_refundable_storage_fee: 22952 + +task 20, lines 123-125: +//# programmable --sender A --inputs object(19,0) +//> P::M::unwrap(Input(0)); +//> P::M::destroy(Result(0)) +mutated: object(0,0) +deleted: object(19,0) +unwrapped_then_deleted: object(18,0) +gas summary: computation_cost: 1000000, storage_cost: 988000, storage_rebate: 2520540, non_refundable_storage_fee: 25460 + +task 21, line 127: +//# create-checkpoint +Checkpoint created: 1 + +task 22, lines 129-150: +//# run-graphql +Response: { + "data": { + "all": { + "nodes": [ + { + "digest": "EAu7t7RFbwASW5ZLxtqa33ody8xidNR3U3KFKBKAFU5q" + }, + { + "digest": "9Gorn5uVdjc7eHBW3WafnpiqQfYg6iPSkxhZ6CbuBqwk" + }, + { + "digest": "3qPssWeR5bvwaZiwkjbYTkR7ng9XSDp7aaky8A4DJp6S" + }, + { + "digest": "CU6apQSKPqGNqSxg2D5v3TVFzbDmx4gPhLEaJwiiWqXC" + }, + { + "digest": "5E5JyrSFLvhB2EcN74bUk4dEpm23jGP2GRnVK4JCBCTa" + }, + { + "digest": "2jmmPvyktGqZcF5KFNmNvyisGwRwkvRWqGzmoY8Mucda" + }, + { + "digest": "ELRa9MREFjzU2wuUTN8hkoUQ57FhEiuPCbvWyDEAZLRr" + }, + { + "digest": "2AdEFXvVLU8eXS5VamF1Dcn2VfqXdy46BFgiaawaMaTY" + }, + { + "digest": "4Wx6uuwb2swwLwYghbFvGyt6ReKiKX7eUsXZY16y23to" + }, + { + "digest": "D6NJc8Mg1cqXPXTdVCtVmHbxzyepNBh26K6mfUmW7pxU" + }, + { + "digest": "2DXNdmsHtk4YvcJKbb4hKSggWqRwxzXxDyVBzDAdotDZ" + }, + { + "digest": "GPSMkMRGn4X9VY41STGdDR8tFC2pneWuCtXkdjqhZiZP" + }, + { + "digest": "3bAse3nV4m79aUEq8hAVVeDuJGMs4qtyHQUJUKLoyhnQ" + }, + { + "digest": "2uiRgJ2ddzdhy32JpxBt3gajXNz1GyRr6zQjBgnnzwDW" + }, + { + "digest": "3vLFyUwCW4jC6kVhqcgtpVpMSVKxX63nNfKqRqQKW1Hg" + }, + { + "digest": "92UgLjnEiiRkxWuxME8wD3Bh439gMX7RTtRoZm3G29Lx" + }, + { + "digest": "CqYXBbBtW4nNGp66DtDuEsexmVUgasqRX1cwD2o9mojr" + }, + { + "digest": "9A8Nc4qZkEVpRSemjZ7kVJSdTzJbPGxKijPy86ZsWSK3" + }, + { + "digest": "Du8zQ2HM6GzfdZDpwobpntZfrQReEqkDyKCeBoXGeEEA" + } + ] + }, + "affect2": { + "nodes": [ + { + "digest": "EAu7t7RFbwASW5ZLxtqa33ody8xidNR3U3KFKBKAFU5q" + }, + { + "digest": "5E5JyrSFLvhB2EcN74bUk4dEpm23jGP2GRnVK4JCBCTa" + }, + { + "digest": "ELRa9MREFjzU2wuUTN8hkoUQ57FhEiuPCbvWyDEAZLRr" + }, + { + "digest": "4Wx6uuwb2swwLwYghbFvGyt6ReKiKX7eUsXZY16y23to" + }, + { + "digest": "D6NJc8Mg1cqXPXTdVCtVmHbxzyepNBh26K6mfUmW7pxU" + }, + { + "digest": "3bAse3nV4m79aUEq8hAVVeDuJGMs4qtyHQUJUKLoyhnQ" + }, + { + "digest": "2uiRgJ2ddzdhy32JpxBt3gajXNz1GyRr6zQjBgnnzwDW" + } + ] + }, + "input2": { + "nodes": [ + { + "digest": "5E5JyrSFLvhB2EcN74bUk4dEpm23jGP2GRnVK4JCBCTa" + }, + { + "digest": "ELRa9MREFjzU2wuUTN8hkoUQ57FhEiuPCbvWyDEAZLRr" + }, + { + "digest": "D6NJc8Mg1cqXPXTdVCtVmHbxzyepNBh26K6mfUmW7pxU" + }, + { + "digest": "2uiRgJ2ddzdhy32JpxBt3gajXNz1GyRr6zQjBgnnzwDW" + } + ] + }, + "change2": { + "nodes": [ + { + "digest": "EAu7t7RFbwASW5ZLxtqa33ody8xidNR3U3KFKBKAFU5q" + }, + { + "digest": "5E5JyrSFLvhB2EcN74bUk4dEpm23jGP2GRnVK4JCBCTa" + }, + { + "digest": "4Wx6uuwb2swwLwYghbFvGyt6ReKiKX7eUsXZY16y23to" + }, + { + "digest": "D6NJc8Mg1cqXPXTdVCtVmHbxzyepNBh26K6mfUmW7pxU" + }, + { + "digest": "3bAse3nV4m79aUEq8hAVVeDuJGMs4qtyHQUJUKLoyhnQ" + } + ] + }, + "affect3": { + "nodes": [ + { + "digest": "9Gorn5uVdjc7eHBW3WafnpiqQfYg6iPSkxhZ6CbuBqwk" + }, + { + "digest": "D6NJc8Mg1cqXPXTdVCtVmHbxzyepNBh26K6mfUmW7pxU" + }, + { + "digest": "3bAse3nV4m79aUEq8hAVVeDuJGMs4qtyHQUJUKLoyhnQ" + }, + { + "digest": "3vLFyUwCW4jC6kVhqcgtpVpMSVKxX63nNfKqRqQKW1Hg" + } + ] + }, + "input3": { + "nodes": [ + { + "digest": "D6NJc8Mg1cqXPXTdVCtVmHbxzyepNBh26K6mfUmW7pxU" + }, + { + "digest": "3bAse3nV4m79aUEq8hAVVeDuJGMs4qtyHQUJUKLoyhnQ" + }, + { + "digest": "3vLFyUwCW4jC6kVhqcgtpVpMSVKxX63nNfKqRqQKW1Hg" + } + ] + }, + "change3": { + "nodes": [ + { + "digest": "9Gorn5uVdjc7eHBW3WafnpiqQfYg6iPSkxhZ6CbuBqwk" + }, + { + "digest": "D6NJc8Mg1cqXPXTdVCtVmHbxzyepNBh26K6mfUmW7pxU" + }, + { + "digest": "3bAse3nV4m79aUEq8hAVVeDuJGMs4qtyHQUJUKLoyhnQ" + } + ] + }, + "affect4": { + "nodes": [ + { + "digest": "3qPssWeR5bvwaZiwkjbYTkR7ng9XSDp7aaky8A4DJp6S" + }, + { + "digest": "GPSMkMRGn4X9VY41STGdDR8tFC2pneWuCtXkdjqhZiZP" + }, + { + "digest": "92UgLjnEiiRkxWuxME8wD3Bh439gMX7RTtRoZm3G29Lx" + } + ] + }, + "input4": { + "nodes": [ + { + "digest": "GPSMkMRGn4X9VY41STGdDR8tFC2pneWuCtXkdjqhZiZP" + }, + { + "digest": "92UgLjnEiiRkxWuxME8wD3Bh439gMX7RTtRoZm3G29Lx" + } + ] + }, + "change4": { + "nodes": [ + { + "digest": "3qPssWeR5bvwaZiwkjbYTkR7ng9XSDp7aaky8A4DJp6S" + }, + { + "digest": "GPSMkMRGn4X9VY41STGdDR8tFC2pneWuCtXkdjqhZiZP" + } + ] + }, + "affect5": { + "nodes": [ + { + "digest": "CU6apQSKPqGNqSxg2D5v3TVFzbDmx4gPhLEaJwiiWqXC" + }, + { + "digest": "CqYXBbBtW4nNGp66DtDuEsexmVUgasqRX1cwD2o9mojr" + }, + { + "digest": "9A8Nc4qZkEVpRSemjZ7kVJSdTzJbPGxKijPy86ZsWSK3" + }, + { + "digest": "Du8zQ2HM6GzfdZDpwobpntZfrQReEqkDyKCeBoXGeEEA" + } + ] + }, + "input5": { + "nodes": [ + { + "digest": "9A8Nc4qZkEVpRSemjZ7kVJSdTzJbPGxKijPy86ZsWSK3" + } + ] + }, + "change5": { + "nodes": [ + { + "digest": "CqYXBbBtW4nNGp66DtDuEsexmVUgasqRX1cwD2o9mojr" + } + ] + } + } +} diff --git a/crates/sui-graphql-e2e-tests/tests/staging/transactions/filters/affected_object.move b/crates/sui-graphql-e2e-tests/tests/staging/transactions/filters/affected_object.move new file mode 100644 index 0000000000000..a6304175ba793 --- /dev/null +++ b/crates/sui-graphql-e2e-tests/tests/staging/transactions/filters/affected_object.move @@ -0,0 +1,150 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +//# init --protocol-version 51 --addresses P=0x0 --accounts A --simulator + +//# publish +module P::M { + use sui::transfer::Receiving; + + public struct Object has key, store { + id: UID, + xs: u64, + } + + public struct Wrapper has key, store { + id: UID, + obj: Object, + } + + public fun new(xs: u64, ctx: &mut TxContext): Object { + Object { id: object::new(ctx), xs } + } + + public fun inc(o: &mut Object) { + o.xs = o.xs + 1; + } + + public fun destroy(o: Object) { + let Object { id, xs: _ } = o; + id.delete(); + } + + public fun wrap(o: Object, ctx: &mut TxContext): Wrapper { + Wrapper { id: object::new(ctx), obj: o } + } + + public fun incw(w: &mut Wrapper) { + w.obj.xs = w.obj.xs + 1; + } + + public fun unwrap(w: Wrapper): Object { + let Wrapper { id, obj } = w; + id.delete(); + obj + } + + public fun receive(p: &mut Object, r: Receiving): Object { + transfer::receive(&mut p.id, r) + } + + public fun drop_receiving(_: Receiving) {} + + public fun transfer(to: &Object, from: Object) { + transfer::transfer(from, to.id.to_address()) + } +} + +//# programmable --sender A --inputs 1 @A +//> P::M::new(Input(0)); +//> TransferObjects([Result(0)], Input(1)) + +//# programmable --sender A --inputs 2 @A +//> P::M::new(Input(0)); +//> TransferObjects([Result(0)], Input(1)) + +//# programmable --sender A --inputs 3 @A +//> P::M::new(Input(0)); +//> TransferObjects([Result(0)], Input(1)) + +//# programmable --sender A --inputs 4 @A +//> P::M::new(Input(0)); +//> P::M::wrap(Result(0)); +//> TransferObjects([Result(1)], Input(1)) + +//# programmable --sender A --inputs object(2,0) +//> P::M::inc(Input(0)) + +//# programmable --sender A --inputs object(5,0) +//> P::M::incw(Input(0)) + +//# programmable --sender A --inputs object(2,0) @A +//> P::M::wrap(Input(0)); +//> TransferObjects([Result(0)], Input(1)) + +//# programmable --sender A --inputs object(8,0) +//> P::M::incw(Input(0)) + +//# programmable --sender A --inputs object(8,0) @A +//> P::M::unwrap(Input(0)); +//> TransferObjects([Result(0)], Input(1)) + +//# programmable --sender A --inputs object(3,0) object(2,0) +//> P::M::transfer(Input(0), Input(1)) + +//# programmable --sender A --inputs receiving(2,0) +//> P::M::drop_receiving(Input(0)) + +//# programmable --sender A --inputs object(4,0) receiving(2,0) @A +//> P::M::receive(Input(0), Input(1)); +//> TransferObjects([Result(0)], Input(2)) + +//# programmable --sender A --inputs object(3,0) receiving(2,0) @A +//> P::M::receive(Input(0), Input(1)); +//> TransferObjects([Result(0)], Input(2)) + +//# programmable --sender A --inputs object(2,0) +//> P::M::destroy(Input(0)) + +//# programmable --sender A --inputs object(3,0) +//> P::M::destroy(Input(0)) + +//# programmable --sender A --inputs object(4,0) +//> P::M::destroy(Input(0)) + +//# programmable --sender A --inputs object(5,0) @A +//> P::M::unwrap(Input(0)); +//> TransferObjects([Result(0)], Input(1)) + +//# programmable --sender A --inputs object(18,0) @A +//> P::M::wrap(Input(0)); +//> TransferObjects([Result(0)], Input(1)) + +//# programmable --sender A --inputs object(19,0) +//> P::M::unwrap(Input(0)); +//> P::M::destroy(Result(0)) + +//# create-checkpoint + +//# run-graphql +{ + all: transactionBlocks(last: 19) { nodes { digest } } + + affect2: transactionBlocks(last: 19, filter: { affectedObject: "@{obj_2_0}" }) { nodes { digest } } + input2: transactionBlocks(last: 19, filter: { inputObject: "@{obj_2_0}" }) { nodes { digest } } + change2: transactionBlocks(last: 19, filter: { changedObject: "@{obj_2_0}" }) { nodes { digest } } + + affect3: transactionBlocks(last: 19, filter: { affectedObject: "@{obj_3_0}" }) { nodes { digest } } + input3: transactionBlocks(last: 19, filter: { inputObject: "@{obj_3_0}" }) { nodes { digest } } + change3: transactionBlocks(last: 19, filter: { changedObject: "@{obj_3_0}" }) { nodes { digest } } + + affect4: transactionBlocks(last: 19, filter: { affectedObject: "@{obj_4_0}" }) { nodes { digest } } + input4: transactionBlocks(last: 19, filter: { inputObject: "@{obj_4_0}" }) { nodes { digest } } + change4: transactionBlocks(last: 19, filter: { changedObject: "@{obj_4_0}" }) { nodes { digest } } + + # The object that was first created at transaction 5 was unwrapped at transaction 18, so that's + # the variable we refer to it at. + affect5: transactionBlocks(last: 19, filter: { affectedObject: "@{obj_18_0}" }) { nodes { digest } } + input5: transactionBlocks(last: 19, filter: { inputObject: "@{obj_18_0}" }) { nodes { digest } } + change5: transactionBlocks(last: 19, filter: { changedObject: "@{obj_18_0}" }) { nodes { digest } } +} diff --git a/crates/sui-graphql-rpc/examples/checkpoint/with_tx_sent_addr_filter.graphql b/crates/sui-graphql-rpc/examples/checkpoint/with_tx_sent_addr_filter.graphql index 8d6ecbb50a000..8862c9d71ffdc 100644 --- a/crates/sui-graphql-rpc/examples/checkpoint/with_tx_sent_addr_filter.graphql +++ b/crates/sui-graphql-rpc/examples/checkpoint/with_tx_sent_addr_filter.graphql @@ -1,4 +1,4 @@ -# Select checkpoint at sequence number 14830285 for transactions from signAddress +# Select checkpoint at sequence number 14830285 for transactions from sentAddress { checkpoint(id: { sequenceNumber: 14830285 }) { digest @@ -6,7 +6,7 @@ timestamp transactionBlocks( filter: { - signAddress: "0x0000000000000000000000000000000000000000000000000000000000000000" + sentAddress: "0x0000000000000000000000000000000000000000000000000000000000000000" } ) { edges { diff --git a/crates/sui-graphql-rpc/examples/transaction_block_connection/recv_addr_filter.graphql b/crates/sui-graphql-rpc/examples/transaction_block_connection/affected_addr_filter.graphql similarity index 89% rename from crates/sui-graphql-rpc/examples/transaction_block_connection/recv_addr_filter.graphql rename to crates/sui-graphql-rpc/examples/transaction_block_connection/affected_addr_filter.graphql index 97c690e22651f..61e6de3dfc36b 100644 --- a/crates/sui-graphql-rpc/examples/transaction_block_connection/recv_addr_filter.graphql +++ b/crates/sui-graphql-rpc/examples/transaction_block_connection/affected_addr_filter.graphql @@ -1,4 +1,4 @@ -# Filter on recvAddress +# Filter on affected address { transactionBlocks( filter: { diff --git a/crates/sui-graphql-rpc/examples/transaction_block_connection/input_object_sign_addr_filter.graphql b/crates/sui-graphql-rpc/examples/transaction_block_connection/input_object_sent_addr_filter.graphql similarity index 86% rename from crates/sui-graphql-rpc/examples/transaction_block_connection/input_object_sign_addr_filter.graphql rename to crates/sui-graphql-rpc/examples/transaction_block_connection/input_object_sent_addr_filter.graphql index ca0bb88bea535..5cf8f6675e845 100644 --- a/crates/sui-graphql-rpc/examples/transaction_block_connection/input_object_sign_addr_filter.graphql +++ b/crates/sui-graphql-rpc/examples/transaction_block_connection/input_object_sent_addr_filter.graphql @@ -3,7 +3,7 @@ transactionBlocks( filter: { inputObject: "0x0000000000000000000000000000000000000000000000000000000000000006" - signAddress: "0x0000000000000000000000000000000000000000000000000000000000000000" + sentAddress: "0x0000000000000000000000000000000000000000000000000000000000000000" } ) { nodes { diff --git a/crates/sui-graphql-rpc/examples/transaction_block_connection/sign_addr_filter.graphql b/crates/sui-graphql-rpc/examples/transaction_block_connection/sent_addr_filter.graphql similarity index 77% rename from crates/sui-graphql-rpc/examples/transaction_block_connection/sign_addr_filter.graphql rename to crates/sui-graphql-rpc/examples/transaction_block_connection/sent_addr_filter.graphql index a15a4056e4a8b..e5692d42b351d 100644 --- a/crates/sui-graphql-rpc/examples/transaction_block_connection/sign_addr_filter.graphql +++ b/crates/sui-graphql-rpc/examples/transaction_block_connection/sent_addr_filter.graphql @@ -2,7 +2,7 @@ { transactionBlocks( filter: { - signAddress: "0x0000000000000000000000000000000000000000000000000000000000000000" + sentAddress: "0x0000000000000000000000000000000000000000000000000000000000000000" } ) { nodes { diff --git a/crates/sui-graphql-rpc/schema.graphql b/crates/sui-graphql-rpc/schema.graphql index 48e96fa24f3c7..92330bc77ffa1 100644 --- a/crates/sui-graphql-rpc/schema.graphql +++ b/crates/sui-graphql-rpc/schema.graphql @@ -163,26 +163,15 @@ type AddressOwner { The possible relationship types for a transaction block: sent, or received. """ enum AddressTransactionBlockRelationship { - """ - Transactions this address has sent. NOTE: this input filter has been deprecated in favor of - `SENT` which behaves identically but is named more clearly. Both filters restrict - transactions by their sender, only, not signers in general. - - This filter will be removed with 1.36.0 (2024-10-14). - """ - SIGN """ Transactions this address has sent. """ SENT """ - Transactions that sent objects to this address. NOTE: this input filter has been deprecated - in favor of `AFFECTED`, which offers an easier to understand behavior. - - This filter will be removed with 1.36.0 (2024-10-14), or at least one release after - `AFFECTED` is introduced, whichever is later. + Transactions that this address was involved in, either as the sender, sponsor, or as the + owner of some object that was created, modified or transfered. """ - RECV + AFFECTED } """ @@ -1200,7 +1189,42 @@ type Epoch { transactionBlocks(first: Int, after: String, last: Int, before: String, filter: TransactionBlockFilter, scanLimit: Int): TransactionBlockConnection! } +type EpochConnection { + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + """ + A list of edges. + """ + edges: [EpochEdge!]! + """ + A list of nodes. + """ + nodes: [Epoch!]! +} + +""" +An edge in a connection. +""" +type EpochEdge { + """ + The item at the end of the edge + """ + node: Epoch! + """ + A cursor for use in pagination + """ + cursor: String! +} + type Event { + """ + The transaction block that emitted this event. This information is only available for + events from indexed transactions, and not from transactions that have just been executed or + dry-run. + """ + transactionBlock: TransactionBlock """ The Move module containing some function that when called by a programmable transaction block (PTB) emitted this event. @@ -1218,32 +1242,13 @@ type Event { """ timestamp: DateTime """ - The value's Move type. + The event's contents as a Move value. """ - type: MoveType! + contents: MoveValue! """ - The BCS representation of this value, Base64 encoded. + The Base64 encoded BCS serialized bytes of the event. """ bcs: Base64! - """ - Structured contents of a Move value. - """ - data: MoveData! - """ - Representation of a Move value in JSON, where: - - - Addresses, IDs, and UIDs are represented in canonical form, as JSON strings. - - Bools are represented by JSON boolean literals. - - u8, u16, and u32 are represented as JSON numbers. - - u64, u128, and u256 are represented as JSON strings. - - Vectors are represented by JSON arrays. - - Structs are represented by JSON objects. - - Empty optional values are represented by `null`. - - This form is offered as a less verbose convenience in cases where the layout of the type is - known by the client. - """ - json: JSON! } type EventConnection { @@ -2354,6 +2359,10 @@ type MovePackage implements IObject & IOwner { """ typeOrigins: [TypeOrigin!] """ + BCS representation of the package itself, as a MovePackage. + """ + packageBcs: Base64 + """ BCS representation of the package's modules. Modules appear as a sequence of pairs (module name, followed by module bytes), in alphabetic order by module name. """ @@ -2891,11 +2900,6 @@ enum ObjectKind { The object is fetched from the index. """ INDEXED - """ - The object is deleted or wrapped and only partial information can be loaded from the - indexer. - """ - WRAPPED_OR_DELETED } """ @@ -3071,11 +3075,13 @@ type PageInfo { """ If the object's owner is a Parent, this object is part of a dynamic field (it is the value of -the dynamic field, or the intermediate Field object itself). Also note that if the owner -is a parent, then it's guaranteed to be an object. +the dynamic field, or the intermediate Field object itself), and it is owned by another object. + +Although its owner is guaranteed to be an object, it is exposed as an Owner, as the parent +object could be wrapped and therefore not directly accessible. """ type Parent { - parent: Object + parent: Owner } """ @@ -3306,6 +3312,7 @@ type Query { `0x2::sui::SUI`). If no type is provided, it will default to `0x2::sui::SUI`. """ coins(first: Int, after: String, last: Int, before: String, type: String): CoinConnection! + epochs(first: Int, after: String, last: Int, before: String): EpochConnection! """ The checkpoints that exist in the network. """ @@ -4203,7 +4210,7 @@ type TransactionBlock { """ expiration: Epoch """ - Serialized form of this transaction's `SenderSignedData`, BCS serialized and Base64 encoded. + Serialized form of this transaction's `TransactionData`, BCS serialized and Base64 encoded. """ bcs: Base64 } @@ -4326,27 +4333,15 @@ input TransactionBlockFilter { """ beforeCheckpoint: UInt53 """ - Limit to transactions that were sent by the given address. NOTE: this input filter has been - deprecated in favor of `sentAddress` which behaves identically but is named more clearly. - Both filters restrict transactions by their sender, only, not signers in general. - - This filter will be removed with 1.36.0 (2024-10-14). + Limit to transactions that interacted with the given address. The address could be a + sender, sponsor, or recipient of the transaction. """ - signAddress: SuiAddress + affectedAddress: SuiAddress """ Limit to transactions that were sent by the given address. """ sentAddress: SuiAddress """ - Limit to transactions that sent an object to the given address. NOTE: this input filter has - been deprecated in favor of `affectedAddress` which offers an easier to understand - behavior. - - This filter will be removed with 1.36.0 (2024-10-14), or at least one release after - `affectedAddress` is introduced, whichever is later. - """ - recvAddress: SuiAddress - """ Limit to transactions that accepted the given object as an input. NOTE: this input filter has been deprecated in favor of `affectedObject` which offers an easier to under behavior. diff --git a/crates/sui-graphql-rpc/src/commands.rs b/crates/sui-graphql-rpc/src/commands.rs index 4b0eca46c11cb..b71a38de99b83 100644 --- a/crates/sui-graphql-rpc/src/commands.rs +++ b/crates/sui-graphql-rpc/src/commands.rs @@ -4,6 +4,8 @@ use clap::*; use std::path::PathBuf; +use crate::config::{ConnectionConfig, Ide, TxExecFullNodeConfig}; + #[derive(Parser)] #[clap( name = "sui-graphql-rpc", @@ -21,34 +23,17 @@ pub enum Command { }, StartServer { - /// The title to display at the top of the page - #[clap(short, long)] - ide_title: Option, - /// DB URL for data fetching - #[clap(short, long)] - db_url: Option, - /// Pool size for DB connections - #[clap(long)] - db_pool_size: Option, - /// Port to bind the server to - #[clap(short, long)] - port: Option, - /// Host to bind the server to - #[clap(long)] - host: Option, - /// Port to bind the prom server to - #[clap(long)] - prom_port: Option, - /// Host to bind the prom server to - #[clap(long)] - prom_host: Option, + #[clap(flatten)] + ide: Ide, + + #[clap(flatten)] + connection: ConnectionConfig, /// Path to TOML file containing configuration for service. #[clap(short, long)] config: Option, - /// RPC url to the Node for tx execution - #[clap(long)] - node_rpc_url: Option, + #[clap(flatten)] + tx_exec_full_node: TxExecFullNodeConfig, }, } diff --git a/crates/sui-graphql-rpc/src/config.rs b/crates/sui-graphql-rpc/src/config.rs index fc3d6c348005f..cb9602fd7a0c6 100644 --- a/crates/sui-graphql-rpc/src/config.rs +++ b/crates/sui-graphql-rpc/src/config.rs @@ -13,7 +13,7 @@ use sui_graphql_config::GraphQLConfig; use sui_json_rpc::name_service::NameServiceConfig; use sui_types::base_types::{ObjectID, SuiAddress}; -pub(crate) const RPC_TIMEOUT_ERR_SLEEP_RETRY_PERIOD: Duration = Duration::from_millis(10_000); +pub(crate) const RPC_TIMEOUT_ERR_SLEEP_RETRY_PERIOD: Duration = Duration::from_millis(30_000); pub(crate) const MAX_CONCURRENT_REQUESTS: usize = 1_000; // Move Registry constants @@ -42,16 +42,30 @@ pub struct ServerConfig { /// specific connections between this service and other services, and might differ from instance to /// instance of the GraphQL service. #[GraphQLConfig] -#[derive(Clone, Eq, PartialEq)] +#[derive(clap::Args, Clone, Eq, PartialEq)] pub struct ConnectionConfig { /// Port to bind the server to + #[clap(short, long, default_value_t = ConnectionConfig::default().port)] pub port: u16, /// Host to bind the server to + #[clap(long, default_value_t = ConnectionConfig::default().host)] pub host: String, + /// DB URL for data fetching + #[clap(short, long, default_value_t = ConnectionConfig::default().db_url)] pub db_url: String, + /// Pool size for DB connections + #[clap(long, default_value_t = ConnectionConfig::default().db_pool_size)] pub db_pool_size: u32, - pub prom_url: String, + /// Host to bind the prom server to + #[clap(long, default_value_t = ConnectionConfig::default().prom_host)] + pub prom_host: String, + /// Port to bind the prom server to + #[clap(long, default_value_t = ConnectionConfig::default().prom_port)] pub prom_port: u16, + /// Skip checking whether the service is compatible with the DB it is about to connect to, on + /// start-up. + #[clap(long, default_value_t = ConnectionConfig::default().skip_migration_consistency_check)] + pub skip_migration_consistency_check: bool, } /// Configuration on features supported by the GraphQL service, passed in a TOML-based file. These @@ -172,8 +186,11 @@ impl Version { } #[GraphQLConfig] +#[derive(clap::Args)] pub struct Ide { - pub(crate) ide_title: String, + /// The title to display at the top of the web-based GraphiQL IDE. + #[clap(short, long, default_value_t = Ide::default().ide_title)] + pub ide_title: String, } #[GraphQLConfig] @@ -199,8 +216,10 @@ pub struct InternalFeatureConfig { } #[GraphQLConfig] -#[derive(Default)] +#[derive(clap::Args, Default)] pub struct TxExecFullNodeConfig { + /// RPC URL for the fullnode to send transactions to execute and dry-run. + #[clap(long)] pub(crate) node_rpc_url: Option, } @@ -337,25 +356,6 @@ impl TxExecFullNodeConfig { } impl ConnectionConfig { - pub fn new( - port: Option, - host: Option, - db_url: Option, - db_pool_size: Option, - prom_url: Option, - prom_port: Option, - ) -> Self { - let default = Self::default(); - Self { - port: port.unwrap_or(default.port), - host: host.unwrap_or(default.host), - db_url: db_url.unwrap_or(default.db_url), - db_pool_size: db_pool_size.unwrap_or(default.db_pool_size), - prom_url: prom_url.unwrap_or(default.prom_url), - prom_port: prom_port.unwrap_or(default.prom_port), - } - } - pub fn db_name(&self) -> String { self.db_url.split('/').last().unwrap().to_string() } @@ -432,14 +432,6 @@ impl Limits { } } -impl Ide { - pub fn new(ide_title: Option) -> Self { - ide_title - .map(|ide_title| Ide { ide_title }) - .unwrap_or_default() - } -} - impl BackgroundTasksConfig { pub fn test_defaults() -> Self { Self { @@ -481,8 +473,9 @@ impl Default for ConnectionConfig { host: "127.0.0.1".to_string(), db_url: "postgres://postgres:postgrespw@localhost:5432/sui_indexer".to_string(), db_pool_size: 10, - prom_url: "0.0.0.0".to_string(), + prom_host: "0.0.0.0".to_string(), prom_port: 9184, + skip_migration_consistency_check: false, } } } diff --git a/crates/sui-graphql-rpc/src/consistency.rs b/crates/sui-graphql-rpc/src/consistency.rs index 285e6ce8f36ba..1bf0650edef8c 100644 --- a/crates/sui-graphql-rpc/src/consistency.rs +++ b/crates/sui-graphql-rpc/src/consistency.rs @@ -68,10 +68,11 @@ impl ScanLimited for JsonCursor {} /// The `objects_snapshot` table contains the latest versions of objects up to a checkpoint sequence /// number, and `objects_history` captures changes after that, so a query to both tables is /// necessary to handle these object states: -/// 1) In snapshot, not in history - occurs when an object gets snapshotted and then has not been +/// 1) In snapshot, not in history - occurs when a live object gets snapshotted and then has not been /// modified since -/// 2) In history, not in snapshot - occurs when a new object is created -/// 3) In snapshot and in history - occurs when an object is snapshotted and further modified +/// 2) Not in snapshot, in history - occurs when a new object is created or a wrapped object is unwrapped +/// 3) In snapshot and in history - occurs when an object is snapshotted and further modified, the modification +/// can be wrapping or deleting. /// /// Additionally, even among objects that satisfy the filtering criteria, it is possible that there /// is a yet more recent version of the object within the checkpoint range, such as when the owner @@ -146,6 +147,7 @@ pub(crate) fn build_objects_query( // Similar to the snapshot query, construct the filtered inner query for the history table. let mut history_objs_inner = query!("SELECT * FROM objects_history"); history_objs_inner = filter_fn(history_objs_inner); + history_objs_inner = filter!(history_objs_inner, "object_status = 0"); let mut history_objs = match view { View::Consistent => { diff --git a/crates/sui-graphql-rpc/src/main.rs b/crates/sui-graphql-rpc/src/main.rs index 7dde900b1d86c..9a88a0908fd7a 100644 --- a/crates/sui-graphql-rpc/src/main.rs +++ b/crates/sui-graphql-rpc/src/main.rs @@ -6,9 +6,7 @@ use std::path::PathBuf; use clap::Parser; use sui_graphql_rpc::commands::Command; -use sui_graphql_rpc::config::{ - ConnectionConfig, Ide, ServerConfig, ServiceConfig, TxExecFullNodeConfig, Version, -}; +use sui_graphql_rpc::config::{ServerConfig, ServiceConfig, Version}; use sui_graphql_rpc::server::graphiql_server::start_graphiql_server; use tokio_util::sync::CancellationToken; use tokio_util::task::TaskTracker; @@ -51,18 +49,11 @@ async fn main() { } Command::StartServer { - ide_title, - db_url, - db_pool_size, - port, - host, + ide, + connection, config, - node_rpc_url, - prom_host, - prom_port, + tx_exec_full_node, } => { - let connection = - ConnectionConfig::new(port, host, db_url, db_pool_size, prom_host, prom_port); let service_config = service_config(config); let _guard = telemetry_subscribers::TelemetryConfig::new() .with_env() @@ -74,8 +65,8 @@ async fn main() { let server_config = ServerConfig { connection, service: service_config, - ide: Ide::new(ide_title), - tx_exec_full_node: TxExecFullNodeConfig::new(node_rpc_url), + ide, + tx_exec_full_node, ..ServerConfig::default() }; diff --git a/crates/sui-graphql-rpc/src/server/builder.rs b/crates/sui-graphql-rpc/src/server/builder.rs index ff49e6060ba95..6420a7c10f5c1 100644 --- a/crates/sui-graphql-rpc/src/server/builder.rs +++ b/crates/sui-graphql-rpc/src/server/builder.rs @@ -79,7 +79,6 @@ pub(crate) struct Server { system_package_task: SystemPackageTask, trigger_exchange_rates_task: TriggerExchangeRatesTask, state: AppState, - db_reader: Db, } impl Server { @@ -88,15 +87,6 @@ impl Server { pub async fn run(mut self) -> Result<(), Error> { get_or_init_server_start_time().await; - let mut connection = self - .db_reader - .inner - .pool() - .get() - .await - .map_err(|e| Error::Internal(e.to_string()))?; - check_db_migration_consistency(&mut connection).await?; - // A handle that spawns a background task to periodically update the `Watermark`, which // consists of the checkpoint upper bound and current epoch. let watermark_task = { @@ -342,7 +332,7 @@ impl ServerBuilder { ); let trigger_exchange_rates_task = TriggerExchangeRatesTask::new( - db_reader.clone(), + db_reader, watermark_task.epoch_receiver(), state.cancellation_token.clone(), ); @@ -364,7 +354,6 @@ impl ServerBuilder { system_package_task, trigger_exchange_rates_task, state, - db_reader, }) } @@ -378,13 +367,13 @@ impl ServerBuilder { // PROMETHEUS let prom_addr: SocketAddr = format!( "{}:{}", - config.connection.prom_url, config.connection.prom_port + config.connection.prom_host, config.connection.prom_port ) .parse() .map_err(|_| { Error::Internal(format!( "Failed to parse url {}, port {} into socket address", - config.connection.prom_url, config.connection.prom_port + config.connection.prom_host, config.connection.prom_port )) })?; @@ -424,6 +413,17 @@ impl ServerBuilder { .await .map_err(|e| Error::Internal(format!("Failed to create pg connection pool: {}", e)))?; + if !config.connection.skip_migration_consistency_check { + check_db_migration_consistency( + &mut reader + .pool() + .get() + .await + .map_err(|e| Error::Internal(e.to_string()))?, + ) + .await?; + } + // DB let db = Db::new( reader.clone(), @@ -706,8 +706,9 @@ pub mod tests { host: "127.0.0.1".to_owned(), db_url, db_pool_size: 5, - prom_url: "127.0.0.1".to_owned(), + prom_host: "127.0.0.1".to_owned(), prom_port: get_available_port(), + skip_migration_consistency_check: false, }; let service_config = service_config.unwrap_or_default(); @@ -775,7 +776,7 @@ pub mod tests { telemetry_subscribers::init_for_testing(); let cluster = start_cluster(ServiceConfig::test_defaults()).await; cluster - .wait_for_checkpoint_catchup(1, Duration::from_secs(10)) + .wait_for_checkpoint_catchup(1, Duration::from_secs(30)) .await; // timeout test includes mutation timeout, which requires a [SuiClient] to be able to run // the test, and a transaction. [WalletContext] gives access to everything that's needed. diff --git a/crates/sui-graphql-rpc/src/test_infra/cluster.rs b/crates/sui-graphql-rpc/src/test_infra/cluster.rs index 554981f19773d..27ee4f5a23b31 100644 --- a/crates/sui-graphql-rpc/src/test_infra/cluster.rs +++ b/crates/sui-graphql-rpc/src/test_infra/cluster.rs @@ -14,14 +14,13 @@ use std::path::PathBuf; use std::sync::Arc; use std::time::Duration; use sui_graphql_rpc_client::simple_client::SimpleClient; +pub use sui_indexer::config::RetentionConfig; pub use sui_indexer::config::SnapshotLagConfig; use sui_indexer::errors::IndexerError; -use sui_indexer::store::indexer_store::IndexerStore; use sui_indexer::store::PgIndexerStore; use sui_indexer::tempdb::get_available_port; use sui_indexer::tempdb::TempDb; -use sui_indexer::test_utils::start_test_indexer_impl; -use sui_indexer::test_utils::ReaderWriterConfig; +use sui_indexer::test_utils::start_indexer_writer_for_testing; use sui_swarm_config::genesis_config::{AccountConfig, DEFAULT_GAS_AMOUNT}; use sui_types::storage::RestStateReader; use tempfile::tempdir; @@ -115,8 +114,9 @@ pub async fn start_network_cluster() -> NetworkCluster { host: "127.0.0.1".to_owned(), db_url: database.database().url().as_str().to_owned(), db_pool_size: 5, - prom_url: "127.0.0.1".to_owned(), + prom_host: "127.0.0.1".to_owned(), prom_port: get_available_port(), + skip_migration_consistency_check: false, }; let data_ingestion_path = tempfile::tempdir().unwrap(); let db_url = graphql_connection_config.db_url.clone(); @@ -126,12 +126,12 @@ pub async fn start_network_cluster() -> NetworkCluster { let val_fn = start_validator_with_fullnode(data_ingestion_path.path().to_path_buf()).await; // Starts indexer - let (pg_store, pg_handle) = start_test_indexer_impl( + let (pg_store, pg_handle, _) = start_indexer_writer_for_testing( db_url, - val_fn.rpc_url().to_string(), - ReaderWriterConfig::writer_mode(None, None), + None, + None, Some(data_ingestion_path.path().to_path_buf()), - cancellation_token.clone(), + Some(cancellation_token.clone()), ) .await; @@ -151,7 +151,7 @@ pub async fn start_network_cluster() -> NetworkCluster { pub async fn serve_executor( executor: Arc, snapshot_config: Option, - epochs_to_keep: Option, + retention_config: Option, data_ingestion_path: PathBuf, ) -> ExecutorCluster { let database = TempDb::new().unwrap(); @@ -160,8 +160,9 @@ pub async fn serve_executor( host: "127.0.0.1".to_owned(), db_url: database.database().url().as_str().to_owned(), db_pool_size: 5, - prom_url: "127.0.0.1".to_owned(), + prom_host: "127.0.0.1".to_owned(), prom_port: get_available_port(), + skip_migration_consistency_check: false, }; let db_url = graphql_connection_config.db_url.clone(); // Creates a cancellation token and adds this to the ExecutorCluster, so that we can send a @@ -178,12 +179,14 @@ pub async fn serve_executor( .await; }); - let (pg_store, pg_handle) = start_test_indexer_impl( + let snapshot_config = snapshot_config.unwrap_or_default(); + + let (pg_store, pg_handle, _) = start_indexer_writer_for_testing( db_url, - format!("http://{}", executor_server_url), - ReaderWriterConfig::writer_mode(snapshot_config.clone(), epochs_to_keep), + Some(snapshot_config.clone()), + retention_config, Some(data_ingestion_path), - cancellation_token.clone(), + Some(cancellation_token.clone()), ) .await; @@ -210,7 +213,7 @@ pub async fn serve_executor( indexer_join_handle: pg_handle, graphql_server_join_handle: graphql_server_handle, graphql_client: client, - snapshot_config: snapshot_config.unwrap_or_default(), + snapshot_config, graphql_connection_config, cancellation_token, database, @@ -306,9 +309,9 @@ async fn start_validator_with_fullnode(data_ingestion_dir: PathBuf) -> TestClust .await } -/// Repeatedly ping the GraphQL server for 10s, until it responds +/// Repeatedly ping the GraphQL server for 60s, until it responds pub async fn wait_for_graphql_server(client: &SimpleClient) { - tokio::time::timeout(Duration::from_secs(10), async { + tokio::time::timeout(Duration::from_secs(60), async { while client.ping().await.is_err() { tokio::time::sleep(Duration::from_millis(500)).await; } @@ -369,6 +372,56 @@ pub async fn wait_for_graphql_checkpoint_catchup( .expect("Timeout waiting for graphql to catchup to checkpoint"); } +/// Ping the GraphQL server until its background task has updated the checkpoint watermark to the +/// desired checkpoint. +pub async fn wait_for_graphql_epoch_catchup( + client: &SimpleClient, + epoch: u64, + base_timeout: Duration, +) { + info!( + "Waiting for graphql to catchup to epoch {}, base time out is {}", + epoch, + base_timeout.as_secs() + ); + let query = r#" + { + epoch { + epochId + } + }"#; + + let timeout = base_timeout.mul_f64(epoch.max(1) as f64); + + tokio::time::timeout(timeout, async { + loop { + let resp = client + .execute_to_graphql(query.to_string(), false, vec![], vec![]) + .await + .unwrap() + .response_body_json(); + + let latest_epoch = resp["data"]["epoch"].get("epochId"); + info!("Latest epoch: {:?}", latest_epoch); + // Indexer has not picked up any epochs yet + let Some(latest_epoch) = latest_epoch else { + tokio::time::sleep(Duration::from_secs(1)).await; + continue; + }; + + // Indexer has picked up an epoch, but it's not the one we're waiting for + let latest_epoch = latest_epoch.as_u64().unwrap(); + if latest_epoch < epoch { + tokio::time::sleep(Duration::from_secs(1)).await; + } else { + break; + } + } + }) + .await + .expect("Timeout waiting for graphql to catchup to epoch"); +} + /// Ping the GraphQL server for a checkpoint until an empty response is returned, indicating that /// the checkpoint has been pruned. pub async fn wait_for_graphql_checkpoint_pruned( @@ -420,6 +473,12 @@ impl Cluster { wait_for_graphql_checkpoint_catchup(&self.graphql_client, checkpoint, base_timeout).await } + /// Waits for the indexer to index up to the given epoch, then waits for the graphql service's + /// background task to update the corresponding watermark. + pub async fn wait_for_epoch_catchup(&self, epoch: u64, base_timeout: Duration) { + wait_for_graphql_epoch_catchup(&self.graphql_client, epoch, base_timeout).await + } + /// Waits for the indexer to prune a given checkpoint. pub async fn wait_for_checkpoint_pruned(&self, checkpoint: u64, base_timeout: Duration) { wait_for_graphql_checkpoint_pruned(&self.graphql_client, checkpoint, base_timeout).await diff --git a/crates/sui-graphql-rpc/src/types/address.rs b/crates/sui-graphql-rpc/src/types/address.rs index 3d5be6cd28256..323a9138a2057 100644 --- a/crates/sui-graphql-rpc/src/types/address.rs +++ b/crates/sui-graphql-rpc/src/types/address.rs @@ -28,23 +28,10 @@ pub(crate) struct Address { /// The possible relationship types for a transaction block: sent, or received. #[derive(Enum, Copy, Clone, Eq, PartialEq)] pub(crate) enum AddressTransactionBlockRelationship { - /// Transactions this address has sent. NOTE: this input filter has been deprecated in favor of - /// `SENT` which behaves identically but is named more clearly. Both filters restrict - /// transactions by their sender, only, not signers in general. - /// - /// This filter will be removed with 1.36.0 (2024-10-14). - Sign, /// Transactions this address has sent. Sent, - /// Transactions that sent objects to this address. NOTE: this input filter has been deprecated - /// in favor of `AFFECTED`, which offers an easier to understand behavior. - /// - /// This filter will be removed with 1.36.0 (2024-10-14), or at least one release after - /// `AFFECTED` is introduced, whichever is later. - Recv, /// Transactions that this address was involved in, either as the sender, sponsor, or as the /// owner of some object that was created, modified or transfered. - #[cfg(feature = "staging")] Affected, } @@ -186,17 +173,11 @@ impl Address { let Some(filter) = filter.unwrap_or_default().intersect(match relation { // Relationship defaults to "sent" if none is supplied. - Some(R::Sign) | Some(R::Sent) | None => TransactionBlockFilter { + Some(R::Sent) | None => TransactionBlockFilter { sent_address: Some(self.address), ..Default::default() }, - Some(R::Recv) => TransactionBlockFilter { - recv_address: Some(self.address), - ..Default::default() - }, - - #[cfg(feature = "staging")] Some(R::Affected) => TransactionBlockFilter { affected_address: Some(self.address), ..Default::default() diff --git a/crates/sui-graphql-rpc/src/types/coin.rs b/crates/sui-graphql-rpc/src/types/coin.rs index fdc63dd611e81..f911256f477f4 100644 --- a/crates/sui-graphql-rpc/src/types/coin.rs +++ b/crates/sui-graphql-rpc/src/types/coin.rs @@ -174,8 +174,8 @@ impl Coin { } /// The owner type of this object: Immutable, Shared, Parent, Address - pub(crate) async fn owner(&self, ctx: &Context<'_>) -> Option { - ObjectImpl(&self.super_.super_).owner(ctx).await + pub(crate) async fn owner(&self) -> Option { + ObjectImpl(&self.super_.super_).owner().await } /// The transaction block that created this version of the object. diff --git a/crates/sui-graphql-rpc/src/types/coin_metadata.rs b/crates/sui-graphql-rpc/src/types/coin_metadata.rs index 2052c8b42b03d..d12f7f343ec77 100644 --- a/crates/sui-graphql-rpc/src/types/coin_metadata.rs +++ b/crates/sui-graphql-rpc/src/types/coin_metadata.rs @@ -162,8 +162,8 @@ impl CoinMetadata { } /// The owner type of this object: Immutable, Shared, Parent, Address - pub(crate) async fn owner(&self, ctx: &Context<'_>) -> Option { - ObjectImpl(&self.super_.super_).owner(ctx).await + pub(crate) async fn owner(&self) -> Option { + ObjectImpl(&self.super_.super_).owner().await } /// The transaction block that created this version of the object. diff --git a/crates/sui-graphql-rpc/src/types/dynamic_field.rs b/crates/sui-graphql-rpc/src/types/dynamic_field.rs index bc61eb45a498e..ed6891a0c07e8 100644 --- a/crates/sui-graphql-rpc/src/types/dynamic_field.rs +++ b/crates/sui-graphql-rpc/src/types/dynamic_field.rs @@ -4,17 +4,15 @@ use async_graphql::connection::{Connection, CursorType, Edge}; use async_graphql::*; use diesel_async::scoped_futures::ScopedFutureExt; -use move_core_types::annotated_value::{self as A, MoveStruct}; use move_core_types::language_storage::TypeTag; use sui_indexer::models::objects::StoredHistoryObject; use sui_indexer::types::OwnerType; -use sui_types::dynamic_field::{ - derive_dynamic_field_id, extract_id_value, DynamicFieldInfo, DynamicFieldType, -}; +use sui_types::dynamic_field::visitor::{Field, FieldVisitor}; +use sui_types::dynamic_field::{derive_dynamic_field_id, DynamicFieldInfo, DynamicFieldType}; use super::available_range::AvailableRange; use super::cursor::{Page, Target}; -use super::object::{self, deserialize_move_struct, Object, ObjectKind}; +use super::object::{self, Object, ObjectKind}; use super::type_filter::ExactTypeFilter; use super::{ base64::Base64, move_object::MoveObject, move_value::MoveValue, sui_address::SuiAddress, @@ -28,7 +26,6 @@ use crate::raw_query::RawQuery; pub(crate) struct DynamicField { pub super_: MoveObject, - pub df_kind: DynamicFieldType, } #[derive(Union)] @@ -61,40 +58,28 @@ impl DynamicField { /// The string type, data, and serialized value of the DynamicField's 'name' field. /// This field is used to uniquely identify a child of the parent object. async fn name(&self, ctx: &Context<'_>) -> Result> { - let resolver: &PackageResolver = ctx - .data() - .map_err(|_| Error::Internal("Unable to fetch Package Cache.".to_string())) - .extend()?; - - let (struct_tag, move_struct) = deserialize_move_struct(&self.super_.native, resolver) - .await - .extend()?; + let resolver: &PackageResolver = ctx.data_unchecked(); - // Get TypeTag of the DynamicField name from StructTag of the MoveStruct - let type_tag = DynamicFieldInfo::try_extract_field_name(&struct_tag, &self.df_kind) + let type_ = TypeTag::from(self.super_.native.type_().clone()); + let layout = resolver.type_layout(type_.clone()).await.map_err(|e| { + Error::Internal(format!( + "Error fetching layout for type {}: {e}", + type_.to_canonical_display(/* with_prefix */ true) + )) + })?; + + let Field { + name_layout, + name_bytes, + .. + } = FieldVisitor::deserialize(self.super_.native.contents(), &layout) .map_err(|e| Error::Internal(e.to_string())) .extend()?; - let name_move_value = extract_field_from_move_struct(move_struct, "name").extend()?; - - let undecorated = if self.df_kind == DynamicFieldType::DynamicObject { - let inner_name_move_value = match name_move_value { - A::MoveValue::Struct(inner_struct) => { - extract_field_from_move_struct(inner_struct, "name") - } - _ => Err(Error::Internal("Expected a wrapper struct".to_string())), - } - .extend()?; - inner_name_move_value.undecorate() - } else { - name_move_value.undecorate() - }; - - let bcs = bcs::to_bytes(&undecorated) - .map_err(|e| Error::Internal(format!("Failed to serialize object: {e}"))) - .extend()?; - - Ok(Some(MoveValue::new(type_tag, Base64::from(bcs)))) + Ok(Some(MoveValue::new( + name_layout.into(), + Base64::from(name_bytes.to_owned()), + ))) } /// The returned dynamic field is an object if its return type is `MoveObject`, @@ -104,30 +89,31 @@ impl DynamicField { async fn value(&self, ctx: &Context<'_>) -> Result> { let resolver: &PackageResolver = ctx.data_unchecked(); - // TODO(annotated-visitor): Use custom visitors to extract just the value. - let (struct_tag, move_struct) = deserialize_move_struct(&self.super_.native, resolver) - .await + let type_ = TypeTag::from(self.super_.native.type_().clone()); + let layout = resolver.type_layout(type_.clone()).await.map_err(|e| { + Error::Internal(format!( + "Error fetching layout for type {}: {e}", + type_.to_canonical_display(/* with_prefix */ true) + )) + })?; + + let Field { + kind, + value_layout, + value_bytes, + .. + } = FieldVisitor::deserialize(self.super_.native.contents(), &layout) + .map_err(|e| Error::Internal(e.to_string())) .extend()?; - let value_move_value = extract_field_from_move_struct(move_struct, "value").extend()?; - - if self.df_kind == DynamicFieldType::DynamicObject { - // If `df_kind` is a DynamicObject, the object we are currently on is the field object, - // and we must resolve one more level down to the value object. - let df_object_id = extract_id_value(&value_move_value) - .ok_or_else(|| { - Error::Internal(format!( - "Couldn't find ID of dynamic object field value: {value_move_value:#?}" - )) - }) + if kind == DynamicFieldType::DynamicObject { + let df_object_id: SuiAddress = bcs::from_bytes(value_bytes) + .map_err(|e| Error::Internal(format!("Failed to deserialize object ID: {e}"))) .extend()?; - // Because we only have checkpoint-level granularity, we may end up reading a later - // version of the value object. Thus, we use the version of the field object to bound - // the value object at the correct version. let obj = MoveObject::query( ctx, - df_object_id.into(), + df_object_id, Object::under_parent(self.root_version(), self.super_.super_.checkpoint_viewed_at), ) .await @@ -135,19 +121,9 @@ impl DynamicField { Ok(obj.map(DynamicFieldValue::MoveObject)) } else { - // Get TypeTag of the DynamicField value from StructTag of the MoveStruct - let type_tag = DynamicFieldInfo::try_extract_field_value(&struct_tag) - .map_err(|e| Error::Internal(e.to_string())) - .extend()?; - - let undecorated = value_move_value.undecorate(); - let bcs = bcs::to_bytes(&undecorated) - .map_err(|e| Error::Internal(format!("Failed to serialize object: {e}"))) - .extend()?; - Ok(Some(DynamicFieldValue::MoveValue(MoveValue::new( - type_tag, - Base64::from(bcs), + value_layout.into(), + Base64::from(value_bytes.to_owned()), )))) } } @@ -277,12 +253,6 @@ impl TryFrom for DynamicField { ObjectKind::NotIndexed(native) | ObjectKind::Indexed(native, _) => native.clone(), ObjectKind::Serialized(bytes) => bcs::from_bytes(bytes) .map_err(|e| Error::Internal(format!("Failed to deserialize object: {e}")))?, - - ObjectKind::WrappedOrDeleted => { - return Err(Error::Internal( - "DynamicField is wrapped or deleted.".to_string(), - )); - } }; let Some(object) = native.data.try_as_move() else { @@ -297,43 +267,10 @@ impl TryFrom for DynamicField { return Err(Error::Internal("Wrong type for DynamicField".to_string())); } - let Some(name) = tag.type_params.first() else { - return Err(Error::Internal("No type for DynamicField name".to_string())); - }; - - let df_kind = if matches!( - name, - TypeTag::Struct(s) if DynamicFieldInfo::is_dynamic_object_field_wrapper(s) - ) { - DynamicFieldType::DynamicObject - } else { - DynamicFieldType::DynamicField - }; - - Ok(DynamicField { - super_: stored, - df_kind, - }) + Ok(DynamicField { super_: stored }) } } -pub fn extract_field_from_move_struct( - move_struct: MoveStruct, - field_name: &str, -) -> Result { - move_struct - .fields - .into_iter() - .find_map(|(id, value)| { - if id.to_string() == field_name { - Some(value) - } else { - None - } - }) - .ok_or_else(|| Error::Internal(format!("Field '{}' not found", field_name))) -} - /// Builds the `RawQuery` for fetching dynamic fields attached to a parent object. If /// `parent_version` is null, the latest version of each field within the given checkpoint range /// [`lhs`, `rhs`] is returned, conditioned on the fact that there is not a more recent version of diff --git a/crates/sui-graphql-rpc/src/types/epoch.rs b/crates/sui-graphql-rpc/src/types/epoch.rs index 36839af4001ae..ab205f3b1c2f3 100644 --- a/crates/sui-graphql-rpc/src/types/epoch.rs +++ b/crates/sui-graphql-rpc/src/types/epoch.rs @@ -4,14 +4,15 @@ use std::collections::{BTreeMap, BTreeSet, HashMap}; use crate::connection::ScanConnection; +use crate::consistency::Checkpointed; use crate::context_data::db_data_provider::{convert_to_validators, PgManager}; -use crate::data::{DataLoader, Db, DbConnection, QueryExecutor}; +use crate::data::{self, DataLoader, Db, DbConnection, QueryExecutor}; use crate::error::Error; use crate::server::watermark_task::Watermark; use super::big_int::BigInt; use super::checkpoint::{self, Checkpoint}; -use super::cursor::Page; +use super::cursor::{self, Page, Paginated, ScanLimited, Target}; use super::date_time::DateTime; use super::protocol_config::ProtocolConfigs; use super::system_state_summary::SystemStateSummary; @@ -21,9 +22,11 @@ use super::validator_set::ValidatorSet; use async_graphql::connection::Connection; use async_graphql::dataloader::Loader; use async_graphql::*; +use connection::{CursorType, Edge}; use diesel::{ExpressionMethods, OptionalExtension, QueryDsl, SelectableHelper}; use diesel_async::scoped_futures::ScopedFutureExt; use fastcrypto::encoding::{Base58, Encoding}; +use serde::{Deserialize, Serialize}; use sui_indexer::models::epoch::QueryableEpochInfo; use sui_indexer::schema::epochs; use sui_types::messages_checkpoint::CheckpointCommitment as EpochCommitment; @@ -42,6 +45,21 @@ struct EpochKey { pub checkpoint_viewed_at: u64, } +pub(crate) type Cursor = cursor::JsonCursor; +type Query = data::Query; + +/// The cursor returned for each `Epoch` in a connection's page of results. The +/// `checkpoint_viewed_at` will set the consistent upper bound for subsequent queries made on this +/// cursor. +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] +pub(crate) struct EpochCursor { + /// The checkpoint sequence number this was viewed at. + #[serde(rename = "c")] + pub checkpoint_viewed_at: u64, + #[serde(rename = "e")] + pub epoch_id: u64, +} + /// Operation of the Sui network is temporally partitioned into non-overlapping epochs, /// and the network aims to keep epochs roughly the same duration as each other. /// During a particular epoch the following data is fixed: @@ -342,8 +360,90 @@ impl Epoch { checkpoint_viewed_at, })) } + + pub(crate) async fn paginate( + db: &Db, + page: Page, + checkpoint_viewed_at: u64, + ) -> Result, Error> { + use epochs::dsl; + let cursor_viewed_at = page.validate_cursor_consistency()?; + let checkpoint_viewed_at = cursor_viewed_at.unwrap_or(checkpoint_viewed_at); + + let (prev, next, results) = db + .execute(move |conn| { + async move { + page.paginate_query::( + conn, + checkpoint_viewed_at, + move || { + dsl::epochs + .select(QueryableEpochInfo::as_select()) + .filter(dsl::first_checkpoint_id.le(checkpoint_viewed_at as i64)) + .into_boxed() + }, + ) + .await + } + .scope_boxed() + }) + .await?; + + // The "checkpoint viewed at" sets a consistent upper bound for the nested queries. + let mut conn = Connection::new(prev, next); + for stored in results { + let cursor = stored.cursor(checkpoint_viewed_at).encode_cursor(); + conn.edges.push(Edge::new( + cursor, + Epoch { + stored, + checkpoint_viewed_at, + }, + )); + } + + Ok(conn) + } } +impl Paginated for QueryableEpochInfo { + type Source = epochs::table; + + fn filter_ge(cursor: &Cursor, query: Query) -> Query { + query.filter(epochs::dsl::epoch.ge(cursor.epoch_id as i64)) + } + + fn filter_le(cursor: &Cursor, query: Query) -> Query { + query.filter(epochs::dsl::epoch.le(cursor.epoch_id as i64)) + } + + fn order(asc: bool, query: Query) -> Query { + use epochs::dsl; + if asc { + query.order(dsl::epoch) + } else { + query.order(dsl::epoch.desc()) + } + } +} + +impl Target for QueryableEpochInfo { + fn cursor(&self, checkpoint_viewed_at: u64) -> Cursor { + Cursor::new(EpochCursor { + checkpoint_viewed_at, + epoch_id: self.epoch as u64, + }) + } +} + +impl Checkpointed for Cursor { + fn checkpoint_viewed_at(&self) -> u64 { + self.checkpoint_viewed_at + } +} + +impl ScanLimited for Cursor {} + #[async_trait::async_trait] impl Loader for Db { type Value = Epoch; diff --git a/crates/sui-graphql-rpc/src/types/event/mod.rs b/crates/sui-graphql-rpc/src/types/event/mod.rs index e5eee0f241d4d..d5efd9fb259cb 100644 --- a/crates/sui-graphql-rpc/src/types/event/mod.rs +++ b/crates/sui-graphql-rpc/src/types/event/mod.rs @@ -6,7 +6,7 @@ use std::str::FromStr; use super::cursor::{Page, Target}; use super::{ address::Address, base64::Base64, date_time::DateTime, move_module::MoveModule, - move_value::MoveValue, + move_value::MoveValue, transaction_block::TransactionBlock, }; use crate::data::{self, DbConnection, QueryExecutor}; use crate::query; @@ -50,6 +50,22 @@ type Query = data::Query; #[Object] impl Event { + /// The transaction block that emitted this event. This information is only available for + /// events from indexed transactions, and not from transactions that have just been executed or + /// dry-run. + async fn transaction_block(&self, ctx: &Context<'_>) -> Result> { + let Some(stored) = &self.stored else { + return Ok(None); + }; + + TransactionBlock::query( + ctx, + TransactionBlock::by_seq(stored.tx_sequence_number as u64, self.checkpoint_viewed_at), + ) + .await + .extend() + } + /// The Move module containing some function that when called by /// a programmable transaction block (PTB) emitted this event. /// For example, if a PTB invokes A::m1::foo, which internally @@ -87,13 +103,20 @@ impl Event { } } - #[graphql(flatten)] - async fn move_value(&self) -> Result { + /// The event's contents as a Move value. + async fn contents(&self) -> Result { Ok(MoveValue::new( self.native.type_.clone().into(), Base64::from(self.native.contents.clone()), )) } + + /// The Base64 encoded BCS serialized bytes of the event. + async fn bcs(&self) -> Result { + Ok(Base64::from( + bcs::to_bytes(&self.native).map_err(|e| Error::Internal(e.to_string()))?, + )) + } } impl Event { @@ -240,6 +263,7 @@ impl Event { .to_canonical_string(/* with_prefix */ true), bcs: native_event.contents.clone(), timestamp_ms: stored_tx.timestamp_ms, + sender: Some(native_event.sender.to_vec()), }; Ok(Self { diff --git a/crates/sui-graphql-rpc/src/types/move_object.rs b/crates/sui-graphql-rpc/src/types/move_object.rs index 5cb47571a8ab9..b78ef3dac54ec 100644 --- a/crates/sui-graphql-rpc/src/types/move_object.rs +++ b/crates/sui-graphql-rpc/src/types/move_object.rs @@ -242,8 +242,8 @@ impl MoveObject { } /// The owner type of this object: Immutable, Shared, Parent, Address - pub(crate) async fn owner(&self, ctx: &Context<'_>) -> Option { - ObjectImpl(&self.super_).owner(ctx).await + pub(crate) async fn owner(&self) -> Option { + ObjectImpl(&self.super_).owner().await } /// The transaction block that created this version of the object. diff --git a/crates/sui-graphql-rpc/src/types/move_package.rs b/crates/sui-graphql-rpc/src/types/move_package.rs index 6a8b0573cb154..ff4762f529a79 100644 --- a/crates/sui-graphql-rpc/src/types/move_package.rs +++ b/crates/sui-graphql-rpc/src/types/move_package.rs @@ -32,7 +32,7 @@ use diesel::prelude::QueryableByName; use diesel::{BoolExpressionMethods, ExpressionMethods, JoinOnDsl, QueryDsl, Selectable}; use diesel_async::scoped_futures::ScopedFutureExt; use serde::{Deserialize, Serialize}; -use sui_indexer::models::objects::StoredHistoryObject; +use sui_indexer::models::objects::StoredFullHistoryObject; use sui_indexer::schema::packages; use sui_package_resolver::{error::Error as PackageCacheError, Package as ParsedMovePackage}; use sui_types::is_system_package; @@ -123,9 +123,10 @@ struct TypeOrigin { #[derive(Selectable, QueryableByName)] #[diesel(table_name = packages)] struct StoredHistoryPackage { + checkpoint_sequence_number: i64, original_id: Vec, #[diesel(embed)] - object: StoredHistoryObject, + object: StoredFullHistoryObject, } pub(crate) struct MovePackageDowncastError; @@ -136,6 +137,10 @@ pub(crate) type Cursor = BcsCursor; /// The inner struct for the `MovePackage` cursor. The package is identified by the checkpoint it /// was created in, its original ID, and its version, and the `checkpoint_viewed_at` specifies the /// checkpoint snapshot that the data came from. +/// +/// The cursor includes the checkpoint the package was created in as well, so that when we paginate +/// through all the packages on-chain, if we pause half way through, we can pick back up based on +/// the checkpoint we've seen so far. #[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Debug)] pub(crate) struct PackageCursor { pub checkpoint_sequence_number: u64, @@ -308,8 +313,8 @@ impl MovePackage { /// The owner type of this object: Immutable, Shared, Parent, Address /// Packages are always Immutable. - pub(crate) async fn owner(&self, ctx: &Context<'_>) -> Option { - ObjectImpl(&self.super_).owner(ctx).await + pub(crate) async fn owner(&self) -> Option { + ObjectImpl(&self.super_).owner().await } /// The transaction block that published or upgraded this package. @@ -543,6 +548,17 @@ impl MovePackage { Some(type_origins) } + /// BCS representation of the package itself, as a MovePackage. + async fn package_bcs(&self) -> Result> { + let bcs = bcs::to_bytes(&self.native) + .map_err(|_| { + Error::Internal(format!("Failed to serialize package {}", self.native.id())) + }) + .extend()?; + + Ok(Some(bcs.into())) + } + /// BCS representation of the package's modules. Modules appear as a sequence of pairs (module /// name, followed by module bytes), in alphabetic order by module name. async fn module_bcs(&self) -> Result> { @@ -711,26 +727,26 @@ impl MovePackage { async move { let mut q = query!( r#" - SELECT - p.original_id, - o.* - FROM - packages p - INNER JOIN - objects_history o - ON - p.package_id = o.object_id - AND p.package_version = o.object_version - AND p.checkpoint_sequence_number = o.checkpoint_sequence_number - "# + SELECT + p.checkpoint_sequence_number, + p.original_id, + o.* + FROM + packages p + INNER JOIN + full_objects_history o + ON + p.package_id = o.object_id + AND p.package_version = o.object_version + "# ); q = filter!( q, - format!("o.checkpoint_sequence_number < {before_checkpoint}") + format!("p.checkpoint_sequence_number < {before_checkpoint}") ); if let Some(after) = after_checkpoint { - q = filter!(q, format!("{after} < o.checkpoint_sequence_number")); + q = filter!(q, format!("{after} < p.checkpoint_sequence_number")); } page.paginate_raw_query::(conn, checkpoint_viewed_at, q) @@ -745,8 +761,7 @@ impl MovePackage { // The "checkpoint viewed at" sets a consistent upper bound for the nested queries. for stored in results { let cursor = stored.cursor(checkpoint_viewed_at).encode_cursor(); - let package = - MovePackage::try_from_stored_history_object(stored.object, checkpoint_viewed_at)?; + let package = MovePackage::try_from_serialized(stored.object, checkpoint_viewed_at)?; conn.edges.push(Edge::new(cursor, package)); } @@ -798,8 +813,7 @@ impl MovePackage { // The "checkpoint viewed at" sets a consistent upper bound for the nested queries. for stored in results { let cursor = stored.cursor(checkpoint_viewed_at).encode_cursor(); - let package = - MovePackage::try_from_stored_history_object(stored.object, checkpoint_viewed_at)?; + let package = MovePackage::try_from_serialized(stored.object, checkpoint_viewed_at)?; conn.edges.push(Edge::new(cursor, package)); } @@ -809,15 +823,20 @@ impl MovePackage { /// `checkpoint_viewed_at` points to the checkpoint snapshot that this `MovePackage` came from. /// This is stored in the `MovePackage` so that related fields from the package are read from /// the same checkpoint (consistently). - pub(crate) fn try_from_stored_history_object( - history_object: StoredHistoryObject, + pub(crate) fn try_from_serialized( + history_object: StoredFullHistoryObject, checkpoint_viewed_at: u64, ) -> Result { - let object = Object::try_from_stored_history_object( - history_object, + let object = Object::new_serialized( + SuiAddress::from_bytes(&history_object.object_id) + .map_err(|_| Error::Internal("Invalid package ID".to_string()))?, + history_object.object_version as u64, + history_object.serialized_object, checkpoint_viewed_at, - /* root_version */ None, - )?; + history_object.object_version as u64, + ) + .ok_or_else(|| Error::Internal("Not a package!".to_string()))?; + Self::try_from(&object).map_err(|_| Error::Internal("Not a package!".to_string())) } } @@ -833,12 +852,12 @@ impl RawPaginated for StoredHistoryPackage { filter!( query, format!( - "o.checkpoint_sequence_number > {cp} OR (\ - o.checkpoint_sequence_number = {cp} AND - original_id > '\\x{id}'::bytea OR (\ - original_id = '\\x{id}'::bytea AND \ - o.object_version >= {pv}\ - ))", + "(p.checkpoint_sequence_number > {cp} OR (\ + p.checkpoint_sequence_number = {cp} AND \ + (original_id > '\\x{id}'::bytea OR (\ + original_id = '\\x{id}'::bytea AND \ + object_version >= {pv}\ + ))))", cp = cursor.checkpoint_sequence_number, id = hex::encode(&cursor.original_id), pv = cursor.package_version, @@ -850,12 +869,12 @@ impl RawPaginated for StoredHistoryPackage { filter!( query, format!( - "o.checkpoint_sequence_number < {cp} OR (\ - o.checkpoint_sequence_number = {cp} AND - original_id < '\\x{id}'::bytea OR (\ - original_id = '\\x{id}'::bytea AND \ - o.object_version <= {pv}\ - ))", + "(p.checkpoint_sequence_number < {cp} OR (\ + p.checkpoint_sequence_number = {cp} AND \ + (original_id < '\\x{id}'::bytea OR (\ + original_id = '\\x{id}'::bytea AND \ + object_version <= {pv}\ + ))))", cp = cursor.checkpoint_sequence_number, id = hex::encode(&cursor.original_id), pv = cursor.package_version, @@ -866,14 +885,14 @@ impl RawPaginated for StoredHistoryPackage { fn order(asc: bool, query: RawQuery) -> RawQuery { if asc { query - .order_by("o.checkpoint_sequence_number ASC") - .order_by("original_id ASC") - .order_by("o.object_version ASC") + .order_by("1 ASC") // checkpoint_sequence_number + .order_by("2 ASC") // original_id + .order_by("object_version ASC") } else { query - .order_by("o.checkpoint_sequence_number DESC") - .order_by("original_id DESC") - .order_by("o.object_version DESC") + .order_by("1 DESC") // checkpoint_sequence_number + .order_by("2 DESC") // original_id + .order_by("object_version DESC") } } } @@ -881,7 +900,7 @@ impl RawPaginated for StoredHistoryPackage { impl Target for StoredHistoryPackage { fn cursor(&self, checkpoint_viewed_at: u64) -> Cursor { Cursor::new(PackageCursor { - checkpoint_sequence_number: self.object.checkpoint_sequence_number as u64, + checkpoint_sequence_number: self.checkpoint_sequence_number as u64, original_id: self.original_id.clone(), package_version: self.object.object_version as u64, checkpoint_viewed_at, @@ -1049,7 +1068,11 @@ impl TryFrom<&Object> for MovePackage { } /// Query for fetching all the versions of a system package (assumes that `package` has already been -/// verified as a system package). This is an `objects_history` query disguised as a package query. +/// verified as a system package). This is a `full_objects_history` query disguised as a package query. +/// +/// We do this because the `packages` table contains only one entry per package ID. For the system +/// packages, this is the latest version of the package (for user packages, there is only one entry +/// per package ID anyway as each version of a package gets its own ID). fn system_package_version_query( package: SuiAddress, filter: Option, @@ -1058,35 +1081,42 @@ fn system_package_version_query( let mut q = query!( r#" SELECT - o.object_id AS original_id, + p.checkpoint_sequence_number, + p.original_id, o.* - FROM - objects_version v + FROM ( + SELECT + object_id AS package_id, + object_id AS original_id, + object_version AS package_version, + cp_sequence_number AS checkpoint_sequence_number + FROM + objects_version + ) p LEFT JOIN - objects_history o + full_objects_history o ON - v.object_id = o.object_id - AND v.object_version = o.object_version - AND v.cp_sequence_number = o.checkpoint_sequence_number + p.package_id = o.object_id + AND p.package_version = o.object_version "# ); q = filter!( q, format!( - "v.object_id = '\\x{}'::bytea", + "original_id = '\\x{}'::bytea", hex::encode(package.into_vec()) ) ); if let Some(after) = filter.as_ref().and_then(|f| f.after_version) { let a: u64 = after.into(); - q = filter!(q, format!("v.object_version > {a}")); + q = filter!(q, format!("object_version > {a}")); } if let Some(before) = filter.as_ref().and_then(|f| f.before_version) { let b: u64 = before.into(); - q = filter!(q, format!("v.object_version < {b}")); + q = filter!(q, format!("object_version < {b}")); } q @@ -1101,20 +1131,19 @@ fn user_package_version_query( let mut q = query!( r#" SELECT + p.checkpoint_sequence_number, p.original_id, o.* FROM packages q INNER JOIN packages p - ON - q.original_id = p.original_id + USING (original_id) INNER JOIN - objects_history o + full_objects_history o ON p.package_id = o.object_id AND p.package_version = o.object_version - AND p.checkpoint_sequence_number = o.checkpoint_sequence_number "# ); diff --git a/crates/sui-graphql-rpc/src/types/move_registry/named_type.rs b/crates/sui-graphql-rpc/src/types/move_registry/named_type.rs index 544642eafb5a1..9a522897ce560 100644 --- a/crates/sui-graphql-rpc/src/types/move_registry/named_type.rs +++ b/crates/sui-graphql-rpc/src/types/move_registry/named_type.rs @@ -9,7 +9,7 @@ use futures::future; use regex::{Captures, Regex}; use sui_types::{base_types::ObjectID, TypeTag}; -use crate::error::Error; +use crate::{data::package_resolver::PackageResolver, error::Error}; use super::{ error::MoveRegistryError, @@ -28,6 +28,7 @@ impl NamedType { name: &str, checkpoint_viewed_at: u64, ) -> Result { + let resolver: &PackageResolver = ctx.data_unchecked(); // we do not de-duplicate the names here, as the dataloader will do this for us. let names = Self::parse_names(name)?; @@ -55,7 +56,13 @@ impl NamedType { let correct_type_tag: String = Self::replace_names(name, &name_package_id_mapping)?; - TypeTag::from_str(&correct_type_tag).map_err(|e| Error::Client(format!("bad type: {e}"))) + let tag = TypeTag::from_str(&correct_type_tag) + .map_err(|e| Error::Client(format!("bad type: {e}")))?; + + resolver + .canonical_type(tag) + .await + .map_err(|e| Error::Internal(format!("Failed to retrieve type: {e}"))) } /// Is this already caught by the global limits? diff --git a/crates/sui-graphql-rpc/src/types/object.rs b/crates/sui-graphql-rpc/src/types/object.rs index 15944d7920ec0..b7e538fc185a5 100644 --- a/crates/sui-graphql-rpc/src/types/object.rs +++ b/crates/sui-graphql-rpc/src/types/object.rs @@ -51,6 +51,7 @@ use sui_types::object::{ MoveObject as NativeMoveObject, Object as NativeObject, Owner as NativeOwner, }; use sui_types::TypeTag; + #[derive(Clone, Debug)] pub(crate) struct Object { pub address: SuiAddress, @@ -85,9 +86,6 @@ pub(crate) enum ObjectKind { Indexed(NativeObject, StoredHistoryObject), /// An object in the bcs serialized form. Serialized(Vec), - /// The object is wrapped or deleted and only partial information can be loaded from the - /// indexer. - WrappedOrDeleted, } #[derive(Enum, Copy, Clone, Eq, PartialEq, Debug)] @@ -98,9 +96,6 @@ pub enum ObjectStatus { NotIndexed, /// The object is fetched from the index. Indexed, - /// The object is deleted or wrapped and only partial information can be loaded from the - /// indexer. - WrappedOrDeleted, } #[derive(Clone, Debug, PartialEq, Eq, InputObject)] @@ -169,11 +164,13 @@ pub(crate) struct Shared { } /// If the object's owner is a Parent, this object is part of a dynamic field (it is the value of -/// the dynamic field, or the intermediate Field object itself). Also note that if the owner -/// is a parent, then it's guaranteed to be an object. +/// the dynamic field, or the intermediate Field object itself), and it is owned by another object. +/// +/// Although its owner is guaranteed to be an object, it is exposed as an Owner, as the parent +/// object could be wrapped and therefore not directly accessible. #[derive(SimpleObject, Clone)] pub(crate) struct Parent { - parent: Option, + parent: Option, } /// An address-owned object is owned by a specific 32-byte address that is @@ -445,8 +442,8 @@ impl Object { /// The owner type of this object: Immutable, Shared, Parent, Address /// Immutable and Shared Objects do not have owners. - pub(crate) async fn owner(&self, ctx: &Context<'_>) -> Option { - ObjectImpl(self).owner(ctx).await + pub(crate) async fn owner(&self) -> Option { + ObjectImpl(self).owner().await } /// The transaction block that created this version of the object. @@ -586,7 +583,7 @@ impl ObjectImpl<'_> { .map(|native| native.digest().base58_encode()) } - pub(crate) async fn owner(&self, ctx: &Context<'_>) -> Option { + pub(crate) async fn owner(&self) -> Option { use NativeOwner as O; let native = self.0.native_impl()?; @@ -604,16 +601,14 @@ impl ObjectImpl<'_> { } O::Immutable => Some(ObjectOwner::Immutable(Immutable { dummy: None })), O::ObjectOwner(address) => { - let parent = Object::query( - ctx, - address.into(), - Object::latest_at(self.0.checkpoint_viewed_at), - ) - .await - .ok() - .flatten(); - - Some(ObjectOwner::Parent(Parent { parent })) + let address = SuiAddress::from(address); + Some(ObjectOwner::Parent(Parent { + parent: Some(Owner { + address, + checkpoint_viewed_at: self.0.checkpoint_viewed_at, + root_version: Some(self.0.root_version()), + }), + })) } O::Shared { initial_shared_version, @@ -632,9 +627,12 @@ impl ObjectImpl<'_> { }; let digest = native.previous_transaction; - TransactionBlock::query(ctx, digest.into(), self.0.checkpoint_viewed_at) - .await - .extend() + TransactionBlock::query( + ctx, + TransactionBlock::by_digest(digest.into(), self.0.checkpoint_viewed_at), + ) + .await + .extend() } pub(crate) async fn storage_rebate(&self) -> Option { @@ -658,9 +656,6 @@ impl ObjectImpl<'_> { let Some(filter) = filter .unwrap_or_default() .intersect(TransactionBlockFilter { - #[cfg(not(feature = "staging"))] - recv_address: Some(self.0.address), - #[cfg(feature = "staging")] affected_address: Some(self.0.address), ..Default::default() }) @@ -676,12 +671,7 @@ impl ObjectImpl<'_> { pub(crate) async fn bcs(&self) -> Result> { use ObjectKind as K; Ok(match &self.0.kind { - K::WrappedOrDeleted => None, - // WrappedOrDeleted objects are also read from the historical objects table, and they do - // not have a serialized object, so the column is also nullable for stored historical - // objects. K::Indexed(_, stored) => stored.serialized_object.as_ref().map(Base64::from), - K::NotIndexed(native) => { let bytes = bcs::to_bytes(native) .map_err(|e| { @@ -764,24 +754,14 @@ impl Object { serialized: Option>, checkpoint_viewed_at: u64, root_version: u64, - ) -> Self { - if let Some(bytes) = serialized { - Self { - address: object_id, - version, - kind: ObjectKind::Serialized(bytes), - checkpoint_viewed_at, - root_version, - } - } else { - Self { - address: object_id, - version, - kind: ObjectKind::WrappedOrDeleted, - checkpoint_viewed_at, - root_version: version, - } - } + ) -> Option { + serialized.map(|bytes| Self { + address: object_id, + version, + kind: ObjectKind::Serialized(bytes), + checkpoint_viewed_at, + root_version, + }) } pub(crate) fn native_impl(&self) -> Option { @@ -790,7 +770,6 @@ impl Object { match &self.kind { K::NotIndexed(native) | K::Indexed(native, _) => Some(native.clone()), K::Serialized(bytes) => bcs::from_bytes(bytes).ok(), - K::WrappedOrDeleted => None, } } @@ -1016,13 +995,9 @@ impl Object { root_version, }) } - NativeObjectStatus::WrappedOrDeleted => Ok(Self { - address, - version: history_object.object_version as u64, - kind: ObjectKind::WrappedOrDeleted, - checkpoint_viewed_at, - root_version: history_object.object_version as u64, - }), + NativeObjectStatus::WrappedOrDeleted => Err(Error::Internal( + "Wrapped or deleted objects should not be loaded from DB.".to_string(), + )), } } } @@ -1265,6 +1240,10 @@ impl Loader for Db { async fn load(&self, keys: &[HistoricalKey]) -> Result, Error> { use objects_version::dsl as v; + if keys.is_empty() { + return Ok(HashMap::new()); + } + let id_versions: BTreeSet<_> = keys .iter() .map(|key| (key.id.into_vec(), key.version as i64)) @@ -1280,6 +1259,9 @@ impl Loader for Db { .into_boxed(); for (id, version) in id_versions.iter().cloned() { + // TODO: consider using something other than `or_filter` to avoid returning + // all results when `id_versions` is empty. It is mitigated today by the + // early return above. query = query .or_filter(v::object_id.eq(id).and(v::object_version.eq(version))); } @@ -1318,16 +1300,14 @@ impl Loader for Db { .zip(point_lookup_keys) .filter_map(|(hist_key, lookup_key)| { let object = objects.get(&lookup_key)?; - Some(( - *hist_key, - Object::new_serialized( - lookup_key.id, - lookup_key.version, - object.clone(), - hist_key.checkpoint_viewed_at, - lookup_key.version, - ), - )) + let hist_obj = Object::new_serialized( + lookup_key.id, + lookup_key.version, + object.clone(), + hist_key.checkpoint_viewed_at, + lookup_key.version, + ); + hist_obj.map(|obj| (*hist_key, obj)) }) .collect(); Ok(results) @@ -1428,18 +1408,16 @@ impl Loader for Db { .zip(point_lookup_keys) .filter_map(|(parent_key, lookup_key)| { let object = objects.get(&lookup_key)?; - Some(( - parent_key, - Object::new_serialized( - parent_key.id, - lookup_key.version, - object.clone(), - parent_key.checkpoint_viewed_at, - // If `ParentVersionKey::parent_version` is set, it must have been correctly - // propagated from the `Object::root_version` of some object. - parent_key.parent_version, - ), - )) + let hist_obj = Object::new_serialized( + parent_key.id, + lookup_key.version, + object.clone(), + parent_key.checkpoint_viewed_at, + // If `ParentVersionKey::parent_version` is set, it must have been correctly + // propagated from the `Object::root_version` of some object. + parent_key.parent_version, + ); + hist_obj.map(|obj| (parent_key, obj)) }) .collect(); @@ -1538,6 +1516,10 @@ impl Loader for Db { ) -> Result>>, Error> { use full_objects_history::dsl as f; + if keys.is_empty() { + return Ok(HashMap::new()); + } + let id_versions: BTreeSet<_> = keys .iter() .map(|key| (key.id.into_vec(), key.version as i64)) @@ -1551,6 +1533,9 @@ impl Loader for Db { .into_boxed(); for (id, version) in id_versions.iter() { + // TODO: consider using something other than `or_filter` to avoid returning + // all results when `id_versions` is empty. It is mitigated today by the + // early return above. query = query.or_filter( f::object_id .eq(id.clone()) @@ -1594,7 +1579,6 @@ impl From<&ObjectKind> for ObjectStatus { match kind { ObjectKind::NotIndexed(_) => ObjectStatus::NotIndexed, ObjectKind::Indexed(_, _) | ObjectKind::Serialized(_) => ObjectStatus::Indexed, - ObjectKind::WrappedOrDeleted => ObjectStatus::WrappedOrDeleted, } } } diff --git a/crates/sui-graphql-rpc/src/types/query.rs b/crates/sui-graphql-rpc/src/types/query.rs index ca8d9afc5a0f0..74d17363a0c31 100644 --- a/crates/sui-graphql-rpc/src/types/query.rs +++ b/crates/sui-graphql-rpc/src/types/query.rs @@ -29,7 +29,7 @@ use super::{ cursor::Page, digest::Digest, dry_run_result::DryRunResult, - epoch::Epoch, + epoch::{self, Epoch}, event::{self, Event, EventFilter}, move_type::MoveType, object::{self, Object, ObjectFilter}, @@ -323,9 +323,8 @@ impl Query { digest: Digest, ) -> Result> { let Watermark { checkpoint, .. } = *ctx.data()?; - TransactionBlock::query(ctx, digest, checkpoint) - .await - .extend() + let lookup = TransactionBlock::by_digest(digest, checkpoint); + TransactionBlock::query(ctx, lookup).await.extend() } /// The coin objects that exist in the network. @@ -356,6 +355,23 @@ impl Query { .extend() } + // The epochs of the network + async fn epochs( + &self, + ctx: &Context<'_>, + first: Option, + after: Option, + last: Option, + before: Option, + ) -> Result> { + let Watermark { checkpoint, .. } = *ctx.data()?; + + let page = Page::from_params(ctx.data_unchecked(), first, after, last, before)?; + Epoch::paginate(ctx.data_unchecked(), page, checkpoint) + .await + .extend() + } + /// The checkpoints that exist in the network. async fn checkpoints( &self, diff --git a/crates/sui-graphql-rpc/src/types/stake.rs b/crates/sui-graphql-rpc/src/types/stake.rs index 75c0a07dabf5a..74c33726c29dc 100644 --- a/crates/sui-graphql-rpc/src/types/stake.rs +++ b/crates/sui-graphql-rpc/src/types/stake.rs @@ -181,8 +181,8 @@ impl StakedSui { } /// The owner type of this object: Immutable, Shared, Parent, Address - pub(crate) async fn owner(&self, ctx: &Context<'_>) -> Option { - ObjectImpl(&self.super_.super_).owner(ctx).await + pub(crate) async fn owner(&self) -> Option { + ObjectImpl(&self.super_.super_).owner().await } /// The transaction block that created this version of the object. diff --git a/crates/sui-graphql-rpc/src/types/suins_registration.rs b/crates/sui-graphql-rpc/src/types/suins_registration.rs index f3dfa9c368e54..d8e9bb27dc142 100644 --- a/crates/sui-graphql-rpc/src/types/suins_registration.rs +++ b/crates/sui-graphql-rpc/src/types/suins_registration.rs @@ -219,8 +219,8 @@ impl SuinsRegistration { } /// The owner type of this object: Immutable, Shared, Parent, Address - pub(crate) async fn owner(&self, ctx: &Context<'_>) -> Option { - ObjectImpl(&self.super_.super_).owner(ctx).await + pub(crate) async fn owner(&self) -> Option { + ObjectImpl(&self.super_.super_).owner().await } /// The transaction block that created this version of the object. diff --git a/crates/sui-graphql-rpc/src/types/transaction_block/filter.rs b/crates/sui-graphql-rpc/src/types/transaction_block/filter.rs index a3300faf96260..7af2c700a56d6 100644 --- a/crates/sui-graphql-rpc/src/types/transaction_block/filter.rs +++ b/crates/sui-graphql-rpc/src/types/transaction_block/filter.rs @@ -28,27 +28,18 @@ pub(crate) struct TransactionBlockFilter { /// Limit to transactions that interacted with the given address. The address could be a /// sender, sponsor, or recipient of the transaction. - #[cfg(feature = "staging")] pub affected_address: Option, - /// Limit to transactions that were sent by the given address. NOTE: this input filter has been - /// deprecated in favor of `sentAddress` which behaves identically but is named more clearly. - /// Both filters restrict transactions by their sender, only, not signers in general. - /// - /// This filter will be removed with 1.36.0 (2024-10-14). - pub sign_address: Option, + /// Limit to transactions that interacted with the given object. The object could have been + /// created, read, modified, deleted, wrapped, or unwrapped by the transaction. Objects that + /// were passed as a `Receiving` input are not considered to have been affected by a + /// transaction unless they were actually received. + #[cfg(feature = "staging")] + pub affected_object: Option, /// Limit to transactions that were sent by the given address. pub sent_address: Option, - /// Limit to transactions that sent an object to the given address. NOTE: this input filter has - /// been deprecated in favor of `affectedAddress` which offers an easier to understand - /// behavior. - /// - /// This filter will be removed with 1.36.0 (2024-10-14), or at least one release after - /// `affectedAddress` is introduced, whichever is later. - pub recv_address: Option, - /// Limit to transactions that accepted the given object as an input. NOTE: this input filter /// has been deprecated in favor of `affectedObject` which offers an easier to under behavior. /// @@ -87,11 +78,10 @@ impl TransactionBlockFilter { at_checkpoint: intersect!(at_checkpoint, intersect::by_eq)?, before_checkpoint: intersect!(before_checkpoint, intersect::by_min)?, - #[cfg(feature = "staging")] affected_address: intersect!(affected_address, intersect::by_eq)?, - sign_address: intersect!(sign_address, intersect::by_eq)?, + #[cfg(feature = "staging")] + affected_object: intersect!(affected_object, intersect::by_eq)?, sent_address: intersect!(sent_address, intersect::by_eq)?, - recv_address: intersect!(recv_address, intersect::by_eq)?, input_object: intersect!(input_object, intersect::by_eq)?, changed_object: intersect!(changed_object, intersect::by_eq)?, @@ -110,9 +100,9 @@ impl TransactionBlockFilter { [ self.function.is_some(), self.kind.is_some(), - #[cfg(feature = "staging")] self.affected_address.is_some(), - self.recv_address.is_some(), + #[cfg(feature = "staging")] + self.affected_object.is_some(), self.input_object.is_some(), self.changed_object.is_some(), self.transaction_ids.is_some(), @@ -129,15 +119,15 @@ impl TransactionBlockFilter { pub(crate) fn explicit_sender(&self) -> Option { let missing_implicit_sender = self.function.is_none() && self.kind.is_none() - && self.recv_address.is_none() + && self.affected_address.is_none() && self.input_object.is_none() && self.changed_object.is_none(); #[cfg(feature = "staging")] - let missing_implicit_sender = missing_implicit_sender && self.affected_address.is_none(); + let missing_implicit_sender = missing_implicit_sender && self.affected_object.is_none(); missing_implicit_sender - .then_some(self.sent_address.or(self.sign_address)) + .then_some(self.sent_address) .flatten() } @@ -146,15 +136,14 @@ impl TransactionBlockFilter { pub(crate) fn has_filters(&self) -> bool { let has_filters = self.function.is_some() || self.kind.is_some() - || self.sign_address.is_some() || self.sent_address.is_some() - || self.recv_address.is_some() + || self.affected_address.is_some() || self.input_object.is_some() || self.changed_object.is_some() || self.transaction_ids.is_some(); #[cfg(feature = "staging")] - let has_filters = has_filters || self.affected_address.is_some(); + let has_filters = has_filters || self.affected_object.is_some(); has_filters } @@ -180,11 +169,5 @@ impl TransactionBlockFilter { if (kind == TransactionBlockKindInput::SystemTx) != (signer == SuiAddress::from(NativeSuiAddress::ZERO)) ) - // Temporary while we deprecate `sign_address` in favor of `sent_address`. - || matches!( - (self.sign_address, self.sent_address), - (Some(signer), Some(sent)) - if signer != sent - ) } } diff --git a/crates/sui-graphql-rpc/src/types/transaction_block/mod.rs b/crates/sui-graphql-rpc/src/types/transaction_block/mod.rs index b03a9e3cd7cd9..7bef4cf389438 100644 --- a/crates/sui-graphql-rpc/src/types/transaction_block/mod.rs +++ b/crates/sui-graphql-rpc/src/types/transaction_block/mod.rs @@ -93,6 +93,18 @@ pub(crate) enum TransactionBlockKindInput { ProgrammableTx = 1, } +/// Filter for a point query of a TransactionBlock. +pub(crate) enum TransactionBlockLookup { + ByDigest { + digest: Digest, + checkpoint_viewed_at: u64, + }, + BySeq { + tx_sequence_number: u64, + checkpoint_viewed_at: u64, + }, +} + type Query = data::Query; /// The cursor returned for each `TransactionBlock` in a connection's page of results. The @@ -110,14 +122,22 @@ pub(crate) struct TransactionBlockCursor { pub tx_checkpoint_number: u64, } -/// `DataLoader` key for fetching a `TransactionBlock` by its digest, optionally constrained by a -/// consistency cursor. +/// `DataLoader` key for fetching a `TransactionBlock` by its digest, constrained by a consistency +/// cursor. #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] struct DigestKey { pub digest: Digest, pub checkpoint_viewed_at: u64, } +/// `DataLoader` key for fetching a `TransactionBlock` by its sequence number, constrained by a +/// consistency cursor. +#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] +struct SeqKey { + pub tx_sequence_number: u64, + pub checkpoint_viewed_at: u64, +} + #[Object] impl TransactionBlock { /// A 32-byte hash that uniquely identifies the transaction block contents, encoded in Base58. @@ -198,17 +218,18 @@ impl TransactionBlock { .extend() } - /// Serialized form of this transaction's `SenderSignedData`, BCS serialized and Base64 encoded. + /// Serialized form of this transaction's `TransactionData`, BCS serialized and Base64 encoded. async fn bcs(&self) -> Option { match &self.inner { - TransactionBlockInner::Stored { stored_tx, .. } => { - Some(Base64::from(&stored_tx.raw_transaction)) + TransactionBlockInner::Stored { native, .. } => Some(Base64::from( + &bcs::to_bytes(native.transaction_data()).unwrap(), + )), + TransactionBlockInner::Executed { tx_data, .. } => Some(Base64::from( + &bcs::to_bytes(tx_data.transaction_data()).unwrap(), + )), + TransactionBlockInner::DryRun { tx_data, .. } => { + Some(Base64::from(&bcs::to_bytes(tx_data).unwrap())) } - TransactionBlockInner::Executed { tx_data, .. } => { - bcs::to_bytes(&tx_data).ok().map(Base64::from) - } - // Dry run transaction does not have signatures so no sender signed data. - TransactionBlockInner::DryRun { .. } => None, } } } @@ -230,21 +251,60 @@ impl TransactionBlock { } } + /// Look-up the transaction block by its transaction digest. + pub(crate) fn by_digest(digest: Digest, checkpoint_viewed_at: u64) -> TransactionBlockLookup { + TransactionBlockLookup::ByDigest { + digest, + checkpoint_viewed_at, + } + } + + /// Look-up the transaction block by its sequence number (this is not usually exposed through + /// the GraphQL schema, but internally, othe entities in the DB will refer to transactions at + /// their sequence number). + pub(crate) fn by_seq( + tx_sequence_number: u64, + checkpoint_viewed_at: u64, + ) -> TransactionBlockLookup { + TransactionBlockLookup::BySeq { + tx_sequence_number, + checkpoint_viewed_at, + } + } + /// Look up a `TransactionBlock` in the database, by its transaction digest. Treats it as if it /// is being viewed at the `checkpoint_viewed_at` (e.g. the state of all relevant addresses will /// be at that checkpoint). pub(crate) async fn query( ctx: &Context<'_>, - digest: Digest, - checkpoint_viewed_at: u64, + lookup: TransactionBlockLookup, ) -> Result, Error> { let DataLoader(loader) = ctx.data_unchecked(); - loader - .load_one(DigestKey { + + match lookup { + TransactionBlockLookup::ByDigest { digest, checkpoint_viewed_at, - }) - .await + } => { + loader + .load_one(DigestKey { + digest, + checkpoint_viewed_at, + }) + .await + } + TransactionBlockLookup::BySeq { + tx_sequence_number, + checkpoint_viewed_at, + } => { + loader + .load_one(SeqKey { + tx_sequence_number, + checkpoint_viewed_at, + }) + .await + } + } } /// Look up multiple `TransactionBlock`s by their digests. Returns a map from those digests to @@ -278,8 +338,8 @@ impl TransactionBlock { /// If the `Page` is set, then this function will defer to the `checkpoint_viewed_at` in /// the cursor if they are consistent. /// - /// Filters that involve a combination of `recvAddress`, `inputObject`, `changedObject`, and - /// `function` should provide a value for `scan_limit`. This modifies querying behavior by + /// Filters that involve a combination of `affectedAddress`, `inputObject`, `changedObject`, + /// and `function` should provide a value for `scan_limit`. This modifies querying behavior by /// limiting how many transactions to scan through before applying filters, and also affects /// pagination behavior. pub(crate) async fn paginate( @@ -497,6 +557,63 @@ impl Loader for Db { } } +#[async_trait::async_trait] +impl Loader for Db { + type Value = TransactionBlock; + type Error = Error; + + async fn load(&self, keys: &[SeqKey]) -> Result, Error> { + use transactions::dsl as tx; + + let seqs: Vec<_> = keys.iter().map(|k| k.tx_sequence_number as i64).collect(); + + let transactions: Vec = self + .execute(move |conn| { + async move { + conn.results(move || { + tx::transactions + .select(StoredTransaction::as_select()) + .filter(tx::tx_sequence_number.eq_any(seqs.clone())) + }) + .await + } + .scope_boxed() + }) + .await + .map_err(|e| Error::Internal(format!("Failed to fetch transactions: {e}")))?; + + let seq_to_stored: BTreeMap<_, _> = transactions + .into_iter() + .map(|tx| (tx.tx_sequence_number as u64, tx)) + .collect(); + + let mut results = HashMap::new(); + for key in keys { + let Some(stored) = seq_to_stored.get(&key.tx_sequence_number).cloned() else { + continue; + }; + + // Filter by key's checkpoint viewed at here. Doing this in memory because it should be + // quite rare that this query actually filters something, but encoding it in SQL is + // complicated. + if key.checkpoint_viewed_at < stored.checkpoint_sequence_number as u64 { + continue; + } + + let inner = TransactionBlockInner::try_from(stored)?; + results.insert( + *key, + TransactionBlock { + inner, + checkpoint_viewed_at: key.checkpoint_viewed_at, + }, + ); + } + + Ok(results) + } +} + impl TryFrom for TransactionBlockInner { type Error = Error; diff --git a/crates/sui-graphql-rpc/src/types/transaction_block/tx_lookups.rs b/crates/sui-graphql-rpc/src/types/transaction_block/tx_lookups.rs index de301a5af8bd5..46941617a56bd 100644 --- a/crates/sui-graphql-rpc/src/types/transaction_block/tx_lookups.rs +++ b/crates/sui-graphql-rpc/src/types/transaction_block/tx_lookups.rs @@ -305,7 +305,7 @@ fn min_option(xs: impl IntoIterator>) -> Option { /// Constructs a `RawQuery` as a join over all relevant side tables, filtered on their own filter /// condition, plus optionally a sender, plus optionally tx/cp bounds. pub(crate) fn subqueries(filter: &TransactionBlockFilter, tx_bounds: TxBounds) -> Option { - let sender = filter.sent_address.or(filter.sign_address); + let sender = filter.sent_address; let mut subqueries = vec![]; @@ -328,7 +328,6 @@ pub(crate) fn subqueries(filter: &TransactionBlockFilter, tx_bounds: TxBounds) - subqueries.push(("tx_kinds", select_kind(*kind, sender, tx_bounds))); } - #[cfg(feature = "staging")] if let Some(affected) = &filter.affected_address { subqueries.push(( "tx_affected_addresses", @@ -336,8 +335,12 @@ pub(crate) fn subqueries(filter: &TransactionBlockFilter, tx_bounds: TxBounds) - )); } - if let Some(recv) = &filter.recv_address { - subqueries.push(("tx_recipients", select_recipient(recv, sender, tx_bounds))); + #[cfg(feature = "staging")] + if let Some(affected) = &filter.affected_object { + subqueries.push(( + "tx_affected_objects", + select_affected_object(affected, sender, tx_bounds), + )); } if let Some(input) = &filter.input_object { @@ -352,7 +355,10 @@ pub(crate) fn subqueries(filter: &TransactionBlockFilter, tx_bounds: TxBounds) - } if let Some(sender) = &filter.explicit_sender() { - subqueries.push(("tx_senders", select_sender(sender, tx_bounds))); + subqueries.push(( + "tx_affected_addresses", + select_affected_address(sender, Some(*sender), tx_bounds), + )); } if let Some(txs) = &filter.transaction_ids { @@ -434,19 +440,21 @@ fn select_fun( /// Returns a RawQuery that selects transactions of a specific kind. If SystemTX is specified, we /// ignore the `sender`. If ProgrammableTX is specified, we filter against the `tx_kinds` table if -/// no `sender` is provided; otherwise, we just query the `tx_senders` table. Other combinations, in -/// particular when kind is SystemTx and sender is specified and not 0x0, are inconsistent and will -/// not produce any results. These inconsistent cases are expected to be checked for before this is -/// called. +/// no `sender` is provided; otherwise, we just query the `tx_affected_addresses` table. Other +/// combinations, in particular when kind is SystemTx and sender is specified and not 0x0, are +/// inconsistent and will not produce any results. These inconsistent cases are expected to be +/// checked for before this is called. fn select_kind( kind: TransactionBlockKindInput, sender: Option, bound: TxBounds, ) -> RawQuery { match (kind, sender) { - // We can simplify the query to just the `tx_senders` table if ProgrammableTX and sender is - // specified. - (TransactionBlockKindInput::ProgrammableTx, Some(sender)) => select_sender(&sender, bound), + // We can simplify the query to just the `tx_affected_addresses` table if ProgrammableTX + // and sender is specified. + (TransactionBlockKindInput::ProgrammableTx, Some(sender)) => { + select_affected_address(&sender, Some(sender), bound) + } // Otherwise, we can ignore the sender always, and just query the `tx_kinds` table. _ => filter!( select_tx(None, bound, "tx_kinds"), @@ -455,7 +463,6 @@ fn select_kind( } } -#[cfg(feature = "staging")] fn select_affected_address( affected: &SuiAddress, sender: Option, @@ -467,14 +474,15 @@ fn select_affected_address( ) } -fn select_sender(sender: &SuiAddress, bound: TxBounds) -> RawQuery { - select_tx(Some(*sender), bound, "tx_senders") -} - -fn select_recipient(recv: &SuiAddress, sender: Option, bound: TxBounds) -> RawQuery { +#[cfg(feature = "staging")] +fn select_affected_object( + affected: &SuiAddress, + sender: Option, + bound: TxBounds, +) -> RawQuery { filter!( - select_tx(sender, bound, "tx_recipients"), - format!("recipient = {}", bytea_literal(recv.as_slice())) + select_tx(sender, bound, "tx_affected_objects"), + format!("affected = {}", bytea_literal(affected.as_slice())) ) } diff --git a/crates/sui-graphql-rpc/staging.graphql b/crates/sui-graphql-rpc/staging.graphql index 63161e4c5d2d3..d37152f34e1bb 100644 --- a/crates/sui-graphql-rpc/staging.graphql +++ b/crates/sui-graphql-rpc/staging.graphql @@ -163,27 +163,11 @@ type AddressOwner { The possible relationship types for a transaction block: sent, or received. """ enum AddressTransactionBlockRelationship { - """ - Transactions this address has sent. NOTE: this input filter has been deprecated in favor of - `SENT` which behaves identically but is named more clearly. Both filters restrict - transactions by their sender, only, not signers in general. - - This filter will be removed with 1.36.0 (2024-10-14). - """ - SIGN """ Transactions this address has sent. """ SENT """ - Transactions that sent objects to this address. NOTE: this input filter has been deprecated - in favor of `AFFECTED`, which offers an easier to understand behavior. - - This filter will be removed with 1.36.0 (2024-10-14), or at least one release after - `AFFECTED` is introduced, whichever is later. - """ - RECV - """ Transactions that this address was involved in, either as the sender, sponsor, or as the owner of some object that was created, modified or transfered. """ @@ -1205,7 +1189,42 @@ type Epoch { transactionBlocks(first: Int, after: String, last: Int, before: String, filter: TransactionBlockFilter, scanLimit: Int): TransactionBlockConnection! } +type EpochConnection { + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + """ + A list of edges. + """ + edges: [EpochEdge!]! + """ + A list of nodes. + """ + nodes: [Epoch!]! +} + +""" +An edge in a connection. +""" +type EpochEdge { + """ + The item at the end of the edge + """ + node: Epoch! + """ + A cursor for use in pagination + """ + cursor: String! +} + type Event { + """ + The transaction block that emitted this event. This information is only available for + events from indexed transactions, and not from transactions that have just been executed or + dry-run. + """ + transactionBlock: TransactionBlock """ The Move module containing some function that when called by a programmable transaction block (PTB) emitted this event. @@ -1223,32 +1242,13 @@ type Event { """ timestamp: DateTime """ - The value's Move type. + The event's contents as a Move value. """ - type: MoveType! + contents: MoveValue! """ - The BCS representation of this value, Base64 encoded. + The Base64 encoded BCS serialized bytes of the event. """ bcs: Base64! - """ - Structured contents of a Move value. - """ - data: MoveData! - """ - Representation of a Move value in JSON, where: - - - Addresses, IDs, and UIDs are represented in canonical form, as JSON strings. - - Bools are represented by JSON boolean literals. - - u8, u16, and u32 are represented as JSON numbers. - - u64, u128, and u256 are represented as JSON strings. - - Vectors are represented by JSON arrays. - - Structs are represented by JSON objects. - - Empty optional values are represented by `null`. - - This form is offered as a less verbose convenience in cases where the layout of the type is - known by the client. - """ - json: JSON! } type EventConnection { @@ -2359,6 +2359,10 @@ type MovePackage implements IObject & IOwner { """ typeOrigins: [TypeOrigin!] """ + BCS representation of the package itself, as a MovePackage. + """ + packageBcs: Base64 + """ BCS representation of the package's modules. Modules appear as a sequence of pairs (module name, followed by module bytes), in alphabetic order by module name. """ @@ -2896,11 +2900,6 @@ enum ObjectKind { The object is fetched from the index. """ INDEXED - """ - The object is deleted or wrapped and only partial information can be loaded from the - indexer. - """ - WRAPPED_OR_DELETED } """ @@ -3076,11 +3075,13 @@ type PageInfo { """ If the object's owner is a Parent, this object is part of a dynamic field (it is the value of -the dynamic field, or the intermediate Field object itself). Also note that if the owner -is a parent, then it's guaranteed to be an object. +the dynamic field, or the intermediate Field object itself), and it is owned by another object. + +Although its owner is guaranteed to be an object, it is exposed as an Owner, as the parent +object could be wrapped and therefore not directly accessible. """ type Parent { - parent: Object + parent: Owner } """ @@ -3311,6 +3312,7 @@ type Query { `0x2::sui::SUI`). If no type is provided, it will default to `0x2::sui::SUI`. """ coins(first: Int, after: String, last: Int, before: String, type: String): CoinConnection! + epochs(first: Int, after: String, last: Int, before: String): EpochConnection! """ The checkpoints that exist in the network. """ @@ -4208,7 +4210,7 @@ type TransactionBlock { """ expiration: Epoch """ - Serialized form of this transaction's `SenderSignedData`, BCS serialized and Base64 encoded. + Serialized form of this transaction's `TransactionData`, BCS serialized and Base64 encoded. """ bcs: Base64 } @@ -4336,27 +4338,17 @@ input TransactionBlockFilter { """ affectedAddress: SuiAddress """ - Limit to transactions that were sent by the given address. NOTE: this input filter has been - deprecated in favor of `sentAddress` which behaves identically but is named more clearly. - Both filters restrict transactions by their sender, only, not signers in general. - - This filter will be removed with 1.36.0 (2024-10-14). + Limit to transactions that interacted with the given object. The object could have been + created, read, modified, deleted, wrapped, or unwrapped by the transaction. Objects that + were passed as a `Receiving` input are not considered to have been affected by a + transaction unless they were actually received. """ - signAddress: SuiAddress + affectedObject: SuiAddress """ Limit to transactions that were sent by the given address. """ sentAddress: SuiAddress """ - Limit to transactions that sent an object to the given address. NOTE: this input filter has - been deprecated in favor of `affectedAddress` which offers an easier to understand - behavior. - - This filter will be removed with 1.36.0 (2024-10-14), or at least one release after - `affectedAddress` is introduced, whichever is later. - """ - recvAddress: SuiAddress - """ Limit to transactions that accepted the given object as an input. NOTE: this input filter has been deprecated in favor of `affectedObject` which offers an easier to under behavior. diff --git a/crates/sui-graphql-rpc/tests/e2e_tests.rs b/crates/sui-graphql-rpc/tests/e2e_tests.rs index 24d3388aefe03..7f4321f59594c 100644 --- a/crates/sui-graphql-rpc/tests/e2e_tests.rs +++ b/crates/sui-graphql-rpc/tests/e2e_tests.rs @@ -32,7 +32,7 @@ async fn test_simple_client_validator_cluster() { let cluster = start_cluster(ServiceConfig::test_defaults()).await; cluster - .wait_for_checkpoint_catchup(1, Duration::from_secs(10)) + .wait_for_checkpoint_catchup(1, Duration::from_secs(30)) .await; let query = r#" @@ -91,7 +91,7 @@ async fn test_simple_client_simulator_cluster() { ) .await; cluster - .wait_for_checkpoint_catchup(1, Duration::from_secs(10)) + .wait_for_checkpoint_catchup(1, Duration::from_secs(30)) .await; let query = r#" @@ -277,8 +277,6 @@ async fn test_graphql_client_variables() { #[tokio::test] async fn test_transaction_execution() { - telemetry_subscribers::init_for_testing(); - let cluster = start_cluster(ServiceConfig::test_defaults()).await; let addresses = cluster @@ -746,7 +744,7 @@ async fn test_dry_run_failed_execution() { } #[tokio::test] -async fn test_epoch_data() { +async fn test_epoch_live_object_set_digest() { telemetry_subscribers::init_for_testing(); let cluster = start_cluster(ServiceConfig::test_defaults()).await; @@ -758,7 +756,9 @@ async fn test_epoch_data() { .await; // Wait for the epoch to be indexed - sleep(Duration::from_secs(10)).await; + cluster + .wait_for_epoch_catchup(0, Duration::from_secs(30)) + .await; // Query the epoch let query = " diff --git a/crates/sui-graphql-rpc/tests/move_registry_e2e.rs b/crates/sui-graphql-rpc/tests/move_registry_e2e.rs index 79455ea7ff635..63a83429ee8d3 100644 --- a/crates/sui-graphql-rpc/tests/move_registry_e2e.rs +++ b/crates/sui-graphql-rpc/tests/move_registry_e2e.rs @@ -215,16 +215,12 @@ fn test_results( ); assert_eq!( - query_result["data"]["v1_type"]["layout"]["struct"]["type"] - .as_str() - .unwrap(), + query_result["data"]["v1_type"]["repr"].as_str().unwrap(), format!("{}{}", v1, DEMO_TYPE) ); assert_eq!( - query_result["data"]["v2_type"]["layout"]["struct"]["type"] - .as_str() - .unwrap(), + query_result["data"]["v2_type"]["repr"].as_str().unwrap(), format!("{}{}", v2, DEMO_TYPE_V2) ); @@ -234,6 +230,13 @@ fn test_results( .unwrap(), format!("{}{}", v3, DEMO_TYPE_V3) ); + + assert_eq!( + query_result["data"]["v3_type"]["layout"]["struct"]["type"] + .as_str() + .unwrap(), + query_result["data"]["v3_type"]["repr"].as_str().unwrap() + ); } async fn init_move_registry_gql( @@ -519,5 +522,5 @@ fn name_query(name: &str) -> String { } fn type_query(named_type: &str) -> String { - format!(r#"typeByName(name: "{}") {{ layout }}"#, named_type) + format!(r#"typeByName(name: "{}") {{ layout, repr }}"#, named_type) } diff --git a/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema.graphql.snap b/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema.graphql.snap index b94601fa163cc..70071a6173061 100644 --- a/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema.graphql.snap +++ b/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__schema.graphql.snap @@ -167,26 +167,15 @@ type AddressOwner { The possible relationship types for a transaction block: sent, or received. """ enum AddressTransactionBlockRelationship { - """ - Transactions this address has sent. NOTE: this input filter has been deprecated in favor of - `SENT` which behaves identically but is named more clearly. Both filters restrict - transactions by their sender, only, not signers in general. - - This filter will be removed with 1.36.0 (2024-10-14). - """ - SIGN """ Transactions this address has sent. """ SENT """ - Transactions that sent objects to this address. NOTE: this input filter has been deprecated - in favor of `AFFECTED`, which offers an easier to understand behavior. - - This filter will be removed with 1.36.0 (2024-10-14), or at least one release after - `AFFECTED` is introduced, whichever is later. + Transactions that this address was involved in, either as the sender, sponsor, or as the + owner of some object that was created, modified or transfered. """ - RECV + AFFECTED } """ @@ -1204,7 +1193,42 @@ type Epoch { transactionBlocks(first: Int, after: String, last: Int, before: String, filter: TransactionBlockFilter, scanLimit: Int): TransactionBlockConnection! } +type EpochConnection { + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + """ + A list of edges. + """ + edges: [EpochEdge!]! + """ + A list of nodes. + """ + nodes: [Epoch!]! +} + +""" +An edge in a connection. +""" +type EpochEdge { + """ + The item at the end of the edge + """ + node: Epoch! + """ + A cursor for use in pagination + """ + cursor: String! +} + type Event { + """ + The transaction block that emitted this event. This information is only available for + events from indexed transactions, and not from transactions that have just been executed or + dry-run. + """ + transactionBlock: TransactionBlock """ The Move module containing some function that when called by a programmable transaction block (PTB) emitted this event. @@ -1222,32 +1246,13 @@ type Event { """ timestamp: DateTime """ - The value's Move type. + The event's contents as a Move value. """ - type: MoveType! + contents: MoveValue! """ - The BCS representation of this value, Base64 encoded. + The Base64 encoded BCS serialized bytes of the event. """ bcs: Base64! - """ - Structured contents of a Move value. - """ - data: MoveData! - """ - Representation of a Move value in JSON, where: - - - Addresses, IDs, and UIDs are represented in canonical form, as JSON strings. - - Bools are represented by JSON boolean literals. - - u8, u16, and u32 are represented as JSON numbers. - - u64, u128, and u256 are represented as JSON strings. - - Vectors are represented by JSON arrays. - - Structs are represented by JSON objects. - - Empty optional values are represented by `null`. - - This form is offered as a less verbose convenience in cases where the layout of the type is - known by the client. - """ - json: JSON! } type EventConnection { @@ -2358,6 +2363,10 @@ type MovePackage implements IObject & IOwner { """ typeOrigins: [TypeOrigin!] """ + BCS representation of the package itself, as a MovePackage. + """ + packageBcs: Base64 + """ BCS representation of the package's modules. Modules appear as a sequence of pairs (module name, followed by module bytes), in alphabetic order by module name. """ @@ -2895,11 +2904,6 @@ enum ObjectKind { The object is fetched from the index. """ INDEXED - """ - The object is deleted or wrapped and only partial information can be loaded from the - indexer. - """ - WRAPPED_OR_DELETED } """ @@ -3075,11 +3079,13 @@ type PageInfo { """ If the object's owner is a Parent, this object is part of a dynamic field (it is the value of -the dynamic field, or the intermediate Field object itself). Also note that if the owner -is a parent, then it's guaranteed to be an object. +the dynamic field, or the intermediate Field object itself), and it is owned by another object. + +Although its owner is guaranteed to be an object, it is exposed as an Owner, as the parent +object could be wrapped and therefore not directly accessible. """ type Parent { - parent: Object + parent: Owner } """ @@ -3310,6 +3316,7 @@ type Query { `0x2::sui::SUI`). If no type is provided, it will default to `0x2::sui::SUI`. """ coins(first: Int, after: String, last: Int, before: String, type: String): CoinConnection! + epochs(first: Int, after: String, last: Int, before: String): EpochConnection! """ The checkpoints that exist in the network. """ @@ -4207,7 +4214,7 @@ type TransactionBlock { """ expiration: Epoch """ - Serialized form of this transaction's `SenderSignedData`, BCS serialized and Base64 encoded. + Serialized form of this transaction's `TransactionData`, BCS serialized and Base64 encoded. """ bcs: Base64 } @@ -4330,27 +4337,15 @@ input TransactionBlockFilter { """ beforeCheckpoint: UInt53 """ - Limit to transactions that were sent by the given address. NOTE: this input filter has been - deprecated in favor of `sentAddress` which behaves identically but is named more clearly. - Both filters restrict transactions by their sender, only, not signers in general. - - This filter will be removed with 1.36.0 (2024-10-14). + Limit to transactions that interacted with the given address. The address could be a + sender, sponsor, or recipient of the transaction. """ - signAddress: SuiAddress + affectedAddress: SuiAddress """ Limit to transactions that were sent by the given address. """ sentAddress: SuiAddress """ - Limit to transactions that sent an object to the given address. NOTE: this input filter has - been deprecated in favor of `affectedAddress` which offers an easier to understand - behavior. - - This filter will be removed with 1.36.0 (2024-10-14), or at least one release after - `affectedAddress` is introduced, whichever is later. - """ - recvAddress: SuiAddress - """ Limit to transactions that accepted the given object as an input. NOTE: this input filter has been deprecated in favor of `affectedObject` which offers an easier to under behavior. diff --git a/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__staging.graphql.snap b/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__staging.graphql.snap index f1a4eb49a7557..498feb7c2fafb 100644 --- a/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__staging.graphql.snap +++ b/crates/sui-graphql-rpc/tests/snapshots/snapshot_tests__staging.graphql.snap @@ -167,27 +167,11 @@ type AddressOwner { The possible relationship types for a transaction block: sent, or received. """ enum AddressTransactionBlockRelationship { - """ - Transactions this address has sent. NOTE: this input filter has been deprecated in favor of - `SENT` which behaves identically but is named more clearly. Both filters restrict - transactions by their sender, only, not signers in general. - - This filter will be removed with 1.36.0 (2024-10-14). - """ - SIGN """ Transactions this address has sent. """ SENT """ - Transactions that sent objects to this address. NOTE: this input filter has been deprecated - in favor of `AFFECTED`, which offers an easier to understand behavior. - - This filter will be removed with 1.36.0 (2024-10-14), or at least one release after - `AFFECTED` is introduced, whichever is later. - """ - RECV - """ Transactions that this address was involved in, either as the sender, sponsor, or as the owner of some object that was created, modified or transfered. """ @@ -1209,7 +1193,42 @@ type Epoch { transactionBlocks(first: Int, after: String, last: Int, before: String, filter: TransactionBlockFilter, scanLimit: Int): TransactionBlockConnection! } +type EpochConnection { + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + """ + A list of edges. + """ + edges: [EpochEdge!]! + """ + A list of nodes. + """ + nodes: [Epoch!]! +} + +""" +An edge in a connection. +""" +type EpochEdge { + """ + The item at the end of the edge + """ + node: Epoch! + """ + A cursor for use in pagination + """ + cursor: String! +} + type Event { + """ + The transaction block that emitted this event. This information is only available for + events from indexed transactions, and not from transactions that have just been executed or + dry-run. + """ + transactionBlock: TransactionBlock """ The Move module containing some function that when called by a programmable transaction block (PTB) emitted this event. @@ -1227,32 +1246,13 @@ type Event { """ timestamp: DateTime """ - The value's Move type. + The event's contents as a Move value. """ - type: MoveType! + contents: MoveValue! """ - The BCS representation of this value, Base64 encoded. + The Base64 encoded BCS serialized bytes of the event. """ bcs: Base64! - """ - Structured contents of a Move value. - """ - data: MoveData! - """ - Representation of a Move value in JSON, where: - - - Addresses, IDs, and UIDs are represented in canonical form, as JSON strings. - - Bools are represented by JSON boolean literals. - - u8, u16, and u32 are represented as JSON numbers. - - u64, u128, and u256 are represented as JSON strings. - - Vectors are represented by JSON arrays. - - Structs are represented by JSON objects. - - Empty optional values are represented by `null`. - - This form is offered as a less verbose convenience in cases where the layout of the type is - known by the client. - """ - json: JSON! } type EventConnection { @@ -2363,6 +2363,10 @@ type MovePackage implements IObject & IOwner { """ typeOrigins: [TypeOrigin!] """ + BCS representation of the package itself, as a MovePackage. + """ + packageBcs: Base64 + """ BCS representation of the package's modules. Modules appear as a sequence of pairs (module name, followed by module bytes), in alphabetic order by module name. """ @@ -2900,11 +2904,6 @@ enum ObjectKind { The object is fetched from the index. """ INDEXED - """ - The object is deleted or wrapped and only partial information can be loaded from the - indexer. - """ - WRAPPED_OR_DELETED } """ @@ -3080,11 +3079,13 @@ type PageInfo { """ If the object's owner is a Parent, this object is part of a dynamic field (it is the value of -the dynamic field, or the intermediate Field object itself). Also note that if the owner -is a parent, then it's guaranteed to be an object. +the dynamic field, or the intermediate Field object itself), and it is owned by another object. + +Although its owner is guaranteed to be an object, it is exposed as an Owner, as the parent +object could be wrapped and therefore not directly accessible. """ type Parent { - parent: Object + parent: Owner } """ @@ -3315,6 +3316,7 @@ type Query { `0x2::sui::SUI`). If no type is provided, it will default to `0x2::sui::SUI`. """ coins(first: Int, after: String, last: Int, before: String, type: String): CoinConnection! + epochs(first: Int, after: String, last: Int, before: String): EpochConnection! """ The checkpoints that exist in the network. """ @@ -4212,7 +4214,7 @@ type TransactionBlock { """ expiration: Epoch """ - Serialized form of this transaction's `SenderSignedData`, BCS serialized and Base64 encoded. + Serialized form of this transaction's `TransactionData`, BCS serialized and Base64 encoded. """ bcs: Base64 } @@ -4340,27 +4342,17 @@ input TransactionBlockFilter { """ affectedAddress: SuiAddress """ - Limit to transactions that were sent by the given address. NOTE: this input filter has been - deprecated in favor of `sentAddress` which behaves identically but is named more clearly. - Both filters restrict transactions by their sender, only, not signers in general. - - This filter will be removed with 1.36.0 (2024-10-14). + Limit to transactions that interacted with the given object. The object could have been + created, read, modified, deleted, wrapped, or unwrapped by the transaction. Objects that + were passed as a `Receiving` input are not considered to have been affected by a + transaction unless they were actually received. """ - signAddress: SuiAddress + affectedObject: SuiAddress """ Limit to transactions that were sent by the given address. """ sentAddress: SuiAddress """ - Limit to transactions that sent an object to the given address. NOTE: this input filter has - been deprecated in favor of `affectedAddress` which offers an easier to understand - behavior. - - This filter will be removed with 1.36.0 (2024-10-14), or at least one release after - `affectedAddress` is introduced, whichever is later. - """ - recvAddress: SuiAddress - """ Limit to transactions that accepted the given object as an input. NOTE: this input filter has been deprecated in favor of `affectedObject` which offers an easier to under behavior. diff --git a/crates/sui-indexer-builder/src/indexer_builder.rs b/crates/sui-indexer-builder/src/indexer_builder.rs index 0c05900f1bc54..b89ff975ae562 100644 --- a/crates/sui-indexer-builder/src/indexer_builder.rs +++ b/crates/sui-indexer-builder/src/indexer_builder.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use anyhow::Error; use async_trait::async_trait; use futures::StreamExt; -use prometheus::{IntCounterVec, IntGaugeVec}; +use prometheus::{IntCounterVec, IntGauge, IntGaugeVec}; use tokio::task::JoinHandle; use crate::{Task, Tasks}; @@ -396,6 +396,14 @@ pub trait Datasource: Sync + Send { .with_label_values(&[&format!("{}-{}", task_name_prefix, task_type_label)]), ); let is_live_task = task.is_live_task; + let _live_tasks_tracker = if is_live_task { + Some(LiveTasksTracker::new( + self.get_inflight_live_tasks_metrics().clone(), + &task_name, + )) + } else { + None + }; let join_handle = self.start_data_retrieval(task.clone(), data_sender).await?; let processed_checkpoints_metrics = self .get_tasks_processed_checkpoints_metric() @@ -414,7 +422,13 @@ pub trait Datasource: Sync + Send { let mut stream = mysten_metrics::metered_channel::ReceiverStream::new(data_rx) .ready_chunks(ingestion_batch_size); let mut last_saved_checkpoint = None; - while let Some(batch) = stream.next().await { + loop { + let batch_option = stream.next().await; + if batch_option.is_none() { + tracing::error!(task_name, "Data stream ended unexpectedly"); + break; + } + let batch = batch_option.unwrap(); let mut max_height = 0; let mut heights = vec![]; let mut data = vec![]; @@ -513,7 +527,9 @@ pub trait Datasource: Sync + Send { if let Some(m) = &remaining_checkpoints_metric { m.set(0) } - join_handle.await? + join_handle.await?.tap_err(|err| { + tracing::error!(task_name, "Data retrieval task failed: {:?}", err); + }) } async fn start_data_retrieval( @@ -529,6 +545,8 @@ pub trait Datasource: Sync + Send { fn get_tasks_remaining_checkpoints_metric(&self) -> &IntGaugeVec; fn get_tasks_processed_checkpoints_metric(&self) -> &IntCounterVec; + + fn get_inflight_live_tasks_metrics(&self) -> &IntGaugeVec; } pub enum BackfillStrategy { @@ -540,3 +558,21 @@ pub enum BackfillStrategy { pub trait DataMapper: Sync + Send + Clone { fn map(&self, data: T) -> Result, anyhow::Error>; } + +struct LiveTasksTracker { + gauge: IntGauge, +} + +impl LiveTasksTracker { + pub fn new(metrics: IntGaugeVec, task_name: &str) -> Self { + let gauge = metrics.with_label_values(&[task_name]); + gauge.inc(); + Self { gauge } + } +} + +impl Drop for LiveTasksTracker { + fn drop(&mut self) { + self.gauge.dec(); + } +} diff --git a/crates/sui-indexer-builder/tests/indexer_test_utils.rs b/crates/sui-indexer-builder/tests/indexer_test_utils.rs index 8d8d47be92274..55dcbb5d7354c 100644 --- a/crates/sui-indexer-builder/tests/indexer_test_utils.rs +++ b/crates/sui-indexer-builder/tests/indexer_test_utils.rs @@ -24,6 +24,7 @@ pub struct TestDatasource { pub genesis_checkpoint: u64, pub gauge_metric: IntGaugeVec, pub counter_metric: IntCounterVec, + pub inflight_live_tasks: IntGaugeVec, } #[async_trait] @@ -67,6 +68,11 @@ where // This is dummy &self.counter_metric } + + fn get_inflight_live_tasks_metrics(&self) -> &IntGaugeVec { + // This is dummy + &self.inflight_live_tasks + } } #[derive(Clone, Debug, Default)] diff --git a/crates/sui-indexer-builder/tests/indexer_tests.rs b/crates/sui-indexer-builder/tests/indexer_tests.rs index 5cbfdb4da4828..b968dd710ee3c 100644 --- a/crates/sui-indexer-builder/tests/indexer_tests.rs +++ b/crates/sui-indexer-builder/tests/indexer_tests.rs @@ -22,8 +22,9 @@ async fn indexer_simple_backfill_task_test() { data: data.clone(), live_task_starting_checkpoint: 5, genesis_checkpoint: 0, - gauge_metric: new_gauge_vec(®istry), + gauge_metric: new_gauge_vec(®istry, "foo"), counter_metric: new_counter_vec(®istry), + inflight_live_tasks: new_gauge_vec(®istry, "bar"), }; let persistent = InMemoryPersistent::new(); let mut indexer = IndexerBuilder::new( @@ -63,8 +64,9 @@ async fn indexer_partitioned_backfill_task_test() { data: data.clone(), live_task_starting_checkpoint: 35, genesis_checkpoint: 0, - gauge_metric: new_gauge_vec(®istry), + gauge_metric: new_gauge_vec(®istry, "foo"), counter_metric: new_counter_vec(®istry), + inflight_live_tasks: new_gauge_vec(®istry, "bar"), }; let persistent = InMemoryPersistent::new(); let mut indexer = IndexerBuilder::new( @@ -110,8 +112,9 @@ async fn indexer_partitioned_task_with_data_already_in_db_test1() { data: data.clone(), live_task_starting_checkpoint: 31, genesis_checkpoint: 0, - gauge_metric: new_gauge_vec(®istry), + gauge_metric: new_gauge_vec(®istry, "foo"), counter_metric: new_counter_vec(®istry), + inflight_live_tasks: new_gauge_vec(®istry, "bar"), }; let persistent = InMemoryPersistent::new(); persistent.data.lock().await.append(&mut (0..=30).collect()); @@ -162,8 +165,9 @@ async fn indexer_partitioned_task_with_data_already_in_db_test2() { data: data.clone(), live_task_starting_checkpoint: 35, genesis_checkpoint: 0, - gauge_metric: new_gauge_vec(®istry), + gauge_metric: new_gauge_vec(®istry, "foo"), counter_metric: new_counter_vec(®istry), + inflight_live_tasks: new_gauge_vec(®istry, "bar"), }; let persistent = InMemoryPersistent::new(); persistent.data.lock().await.append(&mut (0..=30).collect()); @@ -216,8 +220,9 @@ async fn indexer_partitioned_task_with_data_already_in_db_test3() { data: data.clone(), live_task_starting_checkpoint: 28, genesis_checkpoint: 0, - gauge_metric: new_gauge_vec(®istry), + gauge_metric: new_gauge_vec(®istry, "foo"), counter_metric: new_counter_vec(®istry), + inflight_live_tasks: new_gauge_vec(®istry, "bar"), }; let persistent = InMemoryPersistent::new(); persistent.progress_store.lock().await.insert( @@ -274,8 +279,9 @@ async fn indexer_partitioned_task_with_data_already_in_db_test4() { data: data.clone(), live_task_starting_checkpoint: 35, genesis_checkpoint: 0, - gauge_metric: new_gauge_vec(®istry), + gauge_metric: new_gauge_vec(®istry, "foo"), counter_metric: new_counter_vec(®istry), + inflight_live_tasks: new_gauge_vec(®istry, "bar"), }; let persistent = InMemoryPersistent::new(); persistent.progress_store.lock().await.insert( @@ -336,8 +342,9 @@ async fn indexer_with_existing_live_task1() { data: data.clone(), live_task_starting_checkpoint: 35, genesis_checkpoint: 10, - gauge_metric: new_gauge_vec(®istry), + gauge_metric: new_gauge_vec(®istry, "foo"), counter_metric: new_counter_vec(®istry), + inflight_live_tasks: new_gauge_vec(®istry, "bar"), }; let persistent = InMemoryPersistent::new(); persistent.progress_store.lock().await.insert( @@ -382,8 +389,9 @@ async fn indexer_with_existing_live_task2() { data: data.clone(), live_task_starting_checkpoint: 25, genesis_checkpoint: 10, - gauge_metric: new_gauge_vec(®istry), + gauge_metric: new_gauge_vec(®istry, "foo"), counter_metric: new_counter_vec(®istry), + inflight_live_tasks: new_gauge_vec(®istry, "bar"), }; let persistent = InMemoryPersistent::new(); persistent.progress_store.lock().await.insert( @@ -438,8 +446,9 @@ async fn resume_test() { data: data.clone(), live_task_starting_checkpoint: 31, genesis_checkpoint: 0, - gauge_metric: new_gauge_vec(®istry), + gauge_metric: new_gauge_vec(®istry, "foo"), counter_metric: new_counter_vec(®istry), + inflight_live_tasks: new_gauge_vec(®istry, "bar"), }; let persistent = InMemoryPersistent::new(); persistent.progress_store.lock().await.insert( @@ -478,9 +487,8 @@ async fn resume_test() { assert_eq!((10..=50u64).collect::>(), recorded_data); } -fn new_gauge_vec(registry: &Registry) -> IntGaugeVec { - register_int_gauge_vec_with_registry!("whatever_gauge", "whatever", &["whatever"], registry,) - .unwrap() +fn new_gauge_vec(registry: &Registry, name: &str) -> IntGaugeVec { + register_int_gauge_vec_with_registry!(name, "whatever", &["whatever"], registry,).unwrap() } fn new_counter_vec(registry: &Registry) -> IntCounterVec { diff --git a/crates/sui-indexer/Cargo.toml b/crates/sui-indexer/Cargo.toml index 573a50a6f0a9a..fa4490741b163 100644 --- a/crates/sui-indexer/Cargo.toml +++ b/crates/sui-indexer/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] anyhow.workspace = true +rand = "0.8.5" async-trait.workspace = true axum.workspace = true backoff.workspace = true @@ -31,16 +32,20 @@ regex.workspace = true serde.workspace = true serde_json.workspace = true serde_with.workspace = true +strum.workspace = true +strum_macros.workspace = true tap.workspace = true tempfile.workspace = true thiserror.workspace = true tokio = { workspace = true, features = ["full"] } tokio-util = { workspace = true, features = ["rt"] } +toml.workspace = true tracing.workspace = true url.workspace = true fastcrypto = { workspace = true, features = ["copy_key"] } mysten-metrics.workspace = true +simulacrum.workspace = true sui-config.workspace = true sui-archival.workspace = true sui-core.workspace = true @@ -66,6 +71,8 @@ move-binary-format.workspace = true diesel_migrations.workspace = true cached.workspace = true +tokio-stream.workspace = true +dashmap.workspace = true [dev-dependencies] sui-keys.workspace = true @@ -74,7 +81,6 @@ sui-test-transaction-builder.workspace = true test-cluster.workspace = true ntest.workspace = true criterion.workspace = true -simulacrum.workspace = true [[bin]] name = "sui-indexer" diff --git a/crates/sui-indexer/README.md b/crates/sui-indexer/README.md index dd57acf635aa1..73593434dc2fa 100644 --- a/crates/sui-indexer/README.md +++ b/crates/sui-indexer/README.md @@ -32,7 +32,7 @@ See the [docs](https://docs.sui.io/guides/developer/getting-started/local-networ Start a local network using the `sui` binary: ```sh -cargo run --bin sui -- start --with-faucet --force-regenesis +cargo run --bin sui -- start --with-faucet --force-regenesis ``` If you want to run a local network with the indexer enabled (note that `libpq` is required), you can run the following command after following the steps in the next section to set up an indexer DB: @@ -65,11 +65,12 @@ cargo run --bin sui-indexer -- --db-url "" --rpc-client-url "https ``` cargo run --bin sui-indexer -- --db-url "" --rpc-client-url "https://fullnode.devnet.sui.io:443" --rpc-server-worker ``` -More flags info can be found in this [file](https://github.com/MystenLabs/sui/blob/main/crates/sui-indexer/src/lib.rs#L83-L123). +More flags info can be found in this [file](src/main.rs#L41). + ### DB reset -Run this command under `sui/crates/sui-indexer`, which will wipe DB; In case of schema changes in `.sql` files, this will also update corresponding `schema.rs` file. +When making db-related changes, you may find yourself having to run migrations and reset dbs often. The commands below are how you can invoke these actions. ```sh -diesel database reset --database-url="" +cargo run --bin sui-indexer -- --database-url "" reset-database --force ``` ## Steps to run locally (TiDB) @@ -124,3 +125,9 @@ Note that you need an existing database for this to work. Using the DATABASE_URL # Change the RPC_CLIENT_URL to http://0.0.0.0:9000 to run indexer against local validator & fullnode cargo run --bin sui-indexer --features mysql-feature --no-default-features -- --db-url "" --rpc-client-url "https://fullnode.devnet.sui.io:443" --fullnode-sync-worker --reset-db ``` + +### Extending the indexer + +To add a new table, run `diesel migration generate your_table_name`, and modify the newly created `up.sql` and `down.sql` files. + +You would apply the migration with `diesel migration run`, and run the script in `./scripts/generate_indexer_schema.sh` to update the `schema.rs` file. diff --git a/crates/sui-indexer/migrations/pg/2023-10-06-204335_tx_indices/up.sql b/crates/sui-indexer/migrations/pg/2023-10-06-204335_tx_indices/up.sql index 0bcd824e31254..563df854b97ef 100644 --- a/crates/sui-indexer/migrations/pg/2023-10-06-204335_tx_indices/up.sql +++ b/crates/sui-indexer/migrations/pg/2023-10-06-204335_tx_indices/up.sql @@ -18,7 +18,6 @@ CREATE TABLE tx_input_objects ( sender BYTEA NOT NULL, PRIMARY KEY(object_id, tx_sequence_number) ); -CREATE INDEX tx_input_objects_tx_sequence_number_index ON tx_input_objects (tx_sequence_number); CREATE INDEX tx_input_objects_sender ON tx_input_objects (sender, object_id, tx_sequence_number); CREATE TABLE tx_changed_objects ( @@ -27,7 +26,6 @@ CREATE TABLE tx_changed_objects ( sender BYTEA NOT NULL, PRIMARY KEY(object_id, tx_sequence_number) ); -CREATE INDEX tx_changed_objects_tx_sequence_number_index ON tx_changed_objects (tx_sequence_number); CREATE INDEX tx_changed_objects_sender ON tx_changed_objects (sender, object_id, tx_sequence_number); CREATE TABLE tx_calls_pkg ( @@ -61,7 +59,6 @@ CREATE TABLE tx_digests ( tx_digest BYTEA PRIMARY KEY, tx_sequence_number BIGINT NOT NULL ); -CREATE INDEX tx_digests_tx_sequence_number ON tx_digests (tx_sequence_number); CREATE TABLE tx_kinds ( tx_sequence_number BIGINT NOT NULL, diff --git a/crates/sui-indexer/migrations/pg/2024-09-10-195655_drop-df-columns/down.sql b/crates/sui-indexer/migrations/pg/2024-09-10-195655_drop-df-columns/down.sql new file mode 100644 index 0000000000000..8490a091b30f4 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-10-195655_drop-df-columns/down.sql @@ -0,0 +1,15 @@ +ALTER TABLE objects +ADD COLUMN df_name bytea, +ADD COLUMN df_object_type text, +ADD COLUMN df_object_id bytea, +ADD COLUMN checkpoint_sequence_number bigint; + +ALTER TABLE objects_snapshot +ADD COLUMN df_name bytea, +ADD COLUMN df_object_type text, +ADD COLUMN df_object_id bytea; + +ALTER TABLE objects_history +ADD COLUMN df_name bytea, +ADD COLUMN df_object_type text, +ADD COLUMN df_object_id bytea; diff --git a/crates/sui-indexer/migrations/pg/2024-09-10-195655_drop-df-columns/up.sql b/crates/sui-indexer/migrations/pg/2024-09-10-195655_drop-df-columns/up.sql new file mode 100644 index 0000000000000..4782193c4edc9 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-10-195655_drop-df-columns/up.sql @@ -0,0 +1,15 @@ +ALTER TABLE objects +DROP COLUMN df_name, +DROP COLUMN df_object_type, +DROP COLUMN df_object_id, +DROP COLUMN checkpoint_sequence_number; + +ALTER TABLE objects_snapshot +DROP COLUMN df_name, +DROP COLUMN df_object_type, +DROP COLUMN df_object_id; + +ALTER TABLE objects_history +DROP COLUMN df_name, +DROP COLUMN df_object_type, +DROP COLUMN df_object_id; diff --git a/crates/sui-indexer/migrations/pg/2024-09-12-213234_watermarks/down.sql b/crates/sui-indexer/migrations/pg/2024-09-12-213234_watermarks/down.sql new file mode 100644 index 0000000000000..e9de336153f62 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-12-213234_watermarks/down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS watermarks; diff --git a/crates/sui-indexer/migrations/pg/2024-09-12-213234_watermarks/up.sql b/crates/sui-indexer/migrations/pg/2024-09-12-213234_watermarks/up.sql new file mode 100644 index 0000000000000..73bdc70055246 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-12-213234_watermarks/up.sql @@ -0,0 +1,34 @@ +CREATE TABLE IF NOT EXISTS watermarks +( + -- The pipeline governed by this watermark, i.e `epochs`, `checkpoints`, + -- `transactions`. + pipeline TEXT PRIMARY KEY, + -- Inclusive upper epoch bound for this entity's data. Committer updates + -- this field. Pruner uses this to determine if pruning is necessary based + -- on the retention policy. + epoch_hi_inclusive BIGINT NOT NULL, + -- Inclusive upper checkpoint bound for this entity's data. Committer + -- updates this field. All data of this entity in the checkpoint must be + -- persisted before advancing this watermark. The committer refers to this + -- on disaster recovery to resume writing. + checkpoint_hi_inclusive BIGINT NOT NULL, + -- Exclusive upper transaction sequence number bound for this entity's + -- data. Committer updates this field. + tx_hi BIGINT NOT NULL, + -- Inclusive lower epoch bound for this entity's data. Pruner updates this + -- field when the epoch range exceeds the retention policy. + epoch_lo BIGINT NOT NULL, + -- Inclusive low watermark that the pruner advances. Corresponds to the + -- epoch id, checkpoint sequence number, or tx sequence number depending on + -- the entity. Data before this watermark is considered pruned by a reader. + -- The underlying data may still exist in the db instance. + reader_lo BIGINT NOT NULL, + -- Updated using the database's current timestamp when the pruner sees that + -- some data needs to be dropped. The pruner uses this column to determine + -- whether to prune or wait long enough that all in-flight reads complete + -- or timeout before it acts on an updated watermark. + timestamp_ms BIGINT NOT NULL, + -- Column used by the pruner to track its true progress. Data below this + -- watermark can be immediately pruned. + pruner_hi BIGINT NOT NULL +); diff --git a/crates/sui-indexer/migrations/pg/2024-09-18-003318_epochs_add_json_system_state/down.sql b/crates/sui-indexer/migrations/pg/2024-09-18-003318_epochs_add_json_system_state/down.sql new file mode 100644 index 0000000000000..9de241cfe20dc --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-18-003318_epochs_add_json_system_state/down.sql @@ -0,0 +1 @@ +ALTER TABLE epochs DROP COLUMN system_state_summary_json; diff --git a/crates/sui-indexer/migrations/pg/2024-09-18-003318_epochs_add_json_system_state/up.sql b/crates/sui-indexer/migrations/pg/2024-09-18-003318_epochs_add_json_system_state/up.sql new file mode 100644 index 0000000000000..4dce2a5a9ecfd --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-18-003318_epochs_add_json_system_state/up.sql @@ -0,0 +1 @@ +ALTER TABLE epochs ADD COLUMN system_state_summary_json JSONB; diff --git a/crates/sui-indexer/migrations/pg/2024-09-19-011238_raw_checkpoints/down.sql b/crates/sui-indexer/migrations/pg/2024-09-19-011238_raw_checkpoints/down.sql new file mode 100644 index 0000000000000..9cfef48c9b5f6 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-19-011238_raw_checkpoints/down.sql @@ -0,0 +1 @@ +DROP TABLE raw_checkpoints; diff --git a/crates/sui-indexer/migrations/pg/2024-09-19-011238_raw_checkpoints/up.sql b/crates/sui-indexer/migrations/pg/2024-09-19-011238_raw_checkpoints/up.sql new file mode 100644 index 0000000000000..26791856ff4c9 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-19-011238_raw_checkpoints/up.sql @@ -0,0 +1,6 @@ +CREATE TABLE raw_checkpoints +( + sequence_number BIGINT PRIMARY KEY, + certified_checkpoint BYTEA NOT NULL, + checkpoint_contents BYTEA NOT NULL +); diff --git a/crates/sui-indexer/migrations/pg/2024-09-24-213054_epochs_system_state_nullable/down.sql b/crates/sui-indexer/migrations/pg/2024-09-24-213054_epochs_system_state_nullable/down.sql new file mode 100644 index 0000000000000..e6697b4849a4e --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-24-213054_epochs_system_state_nullable/down.sql @@ -0,0 +1 @@ +ALTER TABLE epochs ALTER COLUMN system_state SET NOT NULL; diff --git a/crates/sui-indexer/migrations/pg/2024-09-24-213054_epochs_system_state_nullable/up.sql b/crates/sui-indexer/migrations/pg/2024-09-24-213054_epochs_system_state_nullable/up.sql new file mode 100644 index 0000000000000..a6e7f167c48cc --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-24-213054_epochs_system_state_nullable/up.sql @@ -0,0 +1 @@ +ALTER TABLE epochs ALTER COLUMN system_state DROP NOT NULL; diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135801_event_emit_module_pruning_index/down.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135801_event_emit_module_pruning_index/down.sql new file mode 100644 index 0000000000000..825855f3d700b --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135801_event_emit_module_pruning_index/down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS event_emit_module_tx_sequence_number; diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135801_event_emit_module_pruning_index/metadata.toml b/crates/sui-indexer/migrations/pg/2024-09-25-135801_event_emit_module_pruning_index/metadata.toml new file mode 100644 index 0000000000000..79e9221c1f2a4 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135801_event_emit_module_pruning_index/metadata.toml @@ -0,0 +1 @@ +run_in_transaction = false diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135801_event_emit_module_pruning_index/up.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135801_event_emit_module_pruning_index/up.sql new file mode 100644 index 0000000000000..ac69bc8488758 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135801_event_emit_module_pruning_index/up.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY IF NOT EXISTS + event_emit_module_tx_sequence_number +ON event_emit_module (tx_sequence_number); diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135802_event_emit_package_pruning_index/down.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135802_event_emit_package_pruning_index/down.sql new file mode 100644 index 0000000000000..30b5fdb6cead6 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135802_event_emit_package_pruning_index/down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS event_emit_package_tx_sequence_number; diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135802_event_emit_package_pruning_index/metadata.toml b/crates/sui-indexer/migrations/pg/2024-09-25-135802_event_emit_package_pruning_index/metadata.toml new file mode 100644 index 0000000000000..79e9221c1f2a4 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135802_event_emit_package_pruning_index/metadata.toml @@ -0,0 +1 @@ +run_in_transaction = false diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135802_event_emit_package_pruning_index/up.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135802_event_emit_package_pruning_index/up.sql new file mode 100644 index 0000000000000..231ab598b4766 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135802_event_emit_package_pruning_index/up.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY IF NOT EXISTS + event_emit_package_tx_sequence_number +ON event_emit_package (tx_sequence_number); diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135803_event_senders_pruning_index/down.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135803_event_senders_pruning_index/down.sql new file mode 100644 index 0000000000000..e9b5b0b903ed5 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135803_event_senders_pruning_index/down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS event_senders_tx_sequence_number; diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135803_event_senders_pruning_index/metadata.toml b/crates/sui-indexer/migrations/pg/2024-09-25-135803_event_senders_pruning_index/metadata.toml new file mode 100644 index 0000000000000..79e9221c1f2a4 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135803_event_senders_pruning_index/metadata.toml @@ -0,0 +1 @@ +run_in_transaction = false diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135803_event_senders_pruning_index/up.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135803_event_senders_pruning_index/up.sql new file mode 100644 index 0000000000000..b5883b8a3a4ce --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135803_event_senders_pruning_index/up.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY IF NOT EXISTS + event_senders_tx_sequence_number +ON event_senders (tx_sequence_number); diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135804_event_struct_instantiation_pruning_index/down.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135804_event_struct_instantiation_pruning_index/down.sql new file mode 100644 index 0000000000000..43b1d27d9ed2e --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135804_event_struct_instantiation_pruning_index/down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS event_struct_instantiation_tx_sequence_number; diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135804_event_struct_instantiation_pruning_index/metadata.toml b/crates/sui-indexer/migrations/pg/2024-09-25-135804_event_struct_instantiation_pruning_index/metadata.toml new file mode 100644 index 0000000000000..79e9221c1f2a4 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135804_event_struct_instantiation_pruning_index/metadata.toml @@ -0,0 +1 @@ +run_in_transaction = false diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135804_event_struct_instantiation_pruning_index/up.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135804_event_struct_instantiation_pruning_index/up.sql new file mode 100644 index 0000000000000..7847620e936f3 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135804_event_struct_instantiation_pruning_index/up.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY IF NOT EXISTS + event_struct_instantiation_tx_sequence_number +ON event_struct_instantiation (tx_sequence_number); diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135805_event_struct_module_pruning_index/down.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135805_event_struct_module_pruning_index/down.sql new file mode 100644 index 0000000000000..76606ab0400a6 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135805_event_struct_module_pruning_index/down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS event_struct_module_tx_sequence_number; diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135805_event_struct_module_pruning_index/metadata.toml b/crates/sui-indexer/migrations/pg/2024-09-25-135805_event_struct_module_pruning_index/metadata.toml new file mode 100644 index 0000000000000..79e9221c1f2a4 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135805_event_struct_module_pruning_index/metadata.toml @@ -0,0 +1 @@ +run_in_transaction = false diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135805_event_struct_module_pruning_index/up.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135805_event_struct_module_pruning_index/up.sql new file mode 100644 index 0000000000000..748a4095da169 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135805_event_struct_module_pruning_index/up.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY IF NOT EXISTS + event_struct_module_tx_sequence_number +ON event_struct_module (tx_sequence_number); diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135806_event_struct_name_pruning_index/down.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135806_event_struct_name_pruning_index/down.sql new file mode 100644 index 0000000000000..944405cf172e3 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135806_event_struct_name_pruning_index/down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS event_struct_name_tx_sequence_number; diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135806_event_struct_name_pruning_index/metadata.toml b/crates/sui-indexer/migrations/pg/2024-09-25-135806_event_struct_name_pruning_index/metadata.toml new file mode 100644 index 0000000000000..79e9221c1f2a4 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135806_event_struct_name_pruning_index/metadata.toml @@ -0,0 +1 @@ +run_in_transaction = false diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135806_event_struct_name_pruning_index/up.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135806_event_struct_name_pruning_index/up.sql new file mode 100644 index 0000000000000..2ca251c139af9 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135806_event_struct_name_pruning_index/up.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY IF NOT EXISTS + event_struct_name_tx_sequence_number +ON event_struct_name (tx_sequence_number); diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135807_event_struct_package_pruning_index/down.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135807_event_struct_package_pruning_index/down.sql new file mode 100644 index 0000000000000..40fde7e4578b6 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135807_event_struct_package_pruning_index/down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS event_struct_package_tx_sequence_number; diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135807_event_struct_package_pruning_index/metadata.toml b/crates/sui-indexer/migrations/pg/2024-09-25-135807_event_struct_package_pruning_index/metadata.toml new file mode 100644 index 0000000000000..79e9221c1f2a4 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135807_event_struct_package_pruning_index/metadata.toml @@ -0,0 +1 @@ +run_in_transaction = false diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135807_event_struct_package_pruning_index/up.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135807_event_struct_package_pruning_index/up.sql new file mode 100644 index 0000000000000..00e88fcfb5273 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135807_event_struct_package_pruning_index/up.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY IF NOT EXISTS + event_struct_package_tx_sequence_number +ON event_struct_package (tx_sequence_number); diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135808_tx_calls_fun_pruning_index/down.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135808_tx_calls_fun_pruning_index/down.sql new file mode 100644 index 0000000000000..da1519d208f7a --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135808_tx_calls_fun_pruning_index/down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS tx_calls_fun_tx_sequence_number; diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135808_tx_calls_fun_pruning_index/metadata.toml b/crates/sui-indexer/migrations/pg/2024-09-25-135808_tx_calls_fun_pruning_index/metadata.toml new file mode 100644 index 0000000000000..79e9221c1f2a4 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135808_tx_calls_fun_pruning_index/metadata.toml @@ -0,0 +1 @@ +run_in_transaction = false diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135808_tx_calls_fun_pruning_index/up.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135808_tx_calls_fun_pruning_index/up.sql new file mode 100644 index 0000000000000..c868f6e55a66f --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135808_tx_calls_fun_pruning_index/up.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY IF NOT EXISTS + tx_calls_fun_tx_sequence_number +ON tx_calls_fun (tx_sequence_number); diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135809_tx_calls_mod_pruning_index/down.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135809_tx_calls_mod_pruning_index/down.sql new file mode 100644 index 0000000000000..16bf8eb87dbef --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135809_tx_calls_mod_pruning_index/down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS tx_calls_mod_tx_sequence_number; diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135809_tx_calls_mod_pruning_index/metadata.toml b/crates/sui-indexer/migrations/pg/2024-09-25-135809_tx_calls_mod_pruning_index/metadata.toml new file mode 100644 index 0000000000000..79e9221c1f2a4 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135809_tx_calls_mod_pruning_index/metadata.toml @@ -0,0 +1 @@ +run_in_transaction = false diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135809_tx_calls_mod_pruning_index/up.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135809_tx_calls_mod_pruning_index/up.sql new file mode 100644 index 0000000000000..debc152d98f2d --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135809_tx_calls_mod_pruning_index/up.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY IF NOT EXISTS + tx_calls_mod_tx_sequence_number +ON tx_calls_mod (tx_sequence_number); diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135810_tx_calls_pkg_pruning_index/down.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135810_tx_calls_pkg_pruning_index/down.sql new file mode 100644 index 0000000000000..f6ef795109c61 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135810_tx_calls_pkg_pruning_index/down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS tx_calls_pkg_tx_sequence_number; diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135810_tx_calls_pkg_pruning_index/metadata.toml b/crates/sui-indexer/migrations/pg/2024-09-25-135810_tx_calls_pkg_pruning_index/metadata.toml new file mode 100644 index 0000000000000..79e9221c1f2a4 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135810_tx_calls_pkg_pruning_index/metadata.toml @@ -0,0 +1 @@ +run_in_transaction = false diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135810_tx_calls_pkg_pruning_index/up.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135810_tx_calls_pkg_pruning_index/up.sql new file mode 100644 index 0000000000000..0e6c1f1bf7d30 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135810_tx_calls_pkg_pruning_index/up.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY IF NOT EXISTS + tx_calls_pkg_tx_sequence_number +ON tx_calls_pkg (tx_sequence_number); diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135811_tx_changed_objects_pruning_index/down.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135811_tx_changed_objects_pruning_index/down.sql new file mode 100644 index 0000000000000..1dfcf480b9e86 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135811_tx_changed_objects_pruning_index/down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS tx_changed_objects_tx_sequence_number; diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135811_tx_changed_objects_pruning_index/metadata.toml b/crates/sui-indexer/migrations/pg/2024-09-25-135811_tx_changed_objects_pruning_index/metadata.toml new file mode 100644 index 0000000000000..79e9221c1f2a4 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135811_tx_changed_objects_pruning_index/metadata.toml @@ -0,0 +1 @@ +run_in_transaction = false diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135811_tx_changed_objects_pruning_index/up.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135811_tx_changed_objects_pruning_index/up.sql new file mode 100644 index 0000000000000..4ef5b459dbf05 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135811_tx_changed_objects_pruning_index/up.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY IF NOT EXISTS + tx_changed_objects_tx_sequence_number +ON tx_changed_objects (tx_sequence_number); diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135812_tx_digests_pruning_index/down.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135812_tx_digests_pruning_index/down.sql new file mode 100644 index 0000000000000..d0bd714bc60b2 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135812_tx_digests_pruning_index/down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS tx_digests_tx_sequence_number; diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135812_tx_digests_pruning_index/metadata.toml b/crates/sui-indexer/migrations/pg/2024-09-25-135812_tx_digests_pruning_index/metadata.toml new file mode 100644 index 0000000000000..79e9221c1f2a4 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135812_tx_digests_pruning_index/metadata.toml @@ -0,0 +1 @@ +run_in_transaction = false diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135812_tx_digests_pruning_index/up.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135812_tx_digests_pruning_index/up.sql new file mode 100644 index 0000000000000..efdff9cbe7a56 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135812_tx_digests_pruning_index/up.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY IF NOT EXISTS + tx_digests_tx_sequence_number +ON tx_digests (tx_sequence_number); diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135813_tx_input_objects_pruning_index/down.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135813_tx_input_objects_pruning_index/down.sql new file mode 100644 index 0000000000000..5061884270f6b --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135813_tx_input_objects_pruning_index/down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS tx_input_objects_tx_sequence_number; diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135813_tx_input_objects_pruning_index/metadata.toml b/crates/sui-indexer/migrations/pg/2024-09-25-135813_tx_input_objects_pruning_index/metadata.toml new file mode 100644 index 0000000000000..79e9221c1f2a4 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135813_tx_input_objects_pruning_index/metadata.toml @@ -0,0 +1 @@ +run_in_transaction = false diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135813_tx_input_objects_pruning_index/up.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135813_tx_input_objects_pruning_index/up.sql new file mode 100644 index 0000000000000..39d01c598a4a5 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135813_tx_input_objects_pruning_index/up.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY IF NOT EXISTS + tx_input_objects_tx_sequence_number +ON tx_input_objects (tx_sequence_number); diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135814_tx_kinds_pruning_index/down.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135814_tx_kinds_pruning_index/down.sql new file mode 100644 index 0000000000000..10f5e96b1ce17 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135814_tx_kinds_pruning_index/down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS tx_kinds_tx_sequence_number; diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135814_tx_kinds_pruning_index/metadata.toml b/crates/sui-indexer/migrations/pg/2024-09-25-135814_tx_kinds_pruning_index/metadata.toml new file mode 100644 index 0000000000000..79e9221c1f2a4 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135814_tx_kinds_pruning_index/metadata.toml @@ -0,0 +1 @@ +run_in_transaction = false diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135814_tx_kinds_pruning_index/up.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135814_tx_kinds_pruning_index/up.sql new file mode 100644 index 0000000000000..6227a18f8eb46 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135814_tx_kinds_pruning_index/up.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY IF NOT EXISTS + tx_kinds_tx_sequence_number +ON tx_kinds (tx_sequence_number); diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135815_tx_recipients_pruning_index/down.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135815_tx_recipients_pruning_index/down.sql new file mode 100644 index 0000000000000..138ba02741229 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135815_tx_recipients_pruning_index/down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS tx_recipients_tx_sequence_number; diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135815_tx_recipients_pruning_index/metadata.toml b/crates/sui-indexer/migrations/pg/2024-09-25-135815_tx_recipients_pruning_index/metadata.toml new file mode 100644 index 0000000000000..79e9221c1f2a4 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135815_tx_recipients_pruning_index/metadata.toml @@ -0,0 +1 @@ +run_in_transaction = false diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135815_tx_recipients_pruning_index/up.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135815_tx_recipients_pruning_index/up.sql new file mode 100644 index 0000000000000..d2294ac2561ed --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135815_tx_recipients_pruning_index/up.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY IF NOT EXISTS + tx_recipients_tx_sequence_number +ON tx_recipients (tx_sequence_number); diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135816_tx_senders_pruning_index/down.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135816_tx_senders_pruning_index/down.sql new file mode 100644 index 0000000000000..c09d44eb4660d --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135816_tx_senders_pruning_index/down.sql @@ -0,0 +1 @@ +DROP INDEX CONCURRENTLY IF EXISTS tx_senders_tx_sequence_number; diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135816_tx_senders_pruning_index/metadata.toml b/crates/sui-indexer/migrations/pg/2024-09-25-135816_tx_senders_pruning_index/metadata.toml new file mode 100644 index 0000000000000..79e9221c1f2a4 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135816_tx_senders_pruning_index/metadata.toml @@ -0,0 +1 @@ +run_in_transaction = false diff --git a/crates/sui-indexer/migrations/pg/2024-09-25-135816_tx_senders_pruning_index/up.sql b/crates/sui-indexer/migrations/pg/2024-09-25-135816_tx_senders_pruning_index/up.sql new file mode 100644 index 0000000000000..f22a06e2bb548 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-25-135816_tx_senders_pruning_index/up.sql @@ -0,0 +1,3 @@ +CREATE INDEX CONCURRENTLY IF NOT EXISTS + tx_senders_tx_sequence_number +ON tx_senders (tx_sequence_number); diff --git a/crates/sui-indexer/migrations/pg/2024-09-30-153705_add_event_sender/down.sql b/crates/sui-indexer/migrations/pg/2024-09-30-153705_add_event_sender/down.sql new file mode 100644 index 0000000000000..89e6710d92f1b --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-30-153705_add_event_sender/down.sql @@ -0,0 +1 @@ +ALTER TABLE events DROP COLUMN sender; diff --git a/crates/sui-indexer/migrations/pg/2024-09-30-153705_add_event_sender/up.sql b/crates/sui-indexer/migrations/pg/2024-09-30-153705_add_event_sender/up.sql new file mode 100644 index 0000000000000..7ea312c09453c --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-09-30-153705_add_event_sender/up.sql @@ -0,0 +1 @@ +ALTER TABLE events ADD COLUMN sender BYTEA; diff --git a/crates/sui-indexer/migrations/pg/2024-10-08-025030_partial_index_instead/down.sql b/crates/sui-indexer/migrations/pg/2024-10-08-025030_partial_index_instead/down.sql new file mode 100644 index 0000000000000..82659b80658c0 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-10-08-025030_partial_index_instead/down.sql @@ -0,0 +1,7 @@ +-- Drop the new partial indices +DROP INDEX IF EXISTS objects_history_owner_partial; +DROP INDEX IF EXISTS objects_history_coin_owner_partial; +DROP INDEX IF EXISTS objects_history_coin_only_partial; +DROP INDEX IF EXISTS objects_history_type_partial; +DROP INDEX IF EXISTS objects_history_package_module_name_full_type_partial; +DROP INDEX IF EXISTS objects_history_owner_package_module_name_full_type_partial; diff --git a/crates/sui-indexer/migrations/pg/2024-10-08-025030_partial_index_instead/up.sql b/crates/sui-indexer/migrations/pg/2024-10-08-025030_partial_index_instead/up.sql new file mode 100644 index 0000000000000..800f77b3f540b --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-10-08-025030_partial_index_instead/up.sql @@ -0,0 +1,18 @@ +-- Create new partial indices with object_status = 0 condition +CREATE INDEX IF NOT EXISTS objects_history_owner_partial ON objects_history (checkpoint_sequence_number, owner_type, owner_id) +WHERE owner_type BETWEEN 1 AND 2 AND owner_id IS NOT NULL AND object_status = 0; + +CREATE INDEX IF NOT EXISTS objects_history_coin_owner_partial ON objects_history (checkpoint_sequence_number, owner_id, coin_type, object_id) +WHERE coin_type IS NOT NULL AND owner_type = 1 AND object_status = 0; + +CREATE INDEX IF NOT EXISTS objects_history_coin_only_partial ON objects_history (checkpoint_sequence_number, coin_type, object_id) +WHERE coin_type IS NOT NULL AND object_status = 0; + +CREATE INDEX IF NOT EXISTS objects_history_type_partial ON objects_history (checkpoint_sequence_number, object_type) +WHERE object_status = 0; + +CREATE INDEX IF NOT EXISTS objects_history_package_module_name_full_type_partial ON objects_history (checkpoint_sequence_number, object_type_package, object_type_module, object_type_name, object_type) +WHERE object_status = 0; + +CREATE INDEX IF NOT EXISTS objects_history_owner_package_module_name_full_type_partial ON objects_history (checkpoint_sequence_number, owner_id, object_type_package, object_type_module, object_type_name, object_type) +WHERE object_status = 0; diff --git a/crates/sui-indexer/migrations/pg/2024-10-09-180628_add_network_total_transactions_to_epochs/down.sql b/crates/sui-indexer/migrations/pg/2024-10-09-180628_add_network_total_transactions_to_epochs/down.sql new file mode 100644 index 0000000000000..e088120452e58 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-10-09-180628_add_network_total_transactions_to_epochs/down.sql @@ -0,0 +1 @@ +ALTER TABLE epochs DROP COLUMN first_tx_sequence_number; diff --git a/crates/sui-indexer/migrations/pg/2024-10-09-180628_add_network_total_transactions_to_epochs/up.sql b/crates/sui-indexer/migrations/pg/2024-10-09-180628_add_network_total_transactions_to_epochs/up.sql new file mode 100644 index 0000000000000..becdb61fe5e83 --- /dev/null +++ b/crates/sui-indexer/migrations/pg/2024-10-09-180628_add_network_total_transactions_to_epochs/up.sql @@ -0,0 +1 @@ +ALTER TABLE epochs ADD COLUMN first_tx_sequence_number bigint; diff --git a/crates/sui-indexer/src/apis/mod.rs b/crates/sui-indexer/src/apis/mod.rs index 4ba62a36956ef..e797c7ecdc239 100644 --- a/crates/sui-indexer/src/apis/mod.rs +++ b/crates/sui-indexer/src/apis/mod.rs @@ -15,6 +15,6 @@ mod extended_api; pub mod governance_api; mod indexer_api; mod move_utils; -mod read_api; +pub mod read_api; mod transaction_builder_api; mod write_api; diff --git a/crates/sui-indexer/src/apis/read_api.rs b/crates/sui-indexer/src/apis/read_api.rs index f75e8a31c7ea1..78b8715e16ce7 100644 --- a/crates/sui-indexer/src/apis/read_api.rs +++ b/crates/sui-indexer/src/apis/read_api.rs @@ -24,7 +24,7 @@ use sui_types::digests::{ChainIdentifier, TransactionDigest}; use sui_types::sui_serde::BigInt; #[derive(Clone)] -pub(crate) struct ReadApi { +pub struct ReadApi { inner: IndexerReader, } diff --git a/crates/sui-indexer/src/backfill/backfill_instances/ingestion_backfills/ingestion_backfill_task.rs b/crates/sui-indexer/src/backfill/backfill_instances/ingestion_backfills/ingestion_backfill_task.rs new file mode 100644 index 0000000000000..2702f755c0842 --- /dev/null +++ b/crates/sui-indexer/src/backfill/backfill_instances/ingestion_backfills/ingestion_backfill_task.rs @@ -0,0 +1,98 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::backfill::backfill_instances::ingestion_backfills::IngestionBackfillTrait; +use crate::backfill::backfill_task::BackfillTask; +use crate::database::ConnectionPool; +use dashmap::DashMap; +use std::ops::RangeInclusive; +use std::sync::Arc; +use sui_data_ingestion_core::{setup_single_workflow, ReaderOptions, Worker}; +use sui_types::full_checkpoint_content::CheckpointData; +use sui_types::messages_checkpoint::CheckpointSequenceNumber; +use tokio::sync::Notify; + +pub struct IngestionBackfillTask { + ready_checkpoints: Arc>>, + notify: Arc, + _exit_sender: tokio::sync::oneshot::Sender<()>, +} + +impl IngestionBackfillTask { + pub async fn new(remote_store_url: String, start_checkpoint: CheckpointSequenceNumber) -> Self { + let ready_checkpoints = Arc::new(DashMap::new()); + let notify = Arc::new(Notify::new()); + let adapter: Adapter = Adapter { + ready_checkpoints: ready_checkpoints.clone(), + notify: notify.clone(), + }; + let reader_options = ReaderOptions { + batch_size: 200, + ..Default::default() + }; + let (executor, _exit_sender) = setup_single_workflow( + adapter, + remote_store_url, + start_checkpoint, + 200, + Some(reader_options), + ) + .await + .unwrap(); + tokio::task::spawn(async move { + executor.await.unwrap(); + }); + Self { + ready_checkpoints, + notify, + _exit_sender, + } + } +} + +pub struct Adapter { + ready_checkpoints: Arc>>, + notify: Arc, +} + +#[async_trait::async_trait] +impl Worker for Adapter { + type Result = (); + async fn process_checkpoint(&self, checkpoint: &CheckpointData) -> anyhow::Result<()> { + let processed = T::process_checkpoint(checkpoint); + self.ready_checkpoints + .insert(checkpoint.checkpoint_summary.sequence_number, processed); + self.notify.notify_waiters(); + Ok(()) + } +} + +#[async_trait::async_trait] +impl BackfillTask for IngestionBackfillTask { + async fn backfill_range(&self, pool: ConnectionPool, range: &RangeInclusive) { + let mut processed_data = vec![]; + let mut start = *range.start(); + let end = *range.end(); + loop { + while start <= end { + if let Some((_, processed)) = self + .ready_checkpoints + .remove(&(start as CheckpointSequenceNumber)) + { + processed_data.extend(processed); + start += 1; + } else { + break; + } + } + if start <= end { + self.notify.notified().await; + } else { + break; + } + } + // TODO: Limit the size of each chunk. + // postgres has a parameter limit of 65535, meaning that row_count * col_count <= 65536. + T::commit_chunk(pool.clone(), processed_data).await; + } +} diff --git a/crates/sui-indexer/src/backfill/backfill_instances/ingestion_backfills/mod.rs b/crates/sui-indexer/src/backfill/backfill_instances/ingestion_backfills/mod.rs new file mode 100644 index 0000000000000..17bbc29d7dc5c --- /dev/null +++ b/crates/sui-indexer/src/backfill/backfill_instances/ingestion_backfills/mod.rs @@ -0,0 +1,17 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +pub(crate) mod ingestion_backfill_task; +pub(crate) mod raw_checkpoints; +pub(crate) mod tx_affected_objects; + +use crate::database::ConnectionPool; +use sui_types::full_checkpoint_content::CheckpointData; + +#[async_trait::async_trait] +pub trait IngestionBackfillTrait: Send + Sync { + type ProcessedType: Send + Sync; + + fn process_checkpoint(checkpoint: &CheckpointData) -> Vec; + async fn commit_chunk(pool: ConnectionPool, processed_data: Vec); +} diff --git a/crates/sui-indexer/src/backfill/backfill_instances/ingestion_backfills/raw_checkpoints.rs b/crates/sui-indexer/src/backfill/backfill_instances/ingestion_backfills/raw_checkpoints.rs new file mode 100644 index 0000000000000..aec4f0263ee80 --- /dev/null +++ b/crates/sui-indexer/src/backfill/backfill_instances/ingestion_backfills/raw_checkpoints.rs @@ -0,0 +1,34 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::backfill::backfill_instances::ingestion_backfills::IngestionBackfillTrait; +use crate::database::ConnectionPool; +use crate::models::raw_checkpoints::StoredRawCheckpoint; +use crate::schema::raw_checkpoints::dsl::raw_checkpoints; +use diesel_async::RunQueryDsl; +use sui_types::full_checkpoint_content::CheckpointData; + +pub struct RawCheckpointsBackFill; + +#[async_trait::async_trait] +impl IngestionBackfillTrait for RawCheckpointsBackFill { + type ProcessedType = StoredRawCheckpoint; + + fn process_checkpoint(checkpoint: &CheckpointData) -> Vec { + vec![StoredRawCheckpoint { + sequence_number: checkpoint.checkpoint_summary.sequence_number as i64, + certified_checkpoint: bcs::to_bytes(&checkpoint.checkpoint_summary).unwrap(), + checkpoint_contents: bcs::to_bytes(&checkpoint.checkpoint_contents).unwrap(), + }] + } + + async fn commit_chunk(pool: ConnectionPool, processed_data: Vec) { + let mut conn = pool.get().await.unwrap(); + diesel::insert_into(raw_checkpoints) + .values(processed_data) + .on_conflict_do_nothing() + .execute(&mut conn) + .await + .unwrap(); + } +} diff --git a/crates/sui-indexer/src/backfill/backfill_instances/ingestion_backfills/tx_affected_objects.rs b/crates/sui-indexer/src/backfill/backfill_instances/ingestion_backfills/tx_affected_objects.rs new file mode 100644 index 0000000000000..4e6f6efa6a897 --- /dev/null +++ b/crates/sui-indexer/src/backfill/backfill_instances/ingestion_backfills/tx_affected_objects.rs @@ -0,0 +1,48 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::backfill::backfill_instances::ingestion_backfills::IngestionBackfillTrait; +use crate::database::ConnectionPool; +use crate::models::tx_indices::StoredTxAffectedObjects; +use crate::schema::tx_affected_objects; +use diesel_async::RunQueryDsl; +use sui_types::effects::TransactionEffectsAPI; +use sui_types::full_checkpoint_content::CheckpointData; + +pub struct TxAffectedObjectsBackfill; + +#[async_trait::async_trait] +impl IngestionBackfillTrait for TxAffectedObjectsBackfill { + type ProcessedType = StoredTxAffectedObjects; + + fn process_checkpoint(checkpoint: &CheckpointData) -> Vec { + let first_tx = checkpoint.checkpoint_summary.network_total_transactions as usize + - checkpoint.transactions.len(); + + checkpoint + .transactions + .iter() + .enumerate() + .flat_map(|(i, tx)| { + tx.effects + .object_changes() + .into_iter() + .map(move |change| StoredTxAffectedObjects { + tx_sequence_number: (first_tx + i) as i64, + affected: change.id.to_vec(), + sender: tx.transaction.sender_address().to_vec(), + }) + }) + .collect() + } + + async fn commit_chunk(pool: ConnectionPool, processed_data: Vec) { + let mut conn = pool.get().await.unwrap(); + diesel::insert_into(tx_affected_objects::table) + .values(processed_data) + .on_conflict_do_nothing() + .execute(&mut conn) + .await + .unwrap(); + } +} diff --git a/crates/sui-indexer/src/backfill/backfill_instances/mod.rs b/crates/sui-indexer/src/backfill/backfill_instances/mod.rs new file mode 100644 index 0000000000000..27c96dd6c9234 --- /dev/null +++ b/crates/sui-indexer/src/backfill/backfill_instances/mod.rs @@ -0,0 +1,47 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::backfill::backfill_instances::ingestion_backfills::ingestion_backfill_task::IngestionBackfillTask; +use crate::backfill::backfill_instances::ingestion_backfills::raw_checkpoints::RawCheckpointsBackFill; +use crate::backfill::backfill_instances::ingestion_backfills::tx_affected_objects::TxAffectedObjectsBackfill; +use crate::backfill::backfill_task::BackfillTask; +use crate::backfill::{BackfillTaskKind, IngestionBackfillKind}; +use std::sync::Arc; +use sui_types::messages_checkpoint::CheckpointSequenceNumber; + +mod ingestion_backfills; +mod sql_backfill; +mod system_state_summary_json; + +pub async fn get_backfill_task( + kind: BackfillTaskKind, + range_start: usize, +) -> Arc { + match kind { + BackfillTaskKind::SystemStateSummaryJson => { + Arc::new(system_state_summary_json::SystemStateSummaryJsonBackfill) + } + BackfillTaskKind::Sql { sql, key_column } => { + Arc::new(sql_backfill::SqlBackFill::new(sql, key_column)) + } + BackfillTaskKind::Ingestion { + kind, + remote_store_url, + } => match kind { + IngestionBackfillKind::RawCheckpoints => Arc::new( + IngestionBackfillTask::::new( + remote_store_url, + range_start as CheckpointSequenceNumber, + ) + .await, + ), + IngestionBackfillKind::TxAffectedObjects => Arc::new( + IngestionBackfillTask::::new( + remote_store_url, + range_start as CheckpointSequenceNumber, + ) + .await, + ), + }, + } +} diff --git a/crates/sui-indexer/src/backfill/backfill_instances/sql_backfill.rs b/crates/sui-indexer/src/backfill/backfill_instances/sql_backfill.rs new file mode 100644 index 0000000000000..543f077e2ba3b --- /dev/null +++ b/crates/sui-indexer/src/backfill/backfill_instances/sql_backfill.rs @@ -0,0 +1,36 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::backfill::backfill_task::BackfillTask; +use crate::database::ConnectionPool; +use async_trait::async_trait; +use diesel_async::RunQueryDsl; +use std::ops::RangeInclusive; + +pub struct SqlBackFill { + sql: String, + key_column: String, +} + +impl SqlBackFill { + pub fn new(sql: String, key_column: String) -> Self { + Self { sql, key_column } + } +} + +#[async_trait] +impl BackfillTask for SqlBackFill { + async fn backfill_range(&self, pool: ConnectionPool, range: &RangeInclusive) { + let mut conn = pool.get().await.unwrap(); + + let query = format!( + "{} WHERE {} BETWEEN {} AND {} ON CONFLICT DO NOTHING", + self.sql, + self.key_column, + *range.start(), + *range.end() + ); + + diesel::sql_query(query).execute(&mut conn).await.unwrap(); + } +} diff --git a/crates/sui-indexer/src/backfill/backfill_instances/sql_backfills/event_sender.sh b/crates/sui-indexer/src/backfill/backfill_instances/sql_backfills/event_sender.sh new file mode 100644 index 0000000000000..f214c55f7d7eb --- /dev/null +++ b/crates/sui-indexer/src/backfill/backfill_instances/sql_backfills/event_sender.sh @@ -0,0 +1,6 @@ +# Copyright (c) Mysten Labs, Inc. +# SPDX-License-Identifier: Apache-2.0 + +INDEXER=${INDEXER:-"sui-indexer"} +DB=${DB:-"postgres://postgres:postgrespw@localhost:5432/postgres"} +"$INDEXER" --database-url "$DB" run-back-fill "$1" "$2" sql "UPDATE events SET sender = CASE WHEN cardinality(senders) > 0 THEN senders[1] ELSE NULL END" checkpoint_sequence_number diff --git a/crates/sui-indexer/src/backfill/backfill_instances/sql_backfills/full_objects_history.sh b/crates/sui-indexer/src/backfill/backfill_instances/sql_backfills/full_objects_history.sh new file mode 100644 index 0000000000000..2492b477ed592 --- /dev/null +++ b/crates/sui-indexer/src/backfill/backfill_instances/sql_backfills/full_objects_history.sh @@ -0,0 +1,6 @@ +# Copyright (c) Mysten Labs, Inc. +# SPDX-License-Identifier: Apache-2.0 + +INDEXER=${INDEXER:-"sui-indexer"} +DB=${DB:-"postgres://postgres:postgrespw@localhost:5432/postgres"} +"$INDEXER" --database-url "$DB" run-back-fill "$1" "$2" sql "INSERT INTO full_objects_history (object_id, object_version, serialized_object) SELECT object_id, object_version, serialized_object FROM objects_history" checkpoint_sequence_number diff --git a/crates/sui-indexer/src/backfill/backfill_instances/sql_backfills/tx_affected_addresses.sh b/crates/sui-indexer/src/backfill/backfill_instances/sql_backfills/tx_affected_addresses.sh new file mode 100644 index 0000000000000..df1212e18bbc6 --- /dev/null +++ b/crates/sui-indexer/src/backfill/backfill_instances/sql_backfills/tx_affected_addresses.sh @@ -0,0 +1,7 @@ +# Copyright (c) Mysten Labs, Inc. +# SPDX-License-Identifier: Apache-2.0 + +INDEXER=${INDEXER:-"sui-indexer"} +DB=${DB:-"postgres://postgres:postgrespw@localhost:5432/postgres"} +"$INDEXER" --database-url "$DB" run-back-fill "$1" "$2" sql "INSERT INTO tx_affected_addresses SELECT tx_sequence_number, sender AS affected, sender FROM tx_senders" tx_sequence_number +"$INDEXER" --database-url "$DB" run-back-fill "$1" "$2" sql "INSERT INTO tx_affected_addresses SELECT tx_sequence_number, recipient AS affected, sender FROM tx_recipients" tx_sequence_number diff --git a/crates/sui-indexer/src/backfill/backfill_instances/system_state_summary_json.rs b/crates/sui-indexer/src/backfill/backfill_instances/system_state_summary_json.rs new file mode 100644 index 0000000000000..912abdd871a1c --- /dev/null +++ b/crates/sui-indexer/src/backfill/backfill_instances/system_state_summary_json.rs @@ -0,0 +1,56 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::backfill::backfill_task::BackfillTask; +use crate::database::ConnectionPool; +use crate::schema::epochs; +use async_trait::async_trait; +use diesel::{ExpressionMethods, QueryDsl}; +use diesel_async::{AsyncConnection, RunQueryDsl}; +use std::ops::RangeInclusive; +use sui_types::sui_system_state::sui_system_state_summary::SuiSystemStateSummary; + +pub struct SystemStateSummaryJsonBackfill; + +#[async_trait] +impl BackfillTask for SystemStateSummaryJsonBackfill { + async fn backfill_range(&self, pool: ConnectionPool, range: &RangeInclusive) { + let mut conn = pool.get().await.unwrap(); + + let results: Vec>> = epochs::table + .select(epochs::system_state) + .filter(epochs::epoch.between(*range.start() as i64, *range.end() as i64)) + .load(&mut conn) + .await + .unwrap(); + + let mut system_states = vec![]; + for bytes in results { + let Some(bytes) = bytes else { + continue; + }; + let system_state_summary: SuiSystemStateSummary = bcs::from_bytes(&bytes).unwrap(); + let json_ser = serde_json::to_value(&system_state_summary).unwrap(); + if system_state_summary.epoch == 1 { + // Each existing system state's epoch is off by 1. + // This means there won't be any row with a system state summary for epoch 0. + // We need to manually insert a row for epoch 0. + system_states.push((0, json_ser.clone())); + } + system_states.push((system_state_summary.epoch, json_ser)); + } + conn.transaction::<_, diesel::result::Error, _>(|conn| { + Box::pin(async move { + for (epoch, json_ser) in system_states { + diesel::update(epochs::table.filter(epochs::epoch.eq(epoch as i64))) + .set(epochs::system_state_summary_json.eq(Some(json_ser))) + .execute(conn) + .await?; + } + Ok(()) + }) + }) + .await + .unwrap(); + } +} diff --git a/crates/sui-indexer/src/backfill/backfill_runner.rs b/crates/sui-indexer/src/backfill/backfill_runner.rs new file mode 100644 index 0000000000000..3126dc90fe35f --- /dev/null +++ b/crates/sui-indexer/src/backfill/backfill_runner.rs @@ -0,0 +1,94 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::backfill::backfill_instances::get_backfill_task; +use crate::backfill::backfill_task::BackfillTask; +use crate::backfill::BackfillTaskKind; +use crate::config::BackFillConfig; +use crate::database::ConnectionPool; +use futures::StreamExt; +use std::collections::BTreeSet; +use std::ops::RangeInclusive; +use std::sync::Arc; +use std::time::Instant; +use tokio::sync::{mpsc, Mutex}; +use tokio_stream::wrappers::ReceiverStream; + +pub struct BackfillRunner {} + +impl BackfillRunner { + pub async fn run( + runner_kind: BackfillTaskKind, + pool: ConnectionPool, + backfill_config: BackFillConfig, + total_range: RangeInclusive, + ) { + let task = get_backfill_task(runner_kind, *total_range.start()).await; + Self::run_impl(pool, backfill_config, total_range, task).await; + } + + /// Main function to run the parallel queries and batch processing. + async fn run_impl( + pool: ConnectionPool, + config: BackFillConfig, + total_range: RangeInclusive, + task: Arc, + ) { + let cur_time = Instant::now(); + // Keeps track of the checkpoint ranges (using starting checkpoint number) + // that are in progress. + let in_progress = Arc::new(Mutex::new(BTreeSet::new())); + + let concurrency = config.max_concurrency; + let (tx, rx) = mpsc::channel(concurrency * 10); + // Spawn a task to send chunks lazily over the channel + tokio::spawn(async move { + for chunk in create_chunk_iter(total_range, config.chunk_size) { + if tx.send(chunk).await.is_err() { + // Channel closed, stop producing chunks + break; + } + } + }); + // Convert the receiver into a stream + let stream = ReceiverStream::new(rx); + + // Process chunks in parallel, limiting the number of concurrent query tasks + stream + .for_each_concurrent(concurrency, move |range| { + let pool_clone = pool.clone(); + let in_progress_clone = in_progress.clone(); + let task = task.clone(); + + async move { + in_progress_clone.lock().await.insert(*range.start()); + task.backfill_range(pool_clone, &range).await; + println!("Finished range: {:?}.", range); + in_progress_clone.lock().await.remove(range.start()); + let cur_min_in_progress = in_progress_clone.lock().await.iter().next().cloned(); + if let Some(cur_min_in_progress) = cur_min_in_progress { + println!( + "Average backfill speed: {} checkpoints/s. Minimum range start number still in progress: {:?}.", + cur_min_in_progress as f64 / cur_time.elapsed().as_secs_f64(), + cur_min_in_progress + ); + } + } + }) + .await; + + println!("Finished backfilling in {:?}", cur_time.elapsed()); + } +} + +/// Creates chunks based on the total range and chunk size. +fn create_chunk_iter( + total_range: RangeInclusive, + chunk_size: usize, +) -> impl Iterator> { + let end = *total_range.end(); + total_range.step_by(chunk_size).map(move |chunk_start| { + let chunk_end = std::cmp::min(chunk_start + chunk_size - 1, end); + chunk_start..=chunk_end + }) +} diff --git a/crates/sui-indexer/src/backfill/backfill_task.rs b/crates/sui-indexer/src/backfill/backfill_task.rs new file mode 100644 index 0000000000000..008bfa5b482c0 --- /dev/null +++ b/crates/sui-indexer/src/backfill/backfill_task.rs @@ -0,0 +1,12 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::database::ConnectionPool; +use async_trait::async_trait; +use std::ops::RangeInclusive; + +#[async_trait] +pub trait BackfillTask: Send + Sync { + /// Backfill the database for a specific range. + async fn backfill_range(&self, pool: ConnectionPool, range: &RangeInclusive); +} diff --git a/crates/sui-indexer/src/backfill/mod.rs b/crates/sui-indexer/src/backfill/mod.rs new file mode 100644 index 0000000000000..453d11baeeed2 --- /dev/null +++ b/crates/sui-indexer/src/backfill/mod.rs @@ -0,0 +1,34 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use clap::{Subcommand, ValueEnum}; + +pub mod backfill_instances; +pub mod backfill_runner; +pub mod backfill_task; + +#[derive(Subcommand, Clone, Debug)] +pub enum BackfillTaskKind { + SystemStateSummaryJson, + /// \sql is the SQL string to run, appended with the range between the start and end, + /// as well as conflict resolution (see sql_backfill.rs). + /// \key_column is the primary key column to use for the range. + Sql { + sql: String, + key_column: String, + }, + /// Starts a backfill pipeline from the ingestion engine. + /// \remote_store_url is the URL of the remote store to ingest from. + /// Any `IngestionBackfillKind` will need to map to a type that + /// implements `IngestionBackfillTrait`. + Ingestion { + kind: IngestionBackfillKind, + remote_store_url: String, + }, +} + +#[derive(ValueEnum, Clone, Debug)] +pub enum IngestionBackfillKind { + RawCheckpoints, + TxAffectedObjects, +} diff --git a/crates/sui-indexer/src/config.rs b/crates/sui-indexer/src/config.rs index 5d2a1592e92ff..f51d18ab1ff88 100644 --- a/crates/sui-indexer/src/config.rs +++ b/crates/sui-indexer/src/config.rs @@ -1,13 +1,19 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use crate::db::ConnectionPoolConfig; +use crate::{backfill::BackfillTaskKind, handlers::pruner::PrunableTable}; use clap::{Args, Parser, Subcommand}; -use std::{net::SocketAddr, path::PathBuf}; +use serde::{Deserialize, Serialize}; +use std::{collections::HashMap, net::SocketAddr, path::PathBuf}; +use strum::IntoEnumIterator; use sui_json_rpc::name_service::NameServiceConfig; use sui_types::base_types::{ObjectID, SuiAddress}; use url::Url; -use crate::db::ConnectionPoolConfig; +/// The primary purpose of objects_history is to serve consistency query. +/// A short retention is sufficient. +const OBJECTS_HISTORY_EPOCHS_TO_KEEP: u64 = 2; #[derive(Parser, Clone, Debug)] #[clap( @@ -144,7 +150,7 @@ impl Default for IngestionConfig { } #[derive(Args, Debug, Clone)] -pub struct SqlBackFillConfig { +pub struct BackFillConfig { /// Maximum number of concurrent tasks to run. #[arg( long, @@ -159,7 +165,7 @@ pub struct SqlBackFillConfig { pub chunk_size: usize, } -impl SqlBackFillConfig { +impl BackFillConfig { const DEFAULT_MAX_CONCURRENCY: usize = 10; const DEFAULT_CHUNK_SIZE: usize = 1000; } @@ -183,23 +189,24 @@ pub enum Command { }, /// Run through the migration scripts. RunMigrations, - /// Backfill DB tables for checkpoint range [\first_checkpoint, \last_checkpoint]. - /// by running a SQL query provided in \sql. - /// The tool will automatically slice it into smaller checkpoint ranges and for each range [start, end], - /// it augments the \sql query with: - /// "WHERE {checkpoint_column_name} BETWEEN {start} AND {end}" - /// to avoid running out of memory. - /// Example: - /// ./sui-indexer --database-url <...> sql-back-fill - /// "INSERT INTO full_objects_history (object_id, object_version, serialized_object) SELECT object_id, object_version, serialized_object FROM objects_history" - /// "checkpoint_sequence_number" 0 100000 - SqlBackFill { - sql: String, - checkpoint_column_name: String, - first_checkpoint: u64, - last_checkpoint: u64, + /// Backfill DB tables for some ID range [\start, \end]. + /// The tool will automatically slice it into smaller ranges and for each range, + /// it first makes a read query to the DB to get data needed for backfil if needed, + /// which then can be processed and written back to the DB. + /// To add a new backfill, add a new module and implement the `BackfillTask` trait. + /// full_objects_history.rs provides an example to do SQL-only backfills. + /// system_state_summary_json.rs provides an example to do SQL + processing backfills. + RunBackFill { + /// Start of the range to backfill, inclusive. + /// It can be a checkpoint number or an epoch or any other identifier that can be used to + /// slice the backfill range. + start: usize, + /// End of the range to backfill, inclusive. + end: usize, + #[clap(subcommand)] + runner_kind: BackfillTaskKind, #[command(flatten)] - backfill_config: SqlBackFillConfig, + backfill_config: BackFillConfig, }, /// Restore the database from formal snaphots. Restore(RestoreConfig), @@ -207,8 +214,93 @@ pub enum Command { #[derive(Args, Default, Debug, Clone)] pub struct PruningOptions { - #[arg(long, env = "EPOCHS_TO_KEEP")] - pub epochs_to_keep: Option, + /// Path to TOML file containing configuration for retention policies. + #[arg(long)] + pub pruning_config_path: Option, +} + +/// Represents the default retention policy and overrides for prunable tables. Instantiated only if +/// `PruningOptions` is provided on indexer start. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RetentionConfig { + /// Default retention policy for all tables. + pub epochs_to_keep: u64, + /// A map of tables to their respective retention policies that will override the default. + /// Prunable tables not named here will use the default retention policy. + #[serde(default)] + pub overrides: HashMap, +} + +impl PruningOptions { + /// Load default retention policy and overrides from file. + pub fn load_from_file(&self) -> Option { + let config_path = self.pruning_config_path.as_ref()?; + + let contents = std::fs::read_to_string(config_path) + .expect("Failed to read default retention policy and overrides from file"); + let retention_with_overrides = toml::de::from_str::(&contents) + .expect("Failed to parse into RetentionConfig struct"); + + let default_retention = retention_with_overrides.epochs_to_keep; + + assert!( + default_retention > 0, + "Default retention must be greater than 0" + ); + assert!( + retention_with_overrides + .overrides + .values() + .all(|&policy| policy > 0), + "All retention overrides must be greater than 0" + ); + + Some(retention_with_overrides) + } +} + +impl RetentionConfig { + /// Create a new `RetentionConfig` with the specified default retention and overrides. Call + /// `finalize()` on the instance to update the `policies` field with the default retention + /// policy for all tables that do not have an override specified. + pub fn new(epochs_to_keep: u64, overrides: HashMap) -> Self { + Self { + epochs_to_keep, + overrides, + } + } + + pub fn new_with_default_retention_only_for_testing(epochs_to_keep: u64) -> Self { + let mut overrides = HashMap::new(); + overrides.insert( + PrunableTable::ObjectsHistory, + OBJECTS_HISTORY_EPOCHS_TO_KEEP, + ); + + Self::new(epochs_to_keep, HashMap::new()) + } + + /// Consumes this struct to produce a full mapping of every prunable table and its retention + /// policy. By default, every prunable table will have the default retention policy from + /// `epochs_to_keep`. Some tables like `objects_history` will observe a different default + /// retention policy. These default values are overridden by any entries in `overrides`. + pub fn retention_policies(self) -> HashMap { + let RetentionConfig { + epochs_to_keep, + mut overrides, + } = self; + + for table in PrunableTable::iter() { + let default_retention = match table { + PrunableTable::ObjectsHistory => OBJECTS_HISTORY_EPOCHS_TO_KEEP, + _ => epochs_to_keep, + }; + + overrides.entry(table).or_insert(default_retention); + } + + overrides + } } #[derive(Args, Debug, Clone)] @@ -251,30 +343,47 @@ pub struct UploadOptions { #[derive(Args, Debug, Clone)] pub struct RestoreConfig { + #[arg(long, env = "START_EPOCH", required = true)] + pub start_epoch: u64, + #[arg(long, env = "SNAPSHOT_ENDPOINT")] + pub snapshot_endpoint: String, + #[arg(long, env = "SNAPSHOT_BUCKET")] + pub snapshot_bucket: String, + #[arg(long, env = "SNAPSHOT_DOWNLOAD_DIR", required = true)] + pub snapshot_download_dir: String, + #[arg(long, env = "GCS_ARCHIVE_BUCKET")] pub gcs_archive_bucket: String, - #[arg(long, env = "GCS_CRED_PATH")] - pub gcs_cred_path: String, #[arg(long, env = "GCS_DISPLAY_BUCKET")] pub gcs_display_bucket: String, - #[arg(long, env = "GCS_SNAPSHOT_DIR")] - pub gcs_snapshot_dir: String, - #[arg(long, env = "GCS_SNAPSHOT_BUCKET")] - pub gcs_snapshot_bucket: String, - #[arg(long, env = "START_EPOCH")] - pub start_epoch: u64, - #[arg(long, env = "S3_ENDPOINT")] - pub s3_endpoint: String, + #[arg(env = "OBJECT_STORE_CONCURRENT_LIMIT")] pub object_store_concurrent_limit: usize, #[arg(env = "OBJECT_STORE_MAX_TIMEOUT_SECS")] pub object_store_max_timeout_secs: u64, } +impl Default for RestoreConfig { + fn default() -> Self { + Self { + start_epoch: 0, // not used b/c it's required + snapshot_endpoint: "https://formal-snapshot.mainnet.sui.io".to_string(), + snapshot_bucket: "mysten-mainnet-formal".to_string(), + snapshot_download_dir: "".to_string(), // not used b/c it's required + gcs_archive_bucket: "mysten-mainnet-archives".to_string(), + gcs_display_bucket: "mysten-mainnet-display-table".to_string(), + object_store_concurrent_limit: 50, + object_store_max_timeout_secs: 512, + } + } +} + #[cfg(test)] mod test { use super::*; + use std::io::Write; use tap::Pipe; + use tempfile::NamedTempFile; fn parse_args<'a, T>(args: impl IntoIterator) -> Result where @@ -338,4 +447,129 @@ mod test { // fullnode rpc url must be present parse_args::([]).unwrap_err(); } + + #[test] + fn pruning_options_with_objects_history_override() { + let mut temp_file = NamedTempFile::new().unwrap(); + let toml_content = r#" + epochs_to_keep = 5 + + [overrides] + objects_history = 10 + transactions = 20 + "#; + temp_file.write_all(toml_content.as_bytes()).unwrap(); + let temp_path: PathBuf = temp_file.path().to_path_buf(); + let pruning_options = PruningOptions { + pruning_config_path: Some(temp_path.clone()), + }; + let retention_config = pruning_options.load_from_file().unwrap(); + + // Assert the parsed values + assert_eq!(retention_config.epochs_to_keep, 5); + assert_eq!( + retention_config + .overrides + .get(&PrunableTable::ObjectsHistory) + .copied(), + Some(10) + ); + assert_eq!( + retention_config + .overrides + .get(&PrunableTable::Transactions) + .copied(), + Some(20) + ); + assert_eq!(retention_config.overrides.len(), 2); + + let retention_policies = retention_config.retention_policies(); + + for table in PrunableTable::iter() { + let Some(retention) = retention_policies.get(&table).copied() else { + panic!("Expected a retention policy for table {:?}", table); + }; + + match table { + PrunableTable::ObjectsHistory => assert_eq!(retention, 10), + PrunableTable::Transactions => assert_eq!(retention, 20), + _ => assert_eq!(retention, 5), + }; + } + } + + #[test] + fn pruning_options_no_objects_history_override() { + let mut temp_file = NamedTempFile::new().unwrap(); + let toml_content = r#" + epochs_to_keep = 5 + + [overrides] + tx_affected_addresses = 10 + transactions = 20 + "#; + temp_file.write_all(toml_content.as_bytes()).unwrap(); + let temp_path: PathBuf = temp_file.path().to_path_buf(); + let pruning_options = PruningOptions { + pruning_config_path: Some(temp_path.clone()), + }; + let retention_config = pruning_options.load_from_file().unwrap(); + + // Assert the parsed values + assert_eq!(retention_config.epochs_to_keep, 5); + assert_eq!( + retention_config + .overrides + .get(&PrunableTable::TxAffectedAddresses) + .copied(), + Some(10) + ); + assert_eq!( + retention_config + .overrides + .get(&PrunableTable::Transactions) + .copied(), + Some(20) + ); + assert_eq!(retention_config.overrides.len(), 2); + + let retention_policies = retention_config.retention_policies(); + + for table in PrunableTable::iter() { + let Some(retention) = retention_policies.get(&table).copied() else { + panic!("Expected a retention policy for table {:?}", table); + }; + + match table { + PrunableTable::ObjectsHistory => { + assert_eq!(retention, OBJECTS_HISTORY_EPOCHS_TO_KEEP) + } + PrunableTable::TxAffectedAddresses => assert_eq!(retention, 10), + PrunableTable::Transactions => assert_eq!(retention, 20), + _ => assert_eq!(retention, 5), + }; + } + } + + #[test] + fn test_invalid_pruning_config_file() { + let toml_str = r#" + epochs_to_keep = 5 + + [overrides] + objects_history = 10 + transactions = 20 + invalid_table = 30 + "#; + + let result = toml::from_str::(toml_str); + assert!(result.is_err(), "Expected an error, but parsing succeeded"); + + if let Err(e) = result { + assert!( + e.to_string().contains("unknown variant `invalid_table`"), + "Error message doesn't mention the invalid table" + ); + } + } } diff --git a/crates/sui-indexer/src/db.rs b/crates/sui-indexer/src/db.rs index 6ad831d331f45..9937b61ce2655 100644 --- a/crates/sui-indexer/src/db.rs +++ b/crates/sui-indexer/src/db.rs @@ -3,14 +3,17 @@ use crate::database::Connection; use crate::errors::IndexerError; +use crate::handlers::pruner::PrunableTable; use clap::Args; use diesel::migration::{Migration, MigrationSource, MigrationVersion}; use diesel::pg::Pg; +use diesel::prelude::QueryableByName; use diesel::table; -use diesel::ExpressionMethods; use diesel::QueryDsl; use diesel_migrations::{embed_migrations, EmbeddedMigrations}; +use std::collections::{BTreeSet, HashSet}; use std::time::Duration; +use strum::IntoEnumIterator; use tracing::info; table! { @@ -111,27 +114,75 @@ async fn check_db_migration_consistency_impl( // Unfortunately we cannot call applied_migrations() directly on the connection, // since it implicitly creates the __diesel_schema_migrations table if it doesn't exist, // which is a write operation that we don't want to do in this function. - let applied_migrations: Vec = __diesel_schema_migrations::table - .select(__diesel_schema_migrations::version) - .order(__diesel_schema_migrations::version.asc()) - .load(conn) - .await?; - - // We check that the local migrations is a prefix of the applied migrations. - if local_migrations.len() > applied_migrations.len() { - return Err(IndexerError::DbMigrationError(format!( - "The number of local migrations is greater than the number of applied migrations. Local migrations: {:?}, Applied migrations: {:?}", - local_migrations, applied_migrations - ))); + let applied_migrations: BTreeSet> = BTreeSet::from_iter( + __diesel_schema_migrations::table + .select(__diesel_schema_migrations::version) + .load(conn) + .await?, + ); + + // We check that the local migrations is a subset of the applied migrations. + let unapplied_migrations: Vec<_> = local_migrations + .into_iter() + .filter(|m| !applied_migrations.contains(m)) + .collect(); + + if unapplied_migrations.is_empty() { + return Ok(()); + } + + Err(IndexerError::DbMigrationError(format!( + "This binary expected the following migrations to have been run, and they were not: {:?}", + unapplied_migrations + ))) +} + +/// Check that prunable tables exist in the database. +pub async fn check_prunable_tables_valid(conn: &mut Connection<'_>) -> Result<(), IndexerError> { + info!("Starting compatibility check"); + + use diesel_async::RunQueryDsl; + + let select_parent_tables = r#" + SELECT c.relname AS table_name + FROM pg_class c + JOIN pg_namespace n ON n.oid = c.relnamespace + LEFT JOIN pg_partitioned_table pt ON pt.partrelid = c.oid + WHERE c.relkind IN ('r', 'p') -- 'r' for regular tables, 'p' for partitioned tables + AND n.nspname = 'public' + AND ( + pt.partrelid IS NOT NULL -- This is a partitioned (parent) table + OR NOT EXISTS ( -- This is not a partition (child table) + SELECT 1 + FROM pg_inherits i + WHERE i.inhrelid = c.oid + ) + ); + "#; + + #[derive(QueryableByName)] + struct TableName { + #[diesel(sql_type = diesel::sql_types::Text)] + table_name: String, } - for (local_migration, applied_migration) in local_migrations.iter().zip(&applied_migrations) { - if local_migration != applied_migration { - return Err(IndexerError::DbMigrationError(format!( - "The next applied migration `{:?}` diverges from the local migration `{:?}`", - applied_migration, local_migration + + let result: Vec = diesel::sql_query(select_parent_tables) + .load(conn) + .await + .map_err(|e| IndexerError::DbMigrationError(format!("Failed to fetch tables: {e}")))?; + + let parent_tables_from_db: HashSet<_> = result.into_iter().map(|t| t.table_name).collect(); + + for key in PrunableTable::iter() { + if !parent_tables_from_db.contains(key.as_ref()) { + return Err(IndexerError::GenericError(format!( + "Invalid retention policy override provided for table {}: does not exist in the database", + key ))); } } + + info!("Compatibility check passed"); Ok(()) } @@ -314,4 +365,32 @@ mod tests { .await .unwrap(); } + + #[tokio::test] + async fn db_migration_consistency_subset_test() { + let database = TempDb::new().unwrap(); + let pool = ConnectionPool::new( + database.database().url().to_owned(), + ConnectionPoolConfig { + pool_size: 2, + ..Default::default() + }, + ) + .await + .unwrap(); + + reset_database(pool.dedicated_connection().await.unwrap()) + .await + .unwrap(); + + let migrations: Vec>> = MIGRATIONS.migrations().unwrap(); + let mut local_migrations: Vec<_> = migrations.iter().map(|m| m.name().version()).collect(); + local_migrations.remove(2); + + // Local migrations are missing one record compared to the applied migrations, which should + // still be okay. + check_db_migration_consistency_impl(&mut pool.get().await.unwrap(), local_migrations) + .await + .unwrap(); + } } diff --git a/crates/sui-indexer/src/errors.rs b/crates/sui-indexer/src/errors.rs index e1c40861a2532..c8971e39781ad 100644 --- a/crates/sui-indexer/src/errors.rs +++ b/crates/sui-indexer/src/errors.rs @@ -31,6 +31,9 @@ pub enum IndexerError { #[error("Indexer failed to read from archives store with error: `{0}`")] ArchiveReaderError(String), + #[error("Stream closed unexpectedly with error: `{0}`")] + ChannelClosed(String), + #[error("Indexer failed to convert timestamp to NaiveDateTime with error: `{0}`")] DateTimeParsingError(String), diff --git a/crates/sui-indexer/src/handlers/checkpoint_handler.rs b/crates/sui-indexer/src/handlers/checkpoint_handler.rs index 9f89c4baff687..be4e0d375a923 100644 --- a/crates/sui-indexer/src/handlers/checkpoint_handler.rs +++ b/crates/sui-indexer/src/handlers/checkpoint_handler.rs @@ -1,28 +1,21 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::collections::{BTreeMap, HashMap}; -use std::sync::{Arc, Mutex}; +use std::collections::BTreeMap; +use std::sync::Arc; use async_trait::async_trait; use itertools::Itertools; -use tap::tap::TapFallible; -use tokio::sync::watch; +use sui_types::dynamic_field::DynamicFieldInfo; use tokio_util::sync::CancellationToken; -use tracing::{info, warn}; +use tracing::info; -use move_core_types::annotated_value::{MoveStructLayout, MoveTypeLayout}; use move_core_types::language_storage::{StructTag, TypeTag}; use mysten_metrics::{get_metrics, spawn_monitored_task}; use sui_data_ingestion_core::Worker; -use sui_json_rpc_types::SuiMoveValue; -use sui_package_resolver::{PackageStore, PackageStoreWithLruCache, Resolver}; use sui_rest_api::{CheckpointData, CheckpointTransaction}; -use sui_types::base_types::ObjectID; -use sui_types::dynamic_field::DynamicFieldInfo; -use sui_types::dynamic_field::DynamicFieldName; use sui_types::dynamic_field::DynamicFieldType; -use sui_types::effects::TransactionEffectsAPI; +use sui_types::effects::{ObjectChange, TransactionEffectsAPI}; use sui_types::event::SystemEpochInfoEvent; use sui_types::messages_checkpoint::{ CertifiedCheckpointSummary, CheckpointContents, CheckpointSequenceNumber, @@ -34,15 +27,14 @@ use sui_types::transaction::TransactionDataAPI; use crate::errors::IndexerError; use crate::handlers::committer::start_tx_checkpoint_commit_task; -use crate::handlers::tx_processor::IndexingPackageBuffer; use crate::metrics::IndexerMetrics; use crate::models::display::StoredDisplay; +use crate::models::epoch::{EndOfEpochUpdate, StartOfEpochUpdate}; use crate::models::obj_indices::StoredObjectVersion; -use crate::store::package_resolver::{IndexerStorePackageResolver, InterimPackageResolver}; use crate::store::{IndexerStore, PgIndexerStore}; use crate::types::{ - EventIndex, IndexedCheckpoint, IndexedDeletedObject, IndexedEpochInfo, IndexedEvent, - IndexedObject, IndexedPackage, IndexedTransaction, IndexerResult, TransactionKind, TxIndex, + EventIndex, IndexedCheckpoint, IndexedDeletedObject, IndexedEvent, IndexedObject, + IndexedPackage, IndexedTransaction, IndexerResult, TransactionKind, TxIndex, }; use super::tx_processor::EpochEndIndexingObjectStore; @@ -74,12 +66,10 @@ pub async fn new_handlers( let state_clone = state.clone(); let metrics_clone = metrics.clone(); - let (tx, package_tx) = watch::channel(None); spawn_monitored_task!(start_tx_checkpoint_commit_task( state_clone, metrics_clone, indexed_checkpoint_receiver, - tx, next_checkpoint_sequence_number, cancel.clone() )); @@ -87,7 +77,6 @@ pub async fn new_handlers( state, metrics, indexed_checkpoint_sender, - package_tx, )) } @@ -95,14 +84,11 @@ pub struct CheckpointHandler { state: PgIndexerStore, metrics: IndexerMetrics, indexed_checkpoint_sender: mysten_metrics::metered_channel::Sender, - // buffers for packages that are being indexed but not committed to DB, - // they will be periodically GCed to avoid OOM. - package_buffer: Arc>, - package_resolver: Arc>>, } #[async_trait] impl Worker for CheckpointHandler { + type Result = (); async fn process_checkpoint(&self, checkpoint: &CheckpointData) -> anyhow::Result<()> { let time_now_ms = chrono::Utc::now().timestamp_millis(); let cp_download_lag = time_now_ms - checkpoint.checkpoint_summary.timestamp_ms as i64; @@ -128,21 +114,11 @@ impl Worker for CheckpointHandler { checkpoint, Arc::new(self.metrics.clone()), Self::index_packages(std::slice::from_ref(checkpoint), &self.metrics), - self.package_resolver.clone(), ) .await?; self.indexed_checkpoint_sender.send(checkpoint_data).await?; Ok(()) } - - fn preprocess_hook(&self, checkpoint: &CheckpointData) -> anyhow::Result<()> { - let package_objects = Self::get_package_objects(std::slice::from_ref(checkpoint)); - self.package_buffer - .lock() - .unwrap() - .insert_packages(package_objects); - Ok(()) - } } impl CheckpointHandler { @@ -150,23 +126,11 @@ impl CheckpointHandler { state: PgIndexerStore, metrics: IndexerMetrics, indexed_checkpoint_sender: mysten_metrics::metered_channel::Sender, - package_tx: watch::Receiver>, ) -> Self { - let package_buffer = IndexingPackageBuffer::start(package_tx); - let package_db_resolver = IndexerStorePackageResolver::new(state.pool()); - let in_mem_package_resolver = InterimPackageResolver::new( - package_db_resolver, - package_buffer.clone(), - metrics.clone(), - ); - let cached_package_resolver = PackageStoreWithLruCache::new(in_mem_package_resolver); - let package_resolver = Arc::new(Resolver::new(cached_package_resolver)); Self { state, metrics, indexed_checkpoint_sender, - package_buffer, - package_resolver, } } @@ -189,12 +153,12 @@ impl CheckpointHandler { get_sui_system_state(&checkpoint_object_store)?.into_sui_system_state_summary(); return Ok(Some(EpochToCommit { last_epoch: None, - new_epoch: IndexedEpochInfo::from_new_system_state_summary( + new_epoch: StartOfEpochUpdate::new( system_state_summary, 0, //first_checkpoint_id + 0, // first_tx_sequence_number None, ), - network_total_transactions: 0, })); } @@ -220,13 +184,9 @@ impl CheckpointHandler { let event = bcs::from_bytes::(&epoch_event.contents)?; - // Now we just entered epoch X, we want to calculate the diff between - // TotalTransactionsByEndOfEpoch(X-1) and TotalTransactionsByEndOfEpoch(X-2). Note that on - // the indexer's chain-reading side, this is not guaranteed to have the latest data. Rather - // than impose a wait on the reading side, however, we overwrite this on the persisting - // side, where we can guarantee that the previous epoch's checkpoints have been written to - // db. - + // At some point while committing data in epoch X - 1, we will encounter a new epoch X. We + // want to retrieve X - 2's network total transactions to calculate the number of + // transactions that occurred in epoch X - 1. let network_tx_count_prev_epoch = match system_state_summary.epoch { // If first epoch change, this number is 0 1 => Ok(0), @@ -234,23 +194,28 @@ impl CheckpointHandler { let last_epoch = system_state_summary.epoch - 2; state .get_network_total_transactions_by_end_of_epoch(last_epoch) - .await + .await? + .ok_or_else(|| { + IndexerError::PersistentStorageDataCorruptionError(format!( + "Network total transactions for epoch {} not found", + last_epoch + )) + }) } }?; Ok(Some(EpochToCommit { - last_epoch: Some(IndexedEpochInfo::from_end_of_epoch_data( - system_state_summary.clone(), + last_epoch: Some(EndOfEpochUpdate::new( checkpoint_summary, &event, network_tx_count_prev_epoch, )), - new_epoch: IndexedEpochInfo::from_new_system_state_summary( + new_epoch: StartOfEpochUpdate::new( system_state_summary, checkpoint_summary.sequence_number + 1, // first_checkpoint_id + checkpoint_summary.network_total_transactions, Some(&event), ), - network_total_transactions: checkpoint_summary.network_total_transactions, })) } @@ -280,7 +245,6 @@ impl CheckpointHandler { data: &CheckpointData, metrics: Arc, packages: Vec, - package_resolver: Arc>, ) -> Result { let checkpoint_seq = data.checkpoint_summary.sequence_number; info!(checkpoint_seq, "Indexing checkpoint data blob"); @@ -290,9 +254,9 @@ impl CheckpointHandler { // Index Objects let object_changes: TransactionObjectChangesToCommit = - Self::index_objects(data, &metrics, package_resolver.clone()).await?; + Self::index_objects(data, &metrics).await?; let object_history_changes: TransactionObjectChangesToCommit = - Self::index_objects_history(data, package_resolver.clone()).await?; + Self::index_objects_history(data).await?; let object_versions = Self::derive_object_versions(&object_history_changes); let (checkpoint, db_transactions, db_events, db_tx_indices, db_event_indices, db_displays) = { @@ -441,10 +405,7 @@ impl CheckpointHandler { .map(|display| (display.object_type.clone(), display)), ); - let objects = input_objects - .iter() - .chain(output_objects.iter()) - .collect::>(); + let objects: Vec<_> = input_objects.iter().chain(output_objects.iter()).collect(); let (balance_change, object_changes) = TxChangesProcessor::new(&objects, metrics.clone()) @@ -477,14 +438,21 @@ impl CheckpointHandler { .expect("committed txns have been validated") .into_iter() .map(|obj_kind| obj_kind.object_id()) - .collect::>(); + .collect(); // Changed Objects let changed_objects = fx .all_changed_objects() .into_iter() .map(|(object_ref, _owner, _write_kind)| object_ref.0) - .collect::>(); + .collect(); + + // Affected Objects + let affected_objects = fx + .object_changes() + .into_iter() + .map(|ObjectChange { id, .. }| id) + .collect(); // Payers let payers = vec![tx.gas_owner()]; @@ -501,13 +469,13 @@ impl CheckpointHandler { _ => None, }) .unique() - .collect::>(); + .collect(); // Move Calls let move_calls = tx .move_calls() - .iter() - .map(|(p, m, f)| (*<&ObjectID>::clone(p), m.to_string(), f.to_string())) + .into_iter() + .map(|(p, m, f)| (*p, m.to_string(), f.to_string())) .collect(); db_tx_indices.push(TxIndex { @@ -516,6 +484,7 @@ impl CheckpointHandler { checkpoint_sequence_number: *checkpoint_seq, input_objects, changed_objects, + affected_objects, sender, payers, recipients, @@ -535,7 +504,6 @@ impl CheckpointHandler { pub(crate) async fn index_objects( data: &CheckpointData, metrics: &IndexerMetrics, - package_resolver: Arc>, ) -> Result { let _timer = metrics.indexing_objects_latency.start_timer(); let checkpoint_seq = data.checkpoint_summary.sequence_number; @@ -552,25 +520,14 @@ impl CheckpointHandler { .collect(); let latest_live_output_objects = data.latest_live_output_objects(); - let latest_live_output_object_map = latest_live_output_objects - .clone() - .into_iter() - .map(|o| (o.id(), o.clone())) - .collect::>(); - let move_struct_layout_map = - get_move_struct_layout_map(latest_live_output_objects.clone(), package_resolver) - .await?; let changed_objects = latest_live_output_objects .into_iter() .map(|o| { - let df_info = try_create_dynamic_field_info( - o, - &move_struct_layout_map, - &latest_live_output_object_map, - ); - df_info.map(|info| IndexedObject::from_object(checkpoint_seq, o.clone(), info)) + try_extract_df_kind(o) + .map(|df_kind| IndexedObject::from_object(checkpoint_seq, o.clone(), df_kind)) }) .collect::, _>>()?; + Ok(TransactionObjectChangesToCommit { changed_objects, deleted_objects: indexed_eventually_removed_objects, @@ -580,7 +537,6 @@ impl CheckpointHandler { // similar to index_objects, but objects_history keeps all versions of objects async fn index_objects_history( data: &CheckpointData, - package_resolver: Arc>, ) -> Result { let checkpoint_seq = data.checkpoint_summary.sequence_number; let deleted_objects = data @@ -597,31 +553,19 @@ impl CheckpointHandler { }) .collect(); - let latest_live_output_objects = data.latest_live_output_objects(); - let latest_live_output_object_map = latest_live_output_objects - .clone() - .into_iter() - .map(|o| (o.id(), o.clone())) - .collect::>(); - - let output_objects = data + let output_objects: Vec<_> = data .transactions .iter() .flat_map(|tx| &tx.output_objects) - .collect::>(); + .collect(); + // TODO(gegaowp): the current df_info implementation is not correct, // but we have decided remove all df_* except df_kind. - let move_struct_layout_map = - get_move_struct_layout_map(output_objects.clone(), package_resolver).await?; let changed_objects = output_objects .into_iter() .map(|o| { - let df_info = try_create_dynamic_field_info( - o, - &move_struct_layout_map, - &latest_live_output_object_map, - ); - df_info.map(|info| IndexedObject::from_object(checkpoint_seq, o.clone(), info)) + try_extract_df_kind(o) + .map(|df_kind| IndexedObject::from_object(checkpoint_seq, o.clone(), df_kind)) }) .collect::, _>>()?; @@ -658,98 +602,13 @@ impl CheckpointHandler { }) .collect() } - - pub(crate) fn get_package_objects( - checkpoint_data: &[CheckpointData], - ) -> Vec<(IndexedPackage, Object)> { - checkpoint_data - .iter() - .flat_map(|data| { - let checkpoint_sequence_number = data.checkpoint_summary.sequence_number; - data.transactions - .iter() - .flat_map(|tx| &tx.output_objects) - .filter_map(|o| { - if let sui_types::object::Data::Package(p) = &o.data { - let indexed_pkg = IndexedPackage { - package_id: o.id(), - move_package: p.clone(), - checkpoint_sequence_number, - }; - Some((indexed_pkg, o.clone())) - } else { - None - } - }) - .collect::>() - }) - .collect() - } } -async fn get_move_struct_layout_map( - objects: Vec<&Object>, - package_resolver: Arc>, -) -> Result, IndexerError> { - let struct_tags = objects - .into_iter() - .filter_map(|o| { - let move_object = o.data.try_as_move().cloned(); - move_object.map(|move_object| { - let struct_tag: StructTag = move_object.type_().clone().into(); - struct_tag - }) - }) - .collect::>(); - let struct_tags = struct_tags.into_iter().unique().collect::>(); - info!( - "Resolving Move struct layouts for struct tags of size {}.", - struct_tags.len() - ); - let move_struct_layout_futures = struct_tags - .into_iter() - .map(|struct_tag| { - let package_resolver_clone = package_resolver.clone(); - async move { - let move_type_layout = package_resolver_clone - .type_layout(TypeTag::Struct(Box::new(struct_tag.clone()))) - .await - .map_err(|e| { - IndexerError::DynamicFieldError(format!( - "Fail to resolve struct layout for {:?} with {:?}.", - struct_tag, e - )) - })?; - let move_struct_layout = match move_type_layout { - MoveTypeLayout::Struct(s) => Ok(s), - _ => Err(IndexerError::ResolveMoveStructError( - "MoveTypeLayout is not Struct".to_string(), - )), - }?; - Ok::< - ( - move_core_types::language_storage::StructTag, - move_core_types::annotated_value::MoveStructLayout, - ), - IndexerError, - >((struct_tag, *move_struct_layout)) - } - }) - .collect::>(); - let move_struct_layout_map = futures::future::try_join_all(move_struct_layout_futures) - .await? - .into_iter() - .collect::>(); - Ok(move_struct_layout_map) -} - -fn try_create_dynamic_field_info( - o: &Object, - struct_tag_to_move_struct_layout: &HashMap, - latest_objects: &HashMap, -) -> IndexerResult> { +/// If `o` is a dynamic `Field`, determine whether it represents a Dynamic Field or a Dynamic +/// Object Field based on its type. +fn try_extract_df_kind(o: &Object) -> IndexerResult> { // Skip if not a move object - let Some(move_object) = o.data.try_as_move().cloned() else { + let Some(move_object) = o.data.try_as_move() else { return Ok(None); }; @@ -757,60 +616,17 @@ fn try_create_dynamic_field_info( return Ok(None); } - let struct_tag: StructTag = move_object.type_().clone().into(); - let move_struct_layout = struct_tag_to_move_struct_layout - .get(&struct_tag) - .cloned() - .ok_or_else(|| { - IndexerError::DynamicFieldError(format!( - "Cannot find struct layout in mapfor {:?}.", - struct_tag - )) - })?; - let move_struct = move_object.to_move_struct(&move_struct_layout)?; - let (move_value, type_, object_id) = - DynamicFieldInfo::parse_move_object(&move_struct).tap_err(|e| warn!("{e}"))?; - let name_type = move_object.type_().try_extract_field_name(&type_)?; - let bcs_name = bcs::to_bytes(&move_value.clone().undecorate()).map_err(|e| { - IndexerError::SerdeError(format!( - "Failed to serialize dynamic field name {:?}: {e}", - move_value - )) - })?; - let name = DynamicFieldName { - type_: name_type, - value: SuiMoveValue::from(move_value).to_json_value(), + let type_: StructTag = move_object.type_().clone().into(); + let [name, _] = type_.type_params.as_slice() else { + return Ok(None); }; - Ok(Some(match type_ { - DynamicFieldType::DynamicObject => { - let object = latest_objects - .get(&object_id) - .ok_or(IndexerError::UncategorizedError(anyhow::anyhow!( - "Failed to find object_id {:?} when trying to create dynamic field info", - object_id - )))?; - let version = object.version(); - let digest = object.digest(); - let object_type = object.data.type_().unwrap().clone(); - DynamicFieldInfo { - name, - bcs_name, - type_, - object_type: object_type.to_canonical_string(/* with_prefix */ true), - object_id, - version, - digest, - } - } - DynamicFieldType::DynamicField => DynamicFieldInfo { - name, - bcs_name, - type_, - object_type: move_object.into_type().into_type_params()[1] - .to_canonical_string(/* with_prefix */ true), - object_id: o.id(), - version: o.version(), - digest: o.digest(), + + Ok(Some( + if matches!(name, TypeTag::Struct(s) if DynamicFieldInfo::is_dynamic_object_field_wrapper(s)) + { + DynamicFieldType::DynamicObject + } else { + DynamicFieldType::DynamicField }, - })) + )) } diff --git a/crates/sui-indexer/src/handlers/committer.rs b/crates/sui-indexer/src/handlers/committer.rs index a6a9c2c1df42e..e9b06191047e8 100644 --- a/crates/sui-indexer/src/handlers/committer.rs +++ b/crates/sui-indexer/src/handlers/committer.rs @@ -4,7 +4,6 @@ use std::collections::{BTreeMap, HashMap}; use tap::tap::TapFallible; -use tokio::sync::watch; use tokio_util::sync::CancellationToken; use tracing::instrument; use tracing::{error, info}; @@ -15,7 +14,7 @@ use crate::metrics::IndexerMetrics; use crate::store::IndexerStore; use crate::types::IndexerResult; -use super::{CheckpointDataToCommit, EpochToCommit}; +use super::{CheckpointDataToCommit, CommitterTables, CommitterWatermark, EpochToCommit}; pub(crate) const CHECKPOINT_COMMIT_BATCH_SIZE: usize = 100; @@ -23,7 +22,6 @@ pub async fn start_tx_checkpoint_commit_task( state: S, metrics: IndexerMetrics, tx_indexing_receiver: mysten_metrics::metered_channel::Receiver, - commit_notifier: watch::Sender>, mut next_checkpoint_sequence_number: CheckpointSequenceNumber, cancel: CancellationToken, ) -> IndexerResult<()> @@ -58,9 +56,11 @@ where let epoch = checkpoint.epoch.clone(); batch.push(checkpoint); next_checkpoint_sequence_number += 1; - let epoch_number_option = epoch.as_ref().map(|epoch| epoch.new_epoch.epoch); + let epoch_number_option = epoch.as_ref().map(|epoch| epoch.new_epoch_id()); + // The batch will consist of contiguous checkpoints and at most one epoch boundary at + // the end. if batch.len() == checkpoint_commit_batch_size || epoch.is_some() { - commit_checkpoints(&state, batch, epoch, &metrics, &commit_notifier).await; + commit_checkpoints(&state, batch, epoch, &metrics).await; batch = vec![]; } if let Some(epoch_number) = epoch_number_option { @@ -73,14 +73,18 @@ where })?; } } - if !batch.is_empty() && unprocessed.is_empty() { - commit_checkpoints(&state, batch, None, &metrics, &commit_notifier).await; + if !batch.is_empty() { + commit_checkpoints(&state, batch, None, &metrics).await; batch = vec![]; } } Ok(()) } +/// Writes indexed checkpoint data to the database, and then update watermark upper bounds and +/// metrics. Expects `indexed_checkpoint_batch` to be non-empty, and contain contiguous checkpoints. +/// There can be at most one epoch boundary at the end. If an epoch boundary is detected, +/// epoch-partitioned tables must be advanced. // Unwrap: Caller needs to make sure indexed_checkpoint_batch is not empty #[instrument(skip_all, fields( first = indexed_checkpoint_batch.first().as_ref().unwrap().checkpoint.sequence_number, @@ -91,7 +95,6 @@ async fn commit_checkpoints( indexed_checkpoint_batch: Vec, epoch: Option, metrics: &IndexerMetrics, - commit_notifier: &watch::Sender>, ) where S: IndexerStore + Clone + Sync + Send + 'static, { @@ -133,7 +136,7 @@ async fn commit_checkpoints( } let first_checkpoint_seq = checkpoint_batch.first().as_ref().unwrap().sequence_number; - let last_checkpoint_seq = checkpoint_batch.last().as_ref().unwrap().sequence_number; + let committer_watermark = CommitterWatermark::from(checkpoint_batch.last().unwrap()); let guard = metrics.checkpoint_db_commit_latency.start_timer(); let tx_batch = tx_batch.into_iter().flatten().collect::>(); @@ -150,6 +153,10 @@ async fn commit_checkpoints( let packages_batch = packages_batch.into_iter().flatten().collect::>(); let checkpoint_num = checkpoint_batch.len(); let tx_count = tx_batch.len(); + let raw_checkpoints_batch = checkpoint_batch + .iter() + .map(|c| c.into()) + .collect::>(); { let _step_1_guard = metrics.checkpoint_db_commit_latency_step_1.start_timer(); @@ -167,7 +174,8 @@ async fn commit_checkpoints( state.persist_objects(object_changes_batch.clone()), state.persist_object_history(object_history_changes_batch.clone()), state.persist_full_objects_history(object_history_changes_batch.clone()), - state.persist_object_versions(object_versions_batch.clone()), + state.persist_objects_version(object_versions_batch.clone()), + state.persist_raw_checkpoints(raw_checkpoints_batch), ]; if let Some(epoch_data) = epoch.clone() { persist_tasks.push(state.persist_epoch(epoch_data)); @@ -187,7 +195,8 @@ async fn commit_checkpoints( let is_epoch_end = epoch.is_some(); - // handle partitioning on epoch boundary + // On epoch boundary, we need to modify the existing partitions' upper bound, and introduce a + // new partition for incoming data for the upcoming epoch. if let Some(epoch_data) = epoch { state .advance_epoch(epoch_data) @@ -222,29 +231,37 @@ async fn commit_checkpoints( .await; } - let elapsed = guard.stop_and_record(); + state + .update_watermarks_upper_bound::(committer_watermark) + .await + .tap_err(|e| { + error!( + "Failed to update watermark upper bound with error: {}", + e.to_string() + ); + }) + .expect("Updating watermark upper bound in DB should not fail."); - commit_notifier - .send(Some(last_checkpoint_seq)) - .expect("Commit watcher should not be closed"); + let elapsed = guard.stop_and_record(); info!( elapsed, "Checkpoint {}-{} committed with {} transactions.", first_checkpoint_seq, - last_checkpoint_seq, + committer_watermark.checkpoint_hi_inclusive, tx_count, ); metrics .latest_tx_checkpoint_sequence_number - .set(last_checkpoint_seq as i64); + .set(committer_watermark.checkpoint_hi_inclusive as i64); metrics .total_tx_checkpoint_committed .inc_by(checkpoint_num as u64); metrics.total_transaction_committed.inc_by(tx_count as u64); - metrics - .transaction_per_checkpoint - .observe(tx_count as f64 / (last_checkpoint_seq - first_checkpoint_seq + 1) as f64); + metrics.transaction_per_checkpoint.observe( + tx_count as f64 + / (committer_watermark.checkpoint_hi_inclusive - first_checkpoint_seq + 1) as f64, + ); // 1000.0 is not necessarily the batch size, it's to roughly map average tx commit latency to [0.1, 1] seconds, // which is well covered by DB_COMMIT_LATENCY_SEC_BUCKETS. metrics diff --git a/crates/sui-indexer/src/handlers/mod.rs b/crates/sui-indexer/src/handlers/mod.rs index f96b59b12ecb3..a6c6412f3a42c 100644 --- a/crates/sui-indexer/src/handlers/mod.rs +++ b/crates/sui-indexer/src/handlers/mod.rs @@ -3,20 +3,35 @@ use std::collections::BTreeMap; +use async_trait::async_trait; +use futures::{FutureExt, StreamExt}; + +use serde::{Deserialize, Serialize}; +use sui_rest_api::CheckpointData; +use tokio_util::sync::CancellationToken; + use crate::{ - models::{display::StoredDisplay, obj_indices::StoredObjectVersion}, + errors::IndexerError, + models::{ + display::StoredDisplay, + epoch::{EndOfEpochUpdate, StartOfEpochUpdate}, + obj_indices::StoredObjectVersion, + }, types::{ - EventIndex, IndexedCheckpoint, IndexedDeletedObject, IndexedEpochInfo, IndexedEvent, - IndexedObject, IndexedPackage, IndexedTransaction, TxIndex, + EventIndex, IndexedCheckpoint, IndexedDeletedObject, IndexedEvent, IndexedObject, + IndexedPackage, IndexedTransaction, IndexerResult, TxIndex, }, }; pub mod checkpoint_handler; pub mod committer; -pub mod objects_snapshot_processor; +pub mod objects_snapshot_handler; pub mod pruner; pub mod tx_processor; +pub(crate) const CHECKPOINT_COMMIT_BATCH_SIZE: usize = 100; +pub(crate) const UNPROCESSED_CHECKPOINT_SIZE_LIMIT: usize = 1000; + #[derive(Debug)] pub struct CheckpointDataToCommit { pub checkpoint: IndexedCheckpoint, @@ -40,7 +55,250 @@ pub struct TransactionObjectChangesToCommit { #[derive(Clone, Debug)] pub struct EpochToCommit { - pub last_epoch: Option, - pub new_epoch: IndexedEpochInfo, - pub network_total_transactions: u64, + pub last_epoch: Option, + pub new_epoch: StartOfEpochUpdate, +} + +impl EpochToCommit { + pub fn new_epoch_id(&self) -> u64 { + self.new_epoch.epoch as u64 + } + + pub fn new_epoch_first_checkpoint_id(&self) -> u64 { + self.new_epoch.first_checkpoint_id as u64 + } + + pub fn last_epoch_total_transactions(&self) -> Option { + self.last_epoch + .as_ref() + .map(|e| e.epoch_total_transactions as u64) + } + + pub fn new_epoch_first_tx_sequence_number(&self) -> u64 { + self.new_epoch.first_tx_sequence_number as u64 + } +} + +pub struct CommonHandler { + handler: Box>, +} + +impl CommonHandler { + pub fn new(handler: Box>) -> Self { + Self { handler } + } + + async fn start_transform_and_load( + &self, + cp_receiver: mysten_metrics::metered_channel::Receiver<(CommitterWatermark, T)>, + cancel: CancellationToken, + ) -> IndexerResult<()> { + let checkpoint_commit_batch_size = std::env::var("CHECKPOINT_COMMIT_BATCH_SIZE") + .unwrap_or(CHECKPOINT_COMMIT_BATCH_SIZE.to_string()) + .parse::() + .unwrap(); + let mut stream = mysten_metrics::metered_channel::ReceiverStream::new(cp_receiver) + .ready_chunks(checkpoint_commit_batch_size); + + // Mapping of ordered checkpoint data to ensure that we process them in order. The key is + // just the checkpoint sequence number, and the tuple is (CommitterWatermark, T). + let mut unprocessed: BTreeMap = BTreeMap::new(); + let mut tuple_batch = vec![]; + let mut next_cp_to_process = self + .handler + .get_watermark_hi() + .await? + .map(|n| n.saturating_add(1)) + .unwrap_or_default(); + + loop { + if cancel.is_cancelled() { + return Ok(()); + } + + // Try to fetch new data tuple from the stream + if unprocessed.len() >= UNPROCESSED_CHECKPOINT_SIZE_LIMIT { + tracing::info!( + "Unprocessed checkpoint size reached limit {}, skip reading from stream...", + UNPROCESSED_CHECKPOINT_SIZE_LIMIT + ); + } else { + // Try to fetch new data tuple from the stream + match stream.next().now_or_never() { + Some(Some(tuple_chunk)) => { + if cancel.is_cancelled() { + return Ok(()); + } + for tuple in tuple_chunk { + unprocessed.insert(tuple.0.checkpoint_hi_inclusive, tuple); + } + } + Some(None) => break, // Stream has ended + None => {} // No new data tuple available right now + } + } + + // Process unprocessed checkpoints, even no new checkpoints from stream + let checkpoint_lag_limiter = self.handler.get_max_committable_checkpoint().await?; + while next_cp_to_process <= checkpoint_lag_limiter { + if let Some(data_tuple) = unprocessed.remove(&next_cp_to_process) { + tuple_batch.push(data_tuple); + next_cp_to_process += 1; + } else { + break; + } + } + + if !tuple_batch.is_empty() { + let committer_watermark = tuple_batch.last().unwrap().0; + let batch = tuple_batch.into_iter().map(|t| t.1).collect(); + self.handler.load(batch).await.map_err(|e| { + IndexerError::PostgresWriteError(format!( + "Failed to load transformed data into DB for handler {}: {}", + self.handler.name(), + e + )) + })?; + self.handler.set_watermark_hi(committer_watermark).await?; + tuple_batch = vec![]; + } + } + Err(IndexerError::ChannelClosed(format!( + "Checkpoint channel is closed unexpectedly for handler {}", + self.handler.name() + ))) + } +} + +#[async_trait] +pub trait Handler: Send + Sync { + /// return handler name + fn name(&self) -> String; + + /// commit batch of transformed data to DB + async fn load(&self, batch: Vec) -> IndexerResult<()>; + + /// read high watermark of the table DB + async fn get_watermark_hi(&self) -> IndexerResult>; + + /// Updates the relevant entries on the `watermarks` table with the full `CommitterWatermark`, + /// which tracks the latest epoch, cp, and tx sequence number of the committed batch. + async fn set_watermark_hi(&self, watermark: CommitterWatermark) -> IndexerResult<()>; + + /// By default, return u64::MAX, which means no extra waiting is needed before commiting; + /// get max committable checkpoint, for handlers that want to wait for some condition before commiting, + /// one use-case is the objects snapshot handler, + /// which waits for the lag between snapshot and latest checkpoint to reach a certain threshold. + async fn get_max_committable_checkpoint(&self) -> IndexerResult { + Ok(u64::MAX) + } +} + +/// The indexer writer operates on checkpoint data, which contains information on the current epoch, +/// checkpoint, and transaction. These three numbers form the watermark upper bound for each +/// committed table. The reader and pruner are responsible for determining which of the three units +/// will be used for a particular table. +#[derive(Clone, Copy, Ord, PartialOrd, Eq, PartialEq)] +pub struct CommitterWatermark { + pub epoch_hi_inclusive: u64, + pub checkpoint_hi_inclusive: u64, + pub tx_hi: u64, +} + +impl From<&IndexedCheckpoint> for CommitterWatermark { + fn from(checkpoint: &IndexedCheckpoint) -> Self { + Self { + epoch_hi_inclusive: checkpoint.epoch, + checkpoint_hi_inclusive: checkpoint.sequence_number, + tx_hi: checkpoint.network_total_transactions, + } + } +} + +impl From<&CheckpointData> for CommitterWatermark { + fn from(checkpoint: &CheckpointData) -> Self { + Self { + epoch_hi_inclusive: checkpoint.checkpoint_summary.epoch, + checkpoint_hi_inclusive: checkpoint.checkpoint_summary.sequence_number, + tx_hi: checkpoint.checkpoint_summary.network_total_transactions, + } + } +} + +/// Enum representing tables that the committer handler writes to. +#[derive( + Debug, + Eq, + PartialEq, + strum_macros::Display, + strum_macros::EnumString, + strum_macros::EnumIter, + strum_macros::AsRefStr, + Hash, + Serialize, + Deserialize, + Clone, +)] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum CommitterTables { + // Unpruned tables + ChainIdentifier, + Display, + Epochs, + FeatureFlags, + FullObjectsHistory, + Objects, + ObjectsVersion, + Packages, + ProtocolConfigs, + RawCheckpoints, + + // Prunable tables + ObjectsHistory, + Transactions, + Events, + + EventEmitPackage, + EventEmitModule, + EventSenders, + EventStructInstantiation, + EventStructModule, + EventStructName, + EventStructPackage, + + TxAffectedAddresses, + TxAffectedObjects, + TxCallsPkg, + TxCallsMod, + TxCallsFun, + TxChangedObjects, + TxDigests, + TxInputObjects, + TxKinds, + TxRecipients, + TxSenders, + + Checkpoints, + PrunerCpWatermark, +} + +/// Enum representing tables that the objects snapshot handler writes to. +#[derive( + Debug, + Eq, + PartialEq, + strum_macros::Display, + strum_macros::EnumString, + strum_macros::EnumIter, + strum_macros::AsRefStr, + Hash, + Serialize, + Deserialize, + Clone, +)] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum ObjectsSnapshotHandlerTables { + ObjectsSnapshot, } diff --git a/crates/sui-indexer/src/handlers/objects_snapshot_handler.rs b/crates/sui-indexer/src/handlers/objects_snapshot_handler.rs new file mode 100644 index 0000000000000..816b416fc3743 --- /dev/null +++ b/crates/sui-indexer/src/handlers/objects_snapshot_handler.rs @@ -0,0 +1,127 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use async_trait::async_trait; +use mysten_metrics::get_metrics; +use mysten_metrics::metered_channel::Sender; +use mysten_metrics::spawn_monitored_task; +use sui_data_ingestion_core::Worker; +use sui_rest_api::CheckpointData; +use tokio_util::sync::CancellationToken; +use tracing::info; + +use crate::config::SnapshotLagConfig; +use crate::store::PgIndexerStore; +use crate::types::IndexerResult; +use crate::{metrics::IndexerMetrics, store::IndexerStore}; + +use super::checkpoint_handler::CheckpointHandler; +use super::{CommitterWatermark, ObjectsSnapshotHandlerTables, TransactionObjectChangesToCommit}; +use super::{CommonHandler, Handler}; + +#[derive(Clone)] +pub struct ObjectsSnapshotHandler { + pub store: PgIndexerStore, + pub sender: Sender<(CommitterWatermark, TransactionObjectChangesToCommit)>, + snapshot_config: SnapshotLagConfig, + metrics: IndexerMetrics, +} + +pub struct CheckpointObjectChanges { + pub checkpoint_sequence_number: u64, + pub object_changes: TransactionObjectChangesToCommit, +} + +#[async_trait] +impl Worker for ObjectsSnapshotHandler { + type Result = (); + async fn process_checkpoint(&self, checkpoint: &CheckpointData) -> anyhow::Result<()> { + let transformed_data = CheckpointHandler::index_objects(checkpoint, &self.metrics).await?; + self.sender + .send((CommitterWatermark::from(checkpoint), transformed_data)) + .await?; + Ok(()) + } +} + +#[async_trait] +impl Handler for ObjectsSnapshotHandler { + fn name(&self) -> String { + "objects_snapshot_handler".to_string() + } + + async fn load( + &self, + transformed_data: Vec, + ) -> IndexerResult<()> { + self.store + .persist_objects_snapshot(transformed_data) + .await?; + Ok(()) + } + + async fn get_watermark_hi(&self) -> IndexerResult> { + self.store + .get_latest_object_snapshot_checkpoint_sequence_number() + .await + } + + async fn set_watermark_hi(&self, watermark: CommitterWatermark) -> IndexerResult<()> { + self.store + .update_watermarks_upper_bound::(watermark) + .await?; + + self.metrics + .latest_object_snapshot_sequence_number + .set(watermark.checkpoint_hi_inclusive as i64); + Ok(()) + } + + async fn get_max_committable_checkpoint(&self) -> IndexerResult { + let latest_checkpoint = self.store.get_latest_checkpoint_sequence_number().await?; + Ok(latest_checkpoint + .map(|seq| seq.saturating_sub(self.snapshot_config.snapshot_min_lag as u64)) + .unwrap_or_default()) // hold snapshot handler until at least one checkpoint is in DB + } +} + +pub async fn start_objects_snapshot_handler( + store: PgIndexerStore, + metrics: IndexerMetrics, + snapshot_config: SnapshotLagConfig, + cancel: CancellationToken, +) -> IndexerResult<(ObjectsSnapshotHandler, u64)> { + info!("Starting object snapshot handler..."); + + let global_metrics = get_metrics().unwrap(); + let (sender, receiver) = mysten_metrics::metered_channel::channel( + 600, + &global_metrics + .channel_inflight + .with_label_values(&["objects_snapshot_handler_checkpoint_data"]), + ); + + let objects_snapshot_handler = + ObjectsSnapshotHandler::new(store.clone(), sender, metrics.clone(), snapshot_config); + + let watermark_hi = objects_snapshot_handler.get_watermark_hi().await?; + let common_handler = CommonHandler::new(Box::new(objects_snapshot_handler.clone())); + spawn_monitored_task!(common_handler.start_transform_and_load(receiver, cancel)); + Ok((objects_snapshot_handler, watermark_hi.unwrap_or_default())) +} + +impl ObjectsSnapshotHandler { + pub fn new( + store: PgIndexerStore, + sender: Sender<(CommitterWatermark, TransactionObjectChangesToCommit)>, + metrics: IndexerMetrics, + snapshot_config: SnapshotLagConfig, + ) -> ObjectsSnapshotHandler { + Self { + store, + sender, + metrics, + snapshot_config, + } + } +} diff --git a/crates/sui-indexer/src/handlers/objects_snapshot_processor.rs b/crates/sui-indexer/src/handlers/objects_snapshot_processor.rs deleted file mode 100644 index 2a4968b31b800..0000000000000 --- a/crates/sui-indexer/src/handlers/objects_snapshot_processor.rs +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use async_trait::async_trait; -use futures::StreamExt; -use mysten_metrics::get_metrics; -use mysten_metrics::metered_channel::{Receiver, Sender}; -use mysten_metrics::spawn_monitored_task; -use sui_data_ingestion_core::Worker; -use sui_package_resolver::{PackageStoreWithLruCache, Resolver}; -use sui_rest_api::CheckpointData; -use sui_types::messages_checkpoint::CheckpointSequenceNumber; -use tokio::sync::watch; -use tokio_util::sync::CancellationToken; -use tracing::info; - -use crate::config::SnapshotLagConfig; -use crate::store::package_resolver::{IndexerStorePackageResolver, InterimPackageResolver}; -use crate::store::PgIndexerStore; -use crate::types::IndexerResult; -use crate::{metrics::IndexerMetrics, store::IndexerStore}; -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; - -use super::checkpoint_handler::CheckpointHandler; -use super::tx_processor::IndexingPackageBuffer; -use super::TransactionObjectChangesToCommit; - -pub struct ObjectsSnapshotProcessor { - pub store: PgIndexerStore, - package_buffer: Arc>, - package_resolver: Arc>>, - pub indexed_obj_sender: Sender, - metrics: IndexerMetrics, -} - -pub struct CheckpointObjectChanges { - pub checkpoint_sequence_number: u64, - pub object_changes: TransactionObjectChangesToCommit, -} - -#[async_trait] -impl Worker for ObjectsSnapshotProcessor { - async fn process_checkpoint(&self, checkpoint: &CheckpointData) -> anyhow::Result<()> { - let checkpoint_sequence_number = checkpoint.checkpoint_summary.sequence_number; - // Index the object changes and send them to the committer. - let object_changes: TransactionObjectChangesToCommit = CheckpointHandler::index_objects( - checkpoint, - &self.metrics, - self.package_resolver.clone(), - ) - .await?; - self.indexed_obj_sender - .send(CheckpointObjectChanges { - checkpoint_sequence_number, - object_changes, - }) - .await?; - Ok(()) - } - - fn preprocess_hook(&self, checkpoint: &CheckpointData) -> anyhow::Result<()> { - let package_objects = - CheckpointHandler::get_package_objects(std::slice::from_ref(checkpoint)); - self.package_buffer - .lock() - .unwrap() - .insert_packages(package_objects); - Ok(()) - } -} - -// Start both the ingestion pipeline and committer for objects snapshot table. -pub async fn start_objects_snapshot_processor( - store: PgIndexerStore, - metrics: IndexerMetrics, - snapshot_config: SnapshotLagConfig, - cancel: CancellationToken, -) -> IndexerResult<(ObjectsSnapshotProcessor, u64)> { - info!("Starting object snapshot processor..."); - - let watermark = store - .get_latest_object_snapshot_checkpoint_sequence_number() - .await - .expect("Failed to get latest snapshot checkpoint sequence number from DB") - .map(|seq| seq + 1) - .unwrap_or_default(); - - let (commit_notifier, commit_receiver) = watch::channel(None); - - let global_metrics = get_metrics().unwrap(); - // Channel for actually communicating indexed object changes between the ingestion pipeline and the committer. - let (indexed_obj_sender, indexed_obj_receiver) = mysten_metrics::metered_channel::channel( - // TODO: placeholder for now - 600, - &global_metrics - .channel_inflight - .with_label_values(&["obj_indexing_for_snapshot"]), - ); - - // Start an ingestion pipeline with the objects snapshot processor as a worker. - let worker = ObjectsSnapshotProcessor::new( - store.clone(), - indexed_obj_sender, - commit_receiver, - metrics.clone(), - ); - - // Now start the task that will commit the indexed object changes to the store. - spawn_monitored_task!(ObjectsSnapshotProcessor::commit_objects_snapshot( - store, - watermark, - indexed_obj_receiver, - commit_notifier, - metrics, - snapshot_config, - cancel, - )); - Ok((worker, watermark)) -} - -impl ObjectsSnapshotProcessor { - pub fn new( - store: PgIndexerStore, - indexed_obj_sender: Sender, - commit_receiver: watch::Receiver>, - metrics: IndexerMetrics, - ) -> ObjectsSnapshotProcessor { - // Start the package buffer used for buffering packages before they are written to the db. - // We include a commit receiver which will be paged when a checkpoint has been processed and - // the corresponding package data can be deleted from the buffer. - let package_buffer = IndexingPackageBuffer::start(commit_receiver); - let package_db_resolver = IndexerStorePackageResolver::new(store.pool()); - let in_mem_package_resolver = InterimPackageResolver::new( - package_db_resolver, - package_buffer.clone(), - metrics.clone(), - ); - let cached_package_resolver = PackageStoreWithLruCache::new(in_mem_package_resolver); - let package_resolver = Arc::new(Resolver::new(cached_package_resolver)); - - Self { - store, - indexed_obj_sender, - package_resolver, - package_buffer, - metrics, - } - } - - // Receives object changes from the ingestion pipeline and commits them to the store, - // keeping the appropriate amount of checkpoint lag behind the rest of the indexer. - pub async fn commit_objects_snapshot( - store: PgIndexerStore, - watermark: CheckpointSequenceNumber, - indexed_obj_receiver: Receiver, - commit_notifier: watch::Sender>, - metrics: IndexerMetrics, - config: SnapshotLagConfig, - cancel: CancellationToken, - ) -> IndexerResult<()> { - let batch_size = 100; - let mut stream = mysten_metrics::metered_channel::ReceiverStream::new(indexed_obj_receiver) - .ready_chunks(batch_size); - - let mut start_cp = watermark; - // To prevent the processor from committing more data than allowed by the min lag, keep an - // in-memory buffer of changes that should not be committed to `objects_snapshot` yet. - let mut unprocessed = HashMap::new(); - let mut batch = vec![]; - - info!("Starting objects snapshot committer..."); - loop { - tokio::select! { - _ = cancel.cancelled() => { - info!("Shutdown signal received, terminating object snapshot processor"); - return Ok(()); - } - _ = tokio::time::sleep(std::time::Duration::from_secs(config.sleep_duration)) => { - let latest_indexer_cp = store - .get_latest_checkpoint_sequence_number() - .await? - .unwrap_or_default(); - - // We update the snapshot table when it falls behind the rest of the indexer by - // more than the min_lag. When `latest_indexer_cp = start_cp + - // config.snapshot_min_lag`, we have not actually indexed `start_cp` yet, hence - // why the condition is `>=`. - while latest_indexer_cp >= start_cp + config.snapshot_min_lag as u64 { - // The maximum checkpoint sequence number that can be committed to the - // `objects_snapshot` table. - let max_allowed_cp = latest_indexer_cp - config.snapshot_min_lag as u64; - - if let Some(new_changes) = stream.next().await { - for checkpoint in new_changes { - unprocessed.insert(checkpoint.checkpoint_sequence_number, checkpoint); - } - } - - // Collect the checkpoint object changes to write to `objects_snapshot`, - // stopping when there are gaps in the sequence of unprocessed checkpoints. - // This is an inclusive range, so if `start_cp` is equal to - // `max_allowed_cp`, we'll still index the one checkpoint. - for cp in start_cp..=max_allowed_cp { - if let Some(checkpoint) = unprocessed.remove(&cp) { - batch.push(checkpoint); - } - else { - break; - } - } - - if !batch.is_empty() { - let first_checkpoint_seq = batch.first().as_ref().unwrap().checkpoint_sequence_number; - let last_checkpoint_seq = batch.last().as_ref().unwrap().checkpoint_sequence_number; - info!("Objects snapshot processor is updating objects snapshot table from {} to {}", first_checkpoint_seq, last_checkpoint_seq); - - store.persist_objects_snapshot(batch.drain(..).map(|obj| obj.object_changes).collect()) - .await - .unwrap_or_else(|_| panic!("Failed to backfill objects snapshot from {} to {}", first_checkpoint_seq, last_checkpoint_seq)); - start_cp = last_checkpoint_seq + 1; - - // Tells the package buffer that this checkpoint has been processed and - // the corresponding package data can be deleted. - commit_notifier.send(Some(last_checkpoint_seq)).expect("Commit watcher should not be closed"); - metrics - .latest_object_snapshot_sequence_number - .set(last_checkpoint_seq as i64); - } - } - } - } - } - } -} diff --git a/crates/sui-indexer/src/handlers/pruner.rs b/crates/sui-indexer/src/handlers/pruner.rs index 21dca59607f9d..85b6faa12f071 100644 --- a/crates/sui-indexer/src/handlers/pruner.rs +++ b/crates/sui-indexer/src/handlers/pruner.rs @@ -1,12 +1,15 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use mysten_metrics::spawn_monitored_task; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::time::Duration; - +use strum_macros; use tokio_util::sync::CancellationToken; use tracing::{error, info}; +use crate::config::RetentionConfig; use crate::errors::IndexerError; use crate::store::pg_partition_manager::PgPartitionManager; use crate::store::PgIndexerStore; @@ -15,41 +18,144 @@ use crate::{metrics::IndexerMetrics, store::IndexerStore, types::IndexerResult}; pub struct Pruner { pub store: PgIndexerStore, pub partition_manager: PgPartitionManager, + // TODO: (wlmyng) - we can remove this when pruner logic is updated to use `retention_policies`. pub epochs_to_keep: u64, + pub retention_policies: HashMap, pub metrics: IndexerMetrics, } +/// Enum representing tables that the pruner is allowed to prune. This corresponds to table names in +/// the database, and should be used in lieu of string literals. This enum is also meant to +/// facilitate the process of determining which unit (epoch, cp, or tx) should be used for the +/// table's range. Pruner will ignore any table that is not listed here. +#[derive( + Debug, + Eq, + PartialEq, + strum_macros::Display, + strum_macros::EnumString, + strum_macros::EnumIter, + strum_macros::AsRefStr, + Hash, + Serialize, + Deserialize, + Clone, +)] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum PrunableTable { + ObjectsHistory, + Transactions, + Events, + + EventEmitPackage, + EventEmitModule, + EventSenders, + EventStructInstantiation, + EventStructModule, + EventStructName, + EventStructPackage, + + TxAffectedAddresses, + TxAffectedObjects, + TxCallsPkg, + TxCallsMod, + TxCallsFun, + TxChangedObjects, + TxDigests, + TxInputObjects, + TxKinds, + TxRecipients, + TxSenders, + + Checkpoints, + PrunerCpWatermark, +} + +impl PrunableTable { + pub fn select_reader_lo(&self, cp: u64, tx: u64) -> u64 { + match self { + PrunableTable::ObjectsHistory => cp, + PrunableTable::Transactions => tx, + PrunableTable::Events => tx, + + PrunableTable::EventEmitPackage => tx, + PrunableTable::EventEmitModule => tx, + PrunableTable::EventSenders => tx, + PrunableTable::EventStructInstantiation => tx, + PrunableTable::EventStructModule => tx, + PrunableTable::EventStructName => tx, + PrunableTable::EventStructPackage => tx, + + PrunableTable::TxAffectedAddresses => tx, + PrunableTable::TxAffectedObjects => tx, + PrunableTable::TxCallsPkg => tx, + PrunableTable::TxCallsMod => tx, + PrunableTable::TxCallsFun => tx, + PrunableTable::TxChangedObjects => tx, + PrunableTable::TxDigests => tx, + PrunableTable::TxInputObjects => tx, + PrunableTable::TxKinds => tx, + PrunableTable::TxRecipients => tx, + PrunableTable::TxSenders => tx, + + PrunableTable::Checkpoints => cp, + PrunableTable::PrunerCpWatermark => cp, + } + } +} + impl Pruner { + /// Instantiates a pruner with default retention and overrides. Pruner will finalize the + /// retention policies so there is a value for every prunable table. pub fn new( store: PgIndexerStore, - epochs_to_keep: u64, + retention_config: RetentionConfig, metrics: IndexerMetrics, ) -> Result { let partition_manager = PgPartitionManager::new(store.pool())?; + let epochs_to_keep = retention_config.epochs_to_keep; + let retention_policies = retention_config.retention_policies(); + Ok(Self { store, - partition_manager, epochs_to_keep, + partition_manager, + retention_policies, metrics, }) } + /// Given a table name, return the number of epochs to keep for that table. Return `None` if the + /// table is not prunable. + fn table_retention(&self, table_name: &str) -> Option { + if let Ok(variant) = table_name.parse::() { + self.retention_policies.get(&variant).copied() + } else { + None + } + } + pub async fn start(&self, cancel: CancellationToken) -> IndexerResult<()> { - loop { - if cancel.is_cancelled() { - info!("Pruner task cancelled."); - return Ok(()); - } + let store_clone = self.store.clone(); + let retention_policies = self.retention_policies.clone(); + let cancel_clone = cancel.clone(); + spawn_monitored_task!(update_watermarks_lower_bounds_task( + store_clone, + retention_policies, + cancel_clone + )); - let (mut min_epoch, mut max_epoch) = self.store.get_available_epoch_range().await?; - while min_epoch + self.epochs_to_keep > max_epoch { - if cancel.is_cancelled() { - info!("Pruner task cancelled."); - return Ok(()); - } + let mut last_seen_max_epoch = 0; + // The first epoch that has not yet been pruned. + let mut next_prune_epoch = None; + while !cancel.is_cancelled() { + let (min_epoch, max_epoch) = self.store.get_available_epoch_range().await?; + if max_epoch == last_seen_max_epoch { tokio::time::sleep(Duration::from_secs(5)).await; - (min_epoch, max_epoch) = self.store.get_available_epoch_range().await?; + continue; } + last_seen_max_epoch = max_epoch; // Not all partitioned tables are epoch-partitioned, so we need to filter them out. let table_partitions: HashMap<_, _> = self @@ -65,43 +171,118 @@ impl Pruner { .collect(); for (table_name, (min_partition, max_partition)) in &table_partitions { - if max_epoch != *max_partition { - error!( - "Epochs are out of sync for table {}: max_epoch={}, max_partition={}", - table_name, max_epoch, max_partition - ); - } - // drop partitions if pruning is enabled afterwards, where all epochs before min_epoch - // would have been pruned already if the pruner was running. - for epoch in *min_partition..min_epoch { - self.partition_manager - .drop_table_partition(table_name.clone(), epoch) - .await?; - info!( - "Batch dropped table partition {} epoch {}", - table_name, epoch - ); + if let Some(epochs_to_keep) = self.table_retention(table_name) { + if last_seen_max_epoch != *max_partition { + error!( + "Epochs are out of sync for table {}: max_epoch={}, max_partition={}", + table_name, last_seen_max_epoch, max_partition + ); + } + + for epoch in + *min_partition..last_seen_max_epoch.saturating_sub(epochs_to_keep - 1) + { + if cancel.is_cancelled() { + info!("Pruner task cancelled."); + return Ok(()); + } + self.partition_manager + .drop_table_partition(table_name.clone(), epoch) + .await?; + info!( + "Batch dropped table partition {} epoch {}", + table_name, epoch + ); + } } } - for epoch in min_epoch..max_epoch.saturating_sub(self.epochs_to_keep - 1) { + // TODO: (wlmyng) Once we have the watermarks table, we can iterate through each row + // returned from `watermarks`, look it up against `retention_policies`, and process them + // independently. This also means that pruning overrides will only apply for + // epoch-partitioned tables right now. + let prune_to_epoch = last_seen_max_epoch.saturating_sub(self.epochs_to_keep - 1); + let prune_start_epoch = next_prune_epoch.unwrap_or(min_epoch); + for epoch in prune_start_epoch..prune_to_epoch { if cancel.is_cancelled() { info!("Pruner task cancelled."); return Ok(()); } info!("Pruning epoch {}", epoch); - for table_name in table_partitions.keys() { - self.partition_manager - .drop_table_partition(table_name.clone(), epoch) - .await?; - info!("Dropped table partition {} epoch {}", table_name, epoch); - } - self.store.prune_epoch(epoch).await.unwrap_or_else(|e| { - error!("Failed to prune epoch {}: {}", epoch, e); - }); + if let Err(err) = self.store.prune_epoch(epoch).await { + error!("Failed to prune epoch {}: {}", epoch, err); + break; + }; self.metrics.last_pruned_epoch.set(epoch as i64); info!("Pruned epoch {}", epoch); + next_prune_epoch = Some(epoch + 1); + } + } + info!("Pruner task cancelled."); + Ok(()) + } +} + +/// Task to periodically query the `watermarks` table and update the lower bounds for all watermarks +/// if the entry exceeds epoch-level retention policy. +async fn update_watermarks_lower_bounds_task( + store: PgIndexerStore, + retention_policies: HashMap, + cancel: CancellationToken, +) -> IndexerResult<()> { + let mut interval = tokio::time::interval(Duration::from_secs(5)); + loop { + tokio::select! { + _ = cancel.cancelled() => { + info!("Pruner watermark lower bound update task cancelled."); + return Ok(()); } + _ = interval.tick() => { + update_watermarks_lower_bounds(&store, &retention_policies, &cancel).await?; + } + } + } +} + +/// Fetches all entries from the `watermarks` table, and updates the `reader_lo` for each entry if +/// its epoch range exceeds the respective retention policy. +async fn update_watermarks_lower_bounds( + store: &PgIndexerStore, + retention_policies: &HashMap, + cancel: &CancellationToken, +) -> IndexerResult<()> { + let (watermarks, _) = store.get_watermarks().await?; + let mut lower_bound_updates = vec![]; + + for watermark in watermarks.iter() { + if cancel.is_cancelled() { + info!("Pruner watermark lower bound update task cancelled."); + return Ok(()); } + + let Some(prunable_table) = watermark.entity() else { + continue; + }; + + let Some(epochs_to_keep) = retention_policies.get(&prunable_table) else { + error!( + "No retention policy found for prunable table {}", + prunable_table + ); + continue; + }; + + if let Some(new_epoch_lo) = watermark.new_epoch_lo(*epochs_to_keep) { + lower_bound_updates.push((prunable_table, new_epoch_lo)); + }; } + + if !lower_bound_updates.is_empty() { + store + .update_watermarks_lower_bound(lower_bound_updates) + .await?; + info!("Finished updating lower bounds for watermarks"); + } + + Ok(()) } diff --git a/crates/sui-indexer/src/handlers/tx_processor.rs b/crates/sui-indexer/src/handlers/tx_processor.rs index 2ee43f5880ead..0a8051ee8eabb 100644 --- a/crates/sui-indexer/src/handlers/tx_processor.rs +++ b/crates/sui-indexer/src/handlers/tx_processor.rs @@ -1,124 +1,24 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -// TODO remove the dead_code attribute after integration is done -#![allow(dead_code)] - -use async_trait::async_trait; -use mysten_metrics::monitored_scope; -use mysten_metrics::spawn_monitored_task; -use sui_rest_api::CheckpointData; -use tokio::sync::watch; - use std::collections::HashMap; -use std::sync::{Arc, Mutex}; -use sui_types::object::Object; -use tokio::time::Duration; -use tokio::time::Instant; +use async_trait::async_trait; use sui_json_rpc::get_balance_changes_from_effect; use sui_json_rpc::get_object_changes; use sui_json_rpc::ObjectProvider; +use sui_rest_api::CheckpointData; +use sui_types::base_types::ObjectID; use sui_types::base_types::SequenceNumber; use sui_types::digests::TransactionDigest; use sui_types::effects::{TransactionEffects, TransactionEffectsAPI}; +use sui_types::object::Object; use sui_types::transaction::{TransactionData, TransactionDataAPI}; -use tracing::info; - -use sui_types::base_types::ObjectID; -use sui_types::messages_checkpoint::CheckpointSequenceNumber; use crate::errors::IndexerError; use crate::metrics::IndexerMetrics; -use crate::types::IndexedPackage; use crate::types::{IndexedObjectChange, IndexerResult}; -// GC the buffer every 300 checkpoints, or 5 minutes -pub const BUFFER_GC_INTERVAL: Duration = Duration::from_secs(300); -/// An in-mem buffer for modules during writer path indexing. -/// It has static lifetime. Since we batch process checkpoints, -/// it's possible that when a package is looked up (e.g. to create dynamic field), -/// it has not been persisted in the database yet. So it works as an in-mem -/// store for package resolution. To avoid bloating memory, we GC modules -/// that are older than the committed checkpoints. -pub struct IndexingPackageBuffer { - packages: HashMap< - ObjectID, - ( - Arc, - u64, /* package version */ - CheckpointSequenceNumber, - ), - >, -} - -impl IndexingPackageBuffer { - pub fn start( - commit_watcher: watch::Receiver>, - ) -> Arc> { - let cache = Arc::new(Mutex::new(Self { - packages: HashMap::new(), - })); - let cache_clone = cache.clone(); - spawn_monitored_task!(Self::remove_committed(cache_clone, commit_watcher)); - cache - } - - pub async fn remove_committed( - cache: Arc>, - commit_watcher: watch::Receiver>, - ) { - let mut interval = tokio::time::interval_at(Instant::now(), BUFFER_GC_INTERVAL); - interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); - loop { - interval.tick().await; - let _scope = monitored_scope("IndexingPackageBuffer::remove_committed"); - let Some(committed_checkpoint) = *commit_watcher.borrow() else { - continue; - }; - let mut cache = cache.lock().unwrap(); - info!( - "About to GC packages older than: {committed_checkpoint}. Cache size is {}", - cache.packages.len() - ); - let mut to_remove = vec![]; - for (id, (_, _, checkpoint_seq)) in cache.packages.iter() { - if *checkpoint_seq <= committed_checkpoint { - to_remove.push(*id); - } - } - for id in to_remove { - cache.packages.remove(&id); - } - } - } - - pub fn insert_packages(&mut self, new_package_objects: Vec<(IndexedPackage, Object)>) { - let new_packages = new_package_objects - .into_iter() - .map(|(p, obj)| { - ( - p.package_id, - ( - Arc::new(obj), - p.move_package.version().value(), - p.checkpoint_sequence_number, - ), - ) - }) - .collect::>(); - self.packages.extend(new_packages); - } - - pub fn get_package(&self, id: &ObjectID) -> Option> { - self.packages.get(id).as_ref().map(|(o, _, _)| o.clone()) - } - - pub fn get_version(&self, id: &ObjectID) -> Option { - self.packages.get(id).as_ref().map(|(_, v, _)| *v) - } -} - pub struct InMemObjectCache { id_map: HashMap, seq_map: HashMap<(ObjectID, SequenceNumber), Object>, @@ -187,6 +87,7 @@ impl TxChangesProcessor { .start_timer(); let object_change: Vec<_> = get_object_changes( self, + effects, tx.sender(), effects.modified_at_versions(), effects.all_changed_objects(), diff --git a/crates/sui-indexer/src/indexer.rs b/crates/sui-indexer/src/indexer.rs index ab269d31c5eb8..240e295179094 100644 --- a/crates/sui-indexer/src/indexer.rs +++ b/crates/sui-indexer/src/indexer.rs @@ -19,11 +19,11 @@ use sui_data_ingestion_core::{ use sui_types::messages_checkpoint::CheckpointSequenceNumber; use crate::build_json_rpc_server; -use crate::config::{IngestionConfig, JsonRpcConfig, PruningOptions, SnapshotLagConfig}; +use crate::config::{IngestionConfig, JsonRpcConfig, RetentionConfig, SnapshotLagConfig}; use crate::database::ConnectionPool; use crate::errors::IndexerError; use crate::handlers::checkpoint_handler::new_handlers; -use crate::handlers::objects_snapshot_processor::start_objects_snapshot_processor; +use crate::handlers::objects_snapshot_handler::start_objects_snapshot_handler; use crate::handlers::pruner::Pruner; use crate::indexer_reader::IndexerReader; use crate::metrics::IndexerMetrics; @@ -36,32 +36,14 @@ impl Indexer { config: &IngestionConfig, store: PgIndexerStore, metrics: IndexerMetrics, - ) -> Result<(), IndexerError> { - let snapshot_config = SnapshotLagConfig::default(); - Indexer::start_writer_with_config( - config, - store, - metrics, - snapshot_config, - PruningOptions::default(), - CancellationToken::new(), - ) - .await - } - - pub async fn start_writer_with_config( - config: &IngestionConfig, - store: PgIndexerStore, - metrics: IndexerMetrics, snapshot_config: SnapshotLagConfig, - pruning_options: PruningOptions, + retention_config: Option, cancel: CancellationToken, ) -> Result<(), IndexerError> { info!( "Sui Indexer Writer (version {:?}) started...", env!("CARGO_PKG_VERSION") ); - info!("Sui Indexer Writer config: {config:?}",); let primary_watermark = store @@ -79,7 +61,7 @@ impl Indexer { }; // Start objects snapshot processor, which is a separate pipeline with its ingestion pipeline. - let (object_snapshot_worker, object_snapshot_watermark) = start_objects_snapshot_processor( + let (object_snapshot_worker, object_snapshot_watermark) = start_objects_snapshot_handler( store.clone(), metrics.clone(), snapshot_config, @@ -87,14 +69,10 @@ impl Indexer { ) .await?; - if let Some(epochs_to_keep) = pruning_options.epochs_to_keep { - info!( - "Starting indexer pruner with epochs to keep: {}", - epochs_to_keep - ); - assert!(epochs_to_keep > 0, "Epochs to keep must be positive"); - let pruner = Pruner::new(store.clone(), epochs_to_keep, metrics.clone())?; - spawn_monitored_task!(pruner.start(CancellationToken::new())); + if let Some(retention_config) = retention_config { + let pruner = Pruner::new(store.clone(), retention_config, metrics.clone())?; + let cancel_clone = cancel.clone(); + spawn_monitored_task!(pruner.start(cancel_clone)); } // If we already have chain identifier indexed (i.e. the first checkpoint has been indexed), @@ -109,6 +87,8 @@ impl Indexer { let mut exit_senders = vec![]; let mut executors = vec![]; + // Ingestion task watermarks are snapshotted once on indexer startup based on the + // corresponding watermark table before being handed off to the ingestion task. let progress_store = ShimIndexerProgressStore::new(vec![ ("primary".to_string(), primary_watermark), ("object_snapshot".to_string(), object_snapshot_watermark), @@ -183,13 +163,14 @@ impl Indexer { config: &JsonRpcConfig, registry: &Registry, pool: ConnectionPool, + cancel: CancellationToken, ) -> Result<(), IndexerError> { info!( "Sui Indexer Reader (version {:?}) started...", env!("CARGO_PKG_VERSION") ); let indexer_reader = IndexerReader::new(pool); - let handle = build_json_rpc_server(registry, indexer_reader, config) + let handle = build_json_rpc_server(registry, indexer_reader, config, cancel) .await .expect("Json rpc server should not run into errors upon start."); tokio::spawn(async move { handle.stopped().await }) diff --git a/crates/sui-indexer/src/indexer_reader.rs b/crates/sui-indexer/src/indexer_reader.rs index 779bc91a0049d..d0eed2ee4a461 100644 --- a/crates/sui-indexer/src/indexer_reader.rs +++ b/crates/sui-indexer/src/indexer_reader.rs @@ -1,19 +1,22 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::sync::Arc; +use anyhow::anyhow; use anyhow::Result; use diesel::{ - dsl::sql, sql_types::Bool, ExpressionMethods, OptionalExtension, QueryDsl, - TextExpressionMethods, + dsl::sql, sql_types::Bool, ExpressionMethods, JoinOnDsl, NullableExpressionMethods, + OptionalExtension, QueryDsl, SelectableHelper, TextExpressionMethods, }; use itertools::Itertools; +use std::sync::Arc; +use sui_types::dynamic_field::visitor as DFV; +use sui_types::object::bounded_visitor::BoundedVisitor; use tap::{Pipe, TapFallible}; use tracing::{debug, error, warn}; use fastcrypto::encoding::Encoding; use fastcrypto::encoding::Hex; -use move_core_types::annotated_value::{MoveStructLayout, MoveTypeLayout}; +use move_core_types::annotated_value::MoveStructLayout; use move_core_types::language_storage::{StructTag, TypeTag}; use sui_json_rpc_types::DisplayFieldsResponse; use sui_json_rpc_types::{Balance, Coin as SuiCoin, SuiCoinMetadata, SuiMoveValue}; @@ -30,7 +33,7 @@ use sui_types::{ base_types::{ObjectID, SuiAddress, VersionNumber}, committee::EpochId, digests::TransactionDigest, - dynamic_field::{DynamicFieldInfo, DynamicFieldType}, + dynamic_field::DynamicFieldInfo, object::{Object, ObjectRead}, sui_system_state::{sui_system_state_summary::SuiSystemStateSummary, SuiSystemStateTrait}, }; @@ -39,6 +42,8 @@ use sui_types::{coin::CoinMetadata, event::EventID}; use crate::database::ConnectionPool; use crate::db::ConnectionPoolConfig; use crate::models::transactions::{stored_events_to_events, StoredTransactionEvents}; +use crate::schema::pruner_cp_watermark; +use crate::schema::tx_digests; use crate::{ errors::IndexerError, models::{ @@ -323,27 +328,7 @@ impl IndexerReader { Some(stored_epoch) => stored_epoch, None => return Err(IndexerError::InvalidArgumentError("Invalid epoch".into())), }; - - let system_state_summary: SuiSystemStateSummary = - bcs::from_bytes(&stored_epoch.system_state).map_err(|_| { - IndexerError::PersistentStorageDataCorruptionError(format!( - "Failed to deserialize `system_state` for epoch {:?}", - epoch, - )) - })?; - #[cfg(debug_assertions)] - { - let correct_system_state_summary: SuiSystemStateSummary = - serde_json::from_value(stored_epoch.system_state_summary_json.clone().unwrap()) - .unwrap(); - // The old system state summary is incorrect and its epoch will be offset by 1. - // This is fixed in the new system state summary. Assert it here to double check. - assert_eq!( - correct_system_state_summary.epoch, - stored_epoch.epoch as u64 - ); - } - Ok(system_state_summary) + stored_epoch.get_json_system_state_summary() } async fn get_checkpoint_from_db( @@ -461,7 +446,12 @@ impl IndexerReader { .collect::>(); transactions::table - .filter(transactions::transaction_digest.eq_any(digests)) + .inner_join( + tx_digests::table + .on(transactions::tx_sequence_number.eq(tx_digests::tx_sequence_number)), + ) + .filter(tx_digests::tx_digest.eq_any(digests)) + .select(StoredTransaction::as_select()) .load::(&mut connection) .await .map_err(Into::into) @@ -628,11 +618,19 @@ impl IndexerReader { let mut connection = self.pool.get().await?; + let tx_range: (i64, i64) = pruner_cp_watermark::dsl::pruner_cp_watermark + .select(( + pruner_cp_watermark::min_tx_sequence_number, + pruner_cp_watermark::max_tx_sequence_number, + )) + .filter(pruner_cp_watermark::checkpoint_sequence_number.eq(checkpoint_seq as i64)) + .first::<(i64, i64)>(&mut connection) + .await?; + let mut query = transactions::table - .filter(transactions::checkpoint_sequence_number.eq(checkpoint_seq as i64)) + .filter(transactions::tx_sequence_number.between(tx_range.0, tx_range.1)) .into_boxed(); - // Translate transaction digest cursor to tx sequence number if let Some(cursor_tx_seq) = cursor_tx_seq { if is_descending { query = query.filter(transactions::tx_sequence_number.lt(cursor_tx_seq)); @@ -666,9 +664,9 @@ impl IndexerReader { let mut connection = self.pool.get().await?; let cursor_tx_seq = if let Some(cursor) = cursor { - let tx_seq = transactions::table - .select(transactions::tx_sequence_number) - .filter(transactions::transaction_digest.eq(cursor.into_inner().to_vec())) + let tx_seq = tx_digests::table + .select(tx_digests::tx_sequence_number) + .filter(tx_digests::tx_digest.eq(cursor.into_inner().to_vec())) .first::(&mut connection) .await?; Some(tx_seq) @@ -707,17 +705,15 @@ impl IndexerReader { let package = Hex::encode(package.to_vec()); match (module, function) { (Some(module), Some(function)) => ( - "tx_calls_fun".into(), + "tx_calls_fun".to_owned(), format!( - "package = '\\x{}'::bytea AND module = '{}' AND func = '{}'", - package, module, function + "package = '\\x{package}'::bytea AND module = '{module}' AND func = '{function}'", ), ), (Some(module), None) => ( - "tx_calls_mod".into(), + "tx_calls_mod".to_owned(), format!( - "package = '\\x{}'::bytea AND module = '{}'", - package, module + "package = '\\x{package}'::bytea AND module = '{module}'", ), ), (None, Some(_)) => { @@ -726,105 +722,39 @@ impl IndexerReader { )); } (None, None) => ( - "tx_calls_pkg".into(), - format!("package = '\\x{}'::bytea", package), + "tx_calls_pkg".to_owned(), + format!("package = '\\x{package}'::bytea"), ), } } - Some(TransactionFilter::InputObject(object_id)) => { + Some(TransactionFilter::AffectedObject(object_id)) => { let object_id = Hex::encode(object_id.to_vec()); ( - "tx_input_objects".into(), - format!("object_id = '\\x{}'::bytea", object_id), - ) - } - Some(TransactionFilter::ChangedObject(object_id)) => { - let object_id = Hex::encode(object_id.to_vec()); - ( - "tx_changed_objects".into(), - format!("object_id = '\\x{}'::bytea", object_id), + "tx_affected_objects".to_owned(), + format!("affected = '\\x{object_id}'::bytea"), ) } Some(TransactionFilter::FromAddress(from_address)) => { let from_address = Hex::encode(from_address.to_vec()); ( - "tx_senders".into(), - format!("sender = '\\x{}'::bytea", from_address), - ) - } - Some(TransactionFilter::ToAddress(to_address)) => { - let to_address = Hex::encode(to_address.to_vec()); - ( - "tx_recipients".into(), - format!("recipient = '\\x{}'::bytea", to_address), + "tx_affected_addresses".to_owned(), + format!("sender = '\\x{from_address}'::bytea AND affected = '\\x{from_address}'::bytea"), ) } Some(TransactionFilter::FromAndToAddress { from, to }) => { let from_address = Hex::encode(from.to_vec()); let to_address = Hex::encode(to.to_vec()); - // Need to remove ambiguities for tx_sequence_number column - let cursor_clause = if let Some(cursor_tx_seq) = cursor_tx_seq { - if is_descending { - format!( - "AND tx_senders.{TX_SEQUENCE_NUMBER_STR} < {}", - cursor_tx_seq - ) - } else { - format!( - "AND tx_senders.{TX_SEQUENCE_NUMBER_STR} > {}", - cursor_tx_seq - ) - } - } else { - "".to_string() - }; - let inner_query = format!( - "(SELECT tx_senders.{TX_SEQUENCE_NUMBER_STR} \ - FROM tx_senders \ - JOIN tx_recipients \ - ON tx_senders.{TX_SEQUENCE_NUMBER_STR} = tx_recipients.{TX_SEQUENCE_NUMBER_STR} \ - WHERE tx_senders.sender = '\\x{}'::BYTEA \ - AND tx_recipients.recipient = '\\x{}'::BYTEA \ - {} \ - ORDER BY {TX_SEQUENCE_NUMBER_STR} {} \ - LIMIT {}) AS inner_query - ", - from_address, - to_address, - cursor_clause, - order_str, - limit, - ); - (inner_query, "1 = 1".into()) + ( + "tx_affected_addresses".to_owned(), + format!("sender = '\\x{from_address}'::bytea AND affected = '\\x{to_address}'::bytea"), + ) } Some(TransactionFilter::FromOrToAddress { addr }) => { let address = Hex::encode(addr.to_vec()); - let inner_query = format!( - "( \ - ( \ - SELECT {TX_SEQUENCE_NUMBER_STR} FROM tx_senders \ - WHERE sender = '\\x{}'::BYTEA {} \ - ORDER BY {TX_SEQUENCE_NUMBER_STR} {} \ - LIMIT {} \ - ) \ - UNION \ - ( \ - SELECT {TX_SEQUENCE_NUMBER_STR} FROM tx_recipients \ - WHERE recipient = '\\x{}'::BYTEA {} \ - ORDER BY {TX_SEQUENCE_NUMBER_STR} {} \ - LIMIT {} \ - ) \ - ) AS combined", - address, - cursor_clause, - order_str, - limit, - address, - cursor_clause, - order_str, - limit, - ); - (inner_query, "1 = 1".into()) + ( + "tx_affected_addresses".to_owned(), + format!("affected = '\\x{address}'::bytea"), + ) } Some( TransactionFilter::TransactionKind(_) | TransactionFilter::TransactionKindIn(_), @@ -833,9 +763,19 @@ impl IndexerReader { "TransactionKind filter is not supported.".into(), )); } + Some(TransactionFilter::InputObject(_) | TransactionFilter::ChangedObject(_)) => { + return Err(IndexerError::NotSupportedError( + "InputObject and OutputObject filters are not supported, please use AffectedObject instead.".into() + )) + } + Some(TransactionFilter::ToAddress(_)) => { + return Err(IndexerError::NotSupportedError( + "ToAddress filter is not supported, please use FromOrToAddress instead.".into() + )) + } None => { // apply no filter - ("transactions".into(), "1 = 1".into()) + ("transactions".to_owned(), "1 = 1".into()) } }; @@ -904,8 +844,17 @@ impl IndexerReader { let mut connection = self.pool.get().await?; + // Use the tx_digests lookup table for the corresponding tx_sequence_number, and then fetch + // event-relevant data from the entry on the transactions table. let (timestamp_ms, serialized_events) = transactions::table - .filter(transactions::transaction_digest.eq(digest.into_inner().to_vec())) + .filter( + transactions::tx_sequence_number + .nullable() + .eq(tx_digests::table + .select(tx_digests::tx_sequence_number) + .filter(tx_digests::tx_digest.eq(digest.into_inner().to_vec())) + .single_value()), + ) .select((transactions::timestamp_ms, transactions::events)) .first::<(i64, StoredTransactionEvents)>(&mut connection) .await?; @@ -923,43 +872,78 @@ impl IndexerReader { Ok(sui_tx_events.map_or(vec![], |ste| ste.data)) } - fn query_events_by_tx_digest_query( + async fn query_events_by_tx_digest( &self, tx_digest: TransactionDigest, cursor: Option, + cursor_tx_seq: i64, limit: usize, descending_order: bool, - ) -> IndexerResult { - let cursor = if let Some(cursor) = cursor { + ) -> IndexerResult> { + use diesel_async::RunQueryDsl; + + let mut connection = self.pool.get().await?; + + let mut query = events::table.into_boxed(); + + if let Some(cursor) = cursor { if cursor.tx_digest != tx_digest { return Err(IndexerError::InvalidArgumentError( "Cursor tx_digest does not match the tx_digest in the query.".into(), )); } if descending_order { - format!("e.{EVENT_SEQUENCE_NUMBER_STR} < {}", cursor.event_seq) + query = query.filter(events::event_sequence_number.lt(cursor.event_seq as i64)); } else { - format!("e.{EVENT_SEQUENCE_NUMBER_STR} > {}", cursor.event_seq) + query = query.filter(events::event_sequence_number.gt(cursor.event_seq as i64)); } } else if descending_order { - format!("e.{EVENT_SEQUENCE_NUMBER_STR} <= {}", i64::MAX) + query = query.filter(events::event_sequence_number.le(i64::MAX)); } else { - format!("e.{EVENT_SEQUENCE_NUMBER_STR} >= {}", 0) + query = query.filter(events::event_sequence_number.ge(0)); }; - let order_clause = if descending_order { "DESC" } else { "ASC" }; - Ok(format!( - "SELECT * \ - FROM EVENTS e \ - JOIN TRANSACTIONS t \ - ON t.tx_sequence_number = e.tx_sequence_number \ - AND t.transaction_digest = '\\x{}'::bytea \ - WHERE {cursor} \ - ORDER BY e.{EVENT_SEQUENCE_NUMBER_STR} {order_clause} \ - LIMIT {limit} - ", - Hex::encode(tx_digest.into_inner()), - )) + if descending_order { + query = query.order(events::event_sequence_number.desc()); + } else { + query = query.order(events::event_sequence_number.asc()); + } + + // If the cursor is provided and matches tx_digest, we've already fetched the + // tx_sequence_number and can query events table directly. Otherwise, we can just consult + // the tx_digests table for the tx_sequence_number to key into events table. + if cursor.is_some() { + query = query.filter(events::tx_sequence_number.eq(cursor_tx_seq)); + } else { + query = query.filter( + events::tx_sequence_number.nullable().eq(tx_digests::table + .select(tx_digests::tx_sequence_number) + .filter(tx_digests::tx_digest.eq(tx_digest.into_inner().to_vec())) + .single_value()), + ); + } + + let stored_events = query + .limit(limit as i64) + .load::(&mut connection) + .await?; + + let mut sui_event_futures = vec![]; + for stored_event in stored_events { + sui_event_futures.push(tokio::task::spawn( + stored_event.try_into_sui_event(self.package_resolver.clone()), + )); + } + + let sui_events = futures::future::join_all(sui_event_futures) + .await + .into_iter() + .collect::, _>>() + .tap_err(|e| error!("Failed to join sui event futures: {}", e))? + .into_iter() + .collect::, _>>() + .tap_err(|e| error!("Failed to collect sui event futures: {}", e))?; + Ok(sui_events) } pub async fn query_events( @@ -980,17 +964,19 @@ impl IndexerReader { } = cursor; let tx_seq = transactions::table .select(transactions::tx_sequence_number) - .filter(transactions::transaction_digest.eq(tx_digest.into_inner().to_vec())) + .filter( + transactions::tx_sequence_number + .nullable() + .eq(tx_digests::table + .select(tx_digests::tx_sequence_number) + .filter(tx_digests::tx_digest.eq(tx_digest.into_inner().to_vec())) + .single_value()), + ) .first::(&mut connection) .await?; - (tx_seq, event_seq) + (tx_seq, event_seq as i64) } else if descending_order { - let max_tx_seq = events::table - .select(events::tx_sequence_number) - .order(events::tx_sequence_number.desc()) - .first::(&mut connection) - .await?; - (max_tx_seq + 1, 0) + (i64::MAX, i64::MAX) } else { (-1, 0) }; @@ -1010,11 +996,10 @@ impl IndexerReader { format!( "( \ SELECT * - FROM tx_senders s + FROM event_senders s JOIN events e - ON e.tx_sequence_number = s.tx_sequence_number - AND s.sender = '\\x{}'::bytea - WHERE {} \ + USING (tx_sequence_number, event_sequence_number) + WHERE s.sender = '\\x{}'::bytea AND {} \ ORDER BY {} \ LIMIT {} )", @@ -1024,11 +1009,14 @@ impl IndexerReader { limit, ) } else if let EventFilter::Transaction(tx_digest) = filter { - self.query_events_by_tx_digest_query(tx_digest, cursor, limit, descending_order)? + return self + .query_events_by_tx_digest(tx_digest, cursor, tx_seq, limit, descending_order) + .await; } else { let main_where_clause = match filter { - EventFilter::Package(package_id) => { - format!("package = '\\x{}'::bytea", package_id.to_hex()) + EventFilter::All([]) => { + // No filter + "1 = 1".to_string() } EventFilter::MoveModule { package, module } => { format!( @@ -1052,14 +1040,9 @@ impl IndexerReader { // Processed above unreachable!() } - EventFilter::MoveEventField { .. } - | EventFilter::All(_) - | EventFilter::Any(_) - | EventFilter::And(_, _) - | EventFilter::Or(_, _) - | EventFilter::TimeRange { .. } => { + EventFilter::TimeRange { .. } | EventFilter::Any(_) => { return Err(IndexerError::NotSupportedError( - "This type of EventFilter is not supported.".into(), + "This type of EventFilter is not supported.".to_owned(), )); } }; @@ -1185,49 +1168,57 @@ impl IndexerReader { )); } }; - let struct_tag: StructTag = move_object.type_().clone().into(); - let move_type_layout = self + let type_tag: TypeTag = move_object.type_().clone().into(); + let layout = self .package_resolver - .type_layout(TypeTag::Struct(Box::new(struct_tag.clone()))) + .type_layout(type_tag.clone()) .await .map_err(|e| { IndexerError::ResolveMoveStructError(format!( - "Failed to get type layout for type {}: {}", - struct_tag, e + "Failed to get type layout for type {}: {e}", + type_tag.to_canonical_display(/* with_prefix */ true), )) })?; - let MoveTypeLayout::Struct(move_struct_layout) = move_type_layout else { - return Err(IndexerError::ResolveMoveStructError( - "MoveTypeLayout is not Struct".to_string(), - )); - }; - let move_struct = move_object.to_move_struct(&move_struct_layout)?; - let (move_value, type_, object_id) = - DynamicFieldInfo::parse_move_object(&move_struct).tap_err(|e| warn!("{e}"))?; - let name_type = move_object.type_().try_extract_field_name(&type_)?; - let bcs_name = bcs::to_bytes(&move_value.clone().undecorate()).map_err(|e| { - IndexerError::SerdeError(format!( - "Failed to serialize dynamic field name {:?}: {e}", - move_value - )) - })?; + let field = DFV::FieldVisitor::deserialize(move_object.contents(), &layout) + .tap_err(|e| warn!("{e}"))?; + + let type_ = field.kind; + let name_type: TypeTag = field.name_layout.into(); + let bcs_name = field.name_bytes.to_owned(); + + let name_value = BoundedVisitor::deserialize_value(field.name_bytes, field.name_layout) + .tap_err(|e| warn!("{e}"))?; + let name = DynamicFieldName { type_: name_type, - value: SuiMoveValue::from(move_value).to_json_value(), + value: SuiMoveValue::from(name_value).to_json_value(), }; - Ok(Some(match type_ { - DynamicFieldType::DynamicObject => { - let object = self.get_object(&object_id, None).await?.ok_or( - IndexerError::UncategorizedError(anyhow::anyhow!( - "Failed to find object_id {:?} when trying to create dynamic field info", - object_id - )), - )?; - - let version = object.version(); - let digest = object.digest(); + let value_metadata = field.value_metadata().map_err(|e| { + warn!("{e}"); + IndexerError::UncategorizedError(anyhow!(e)) + })?; + + Ok(Some(match value_metadata { + DFV::ValueMetadata::DynamicField(object_type) => DynamicFieldInfo { + name, + bcs_name, + type_, + object_type: object_type.to_canonical_string(/* with_prefix */ true), + object_id: object.id(), + version: object.version(), + digest: object.digest(), + }, + + DFV::ValueMetadata::DynamicObjectField(object_id) => { + let object = self.get_object(&object_id, None).await?.ok_or_else(|| { + IndexerError::UncategorizedError(anyhow!( + "Failed to find object_id {} when trying to create dynamic field info", + object_id.to_canonical_display(/* with_prefix */ true), + )) + })?; + let object_type = object.data.type_().unwrap().clone(); DynamicFieldInfo { name, @@ -1235,20 +1226,10 @@ impl IndexerReader { type_, object_type: object_type.to_canonical_string(/* with_prefix */ true), object_id, - version, - digest, + version: object.version(), + digest: object.digest(), } } - DynamicFieldType::DynamicField => DynamicFieldInfo { - name, - bcs_name, - type_, - object_type: move_object.into_type().into_type_params()[1] - .to_canonical_string(/* with_prefix */ true), - object_id: object.id(), - version: object.version(), - digest: object.digest(), - }, })) } diff --git a/crates/sui-indexer/src/lib.rs b/crates/sui-indexer/src/lib.rs index aed8ca26ad4fc..e759370c72798 100644 --- a/crates/sui-indexer/src/lib.rs +++ b/crates/sui-indexer/src/lib.rs @@ -26,6 +26,7 @@ use crate::indexer_reader::IndexerReader; use errors::IndexerError; pub mod apis; +pub mod backfill; pub mod config; pub mod database; pub mod db; @@ -37,7 +38,6 @@ pub mod metrics; pub mod models; pub mod restorer; pub mod schema; -pub mod sql_backfill; pub mod store; pub mod system_package_task; pub mod tempdb; @@ -48,6 +48,7 @@ pub async fn build_json_rpc_server( prometheus_registry: &Registry, reader: IndexerReader, config: &JsonRpcConfig, + cancel: CancellationToken, ) -> Result { let mut builder = JsonRpcServerBuilder::new(env!("CARGO_PKG_VERSION"), prometheus_registry, None, None); @@ -65,7 +66,6 @@ pub async fn build_json_rpc_server( builder.register_module(CoinReadApi::new(reader.clone()))?; builder.register_module(ExtendedApi::new(reader.clone()))?; - let cancel = CancellationToken::new(); let system_package_task = SystemPackageTask::new(reader.clone(), cancel.clone(), Duration::from_secs(10)); diff --git a/crates/sui-indexer/src/main.rs b/crates/sui-indexer/src/main.rs index de554fe56cb1a..8978d072d8dea 100644 --- a/crates/sui-indexer/src/main.rs +++ b/crates/sui-indexer/src/main.rs @@ -1,19 +1,21 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use clap::Parser; -use tokio_util::sync::CancellationToken; -use tracing::warn; +use clap::Parser; +use sui_indexer::backfill::backfill_runner::BackfillRunner; use sui_indexer::config::{Command, UploadOptions}; use sui_indexer::database::ConnectionPool; -use sui_indexer::db::{check_db_migration_consistency, reset_database, run_migrations}; +use sui_indexer::db::{ + check_db_migration_consistency, check_prunable_tables_valid, reset_database, run_migrations, +}; use sui_indexer::indexer::Indexer; use sui_indexer::metrics::{ spawn_connection_pool_metric_collector, start_prometheus_server, IndexerMetrics, }; use sui_indexer::restorer::formal_snapshot::IndexerFormalSnapshotRestorer; -use sui_indexer::sql_backfill::run_sql_backfill; use sui_indexer::store::PgIndexerStore; +use tokio_util::sync::CancellationToken; +use tracing::warn; #[tokio::main] async fn main() -> anyhow::Result<()> { @@ -45,14 +47,19 @@ async fn main() -> anyhow::Result<()> { } => { // Make sure to run all migrations on startup, and also serve as a compatibility check. run_migrations(pool.dedicated_connection().await?).await?; + let retention_config = pruning_options.load_from_file(); + if retention_config.is_some() { + check_prunable_tables_valid(&mut pool.get().await?).await?; + } + let store = PgIndexerStore::new(pool, upload_options, indexer_metrics.clone()); - Indexer::start_writer_with_config( + Indexer::start_writer( &ingestion_config, store, indexer_metrics, snapshot_config, - pruning_options, + retention_config, CancellationToken::new(), ) .await?; @@ -60,7 +67,8 @@ async fn main() -> anyhow::Result<()> { Command::JsonRpcService(json_rpc_config) => { check_db_migration_consistency(&mut pool.get().await?).await?; - Indexer::start_reader(&json_rpc_config, ®istry, pool).await?; + Indexer::start_reader(&json_rpc_config, ®istry, pool, CancellationToken::new()) + .await?; } Command::ResetDatabase { force } => { if !force { @@ -74,29 +82,18 @@ async fn main() -> anyhow::Result<()> { Command::RunMigrations => { run_migrations(pool.dedicated_connection().await?).await?; } - Command::SqlBackFill { - sql, - checkpoint_column_name, - first_checkpoint, - last_checkpoint, + Command::RunBackFill { + start, + end, + runner_kind, backfill_config, } => { - run_sql_backfill( - &sql, - &checkpoint_column_name, - first_checkpoint, - last_checkpoint, - pool, - backfill_config, - ) - .await; + let total_range = start..=end; + BackfillRunner::run(runner_kind, pool, backfill_config, total_range).await; } Command::Restore(restore_config) => { - let upload_options = UploadOptions { - gcs_display_bucket: Some(restore_config.gcs_display_bucket.clone()), - gcs_cred_path: Some(restore_config.gcs_snapshot_bucket.clone()), - }; - let store = PgIndexerStore::new(pool, upload_options, indexer_metrics.clone()); + let store = + PgIndexerStore::new(pool, UploadOptions::default(), indexer_metrics.clone()); let mut formal_restorer = IndexerFormalSnapshotRestorer::new(store, restore_config).await?; formal_restorer.restore().await?; diff --git a/crates/sui-indexer/src/metrics.rs b/crates/sui-indexer/src/metrics.rs index 739a686d3cbfc..0b1f8c1e5bed5 100644 --- a/crates/sui-indexer/src/metrics.rs +++ b/crates/sui-indexer/src/metrics.rs @@ -110,10 +110,12 @@ pub struct IndexerMetrics { pub checkpoint_db_commit_latency_transactions_chunks_transformation: Histogram, pub checkpoint_db_commit_latency_objects: Histogram, pub checkpoint_db_commit_latency_objects_snapshot: Histogram, + pub checkpoint_db_commit_latency_objects_version: Histogram, pub checkpoint_db_commit_latency_objects_history: Histogram, pub checkpoint_db_commit_latency_full_objects_history: Histogram, pub checkpoint_db_commit_latency_objects_chunks: Histogram, pub checkpoint_db_commit_latency_objects_snapshot_chunks: Histogram, + pub checkpoint_db_commit_latency_objects_version_chunks: Histogram, pub checkpoint_db_commit_latency_objects_history_chunks: Histogram, pub checkpoint_db_commit_latency_full_objects_history_chunks: Histogram, pub checkpoint_db_commit_latency_events: Histogram, @@ -125,6 +127,7 @@ pub struct IndexerMetrics { pub checkpoint_db_commit_latency_tx_indices_chunks: Histogram, pub checkpoint_db_commit_latency_checkpoints: Histogram, pub checkpoint_db_commit_latency_epoch: Histogram, + pub checkpoint_db_commit_latency_watermarks: Histogram, pub thousand_transaction_avg_db_commit_latency: Histogram, pub object_db_commit_latency: Histogram, pub object_mutation_db_commit_latency: Histogram, @@ -434,6 +437,12 @@ impl IndexerMetrics { registry, ) .unwrap(), + checkpoint_db_commit_latency_objects_version: register_histogram_with_registry!( + "checkpoint_db_commit_latency_objects_version", + "Time spent committing objects version", + DATA_INGESTION_LATENCY_SEC_BUCKETS.to_vec(), + registry, + ).unwrap(), checkpoint_db_commit_latency_objects_history: register_histogram_with_registry!( "checkpoint_db_commit_latency_objects_history", "Time spent committing objects history", @@ -460,6 +469,12 @@ impl IndexerMetrics { registry, ) .unwrap(), + checkpoint_db_commit_latency_objects_version_chunks: register_histogram_with_registry!( + "checkpoint_db_commit_latency_objects_version_chunks", + "Time spent committing objects version chunks", + DATA_INGESTION_LATENCY_SEC_BUCKETS.to_vec(), + registry, + ).unwrap(), checkpoint_db_commit_latency_objects_history_chunks: register_histogram_with_registry!( "checkpoint_db_commit_latency_objects_history_chunks", "Time spent committing objects history chunks", @@ -536,6 +551,13 @@ impl IndexerMetrics { registry, ) .unwrap(), + checkpoint_db_commit_latency_watermarks: register_histogram_with_registry!( + "checkpoint_db_commit_latency_watermarks", + "Time spent committing watermarks", + DATA_INGESTION_LATENCY_SEC_BUCKETS.to_vec(), + registry, + ) + .unwrap(), tokio_blocking_task_wait_latency: register_histogram_with_registry!( "tokio_blocking_task_wait_latency", "Time spent to wait for tokio blocking task pool", diff --git a/crates/sui-indexer/src/models/epoch.rs b/crates/sui-indexer/src/models/epoch.rs index ea2f336638306..0918e50c72c35 100644 --- a/crates/sui-indexer/src/models/epoch.rs +++ b/crates/sui-indexer/src/models/epoch.rs @@ -2,10 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 use crate::schema::epochs; -use crate::types::IndexedEpochInfo; use crate::{errors::IndexerError, schema::feature_flags, schema::protocol_configs}; +use diesel::prelude::{AsChangeset, Identifiable}; use diesel::{Insertable, Queryable, Selectable}; use sui_json_rpc_types::{EndOfEpochInfo, EpochInfo}; +use sui_types::event::SystemEpochInfoEvent; +use sui_types::messages_checkpoint::CertifiedCheckpointSummary; use sui_types::sui_system_state::sui_system_state_summary::SuiSystemStateSummary; #[derive(Queryable, Insertable, Debug, Clone, Default)] @@ -18,7 +20,7 @@ pub struct StoredEpochInfo { pub protocol_version: i64, pub total_stake: i64, pub storage_fund_balance: i64, - pub system_state: Vec, + pub system_state: Option>, pub epoch_total_transactions: Option, pub last_checkpoint_id: Option, pub epoch_end_timestamp: Option, @@ -30,7 +32,43 @@ pub struct StoredEpochInfo { pub total_stake_rewards_distributed: Option, pub leftover_storage_fund_inflow: Option, pub epoch_commitments: Option>, + /// This is the system state summary at the beginning of the epoch, serialized as JSON. pub system_state_summary_json: Option, + /// First transaction sequence number of this epoch. + pub first_tx_sequence_number: Option, +} + +#[derive(Insertable, Identifiable, AsChangeset, Clone, Debug)] +#[diesel(primary_key(epoch))] +#[diesel(table_name = epochs)] +pub struct StartOfEpochUpdate { + pub epoch: i64, + pub first_checkpoint_id: i64, + pub first_tx_sequence_number: i64, + pub epoch_start_timestamp: i64, + pub reference_gas_price: i64, + pub protocol_version: i64, + pub total_stake: i64, + pub storage_fund_balance: i64, + pub system_state_summary_json: serde_json::Value, +} + +#[derive(Identifiable, AsChangeset, Clone, Debug)] +#[diesel(primary_key(epoch))] +#[diesel(table_name = epochs)] +pub struct EndOfEpochUpdate { + pub epoch: i64, + pub epoch_total_transactions: i64, + pub last_checkpoint_id: i64, + pub epoch_end_timestamp: i64, + pub storage_fund_reinvestment: i64, + pub storage_charge: i64, + pub storage_rebate: i64, + pub stake_subsidy_amount: i64, + pub total_gas_fees: i64, + pub total_stake_rewards_distributed: i64, + pub leftover_storage_fund_inflow: i64, + pub epoch_commitments: Vec, } #[derive(Queryable, Insertable, Debug, Clone, Default)] @@ -60,6 +98,7 @@ pub struct QueryableEpochInfo { pub total_stake: i64, pub storage_fund_balance: i64, pub epoch_total_transactions: Option, + pub first_tx_sequence_number: Option, pub last_checkpoint_id: Option, pub epoch_end_timestamp: Option, pub storage_fund_reinvestment: Option, @@ -78,59 +117,81 @@ pub struct QueryableEpochSystemState { pub system_state: Vec, } -impl StoredEpochInfo { - pub fn from_epoch_beginning_info(e: &IndexedEpochInfo) -> Self { +impl StartOfEpochUpdate { + pub fn new( + new_system_state_summary: SuiSystemStateSummary, + first_checkpoint_id: u64, + first_tx_sequence_number: u64, + event: Option<&SystemEpochInfoEvent>, + ) -> Self { Self { - epoch: e.epoch as i64, - system_state_summary_json: Some( - serde_json::to_value(e.system_state_summary.clone()).unwrap(), - ), - first_checkpoint_id: e.first_checkpoint_id as i64, - epoch_start_timestamp: e.epoch_start_timestamp as i64, - reference_gas_price: e.reference_gas_price as i64, - protocol_version: e.protocol_version as i64, - total_stake: e.total_stake as i64, - storage_fund_balance: e.storage_fund_balance as i64, - ..Default::default() + epoch: new_system_state_summary.epoch as i64, + system_state_summary_json: serde_json::to_value(new_system_state_summary.clone()) + .unwrap(), + first_checkpoint_id: first_checkpoint_id as i64, + first_tx_sequence_number: first_tx_sequence_number as i64, + epoch_start_timestamp: new_system_state_summary.epoch_start_timestamp_ms as i64, + reference_gas_price: new_system_state_summary.reference_gas_price as i64, + protocol_version: new_system_state_summary.protocol_version as i64, + // NOTE: total_stake and storage_fund_balance are about new epoch, + // although the event is generated at the end of the previous epoch, + // the event is optional b/c no such event for the first epoch. + total_stake: event.map(|e| e.total_stake as i64).unwrap_or(0), + storage_fund_balance: event.map(|e| e.storage_fund_balance as i64).unwrap_or(0), } } +} - pub fn from_epoch_end_info(e: &IndexedEpochInfo) -> Self { +impl EndOfEpochUpdate { + pub fn new( + last_checkpoint_summary: &CertifiedCheckpointSummary, + event: &SystemEpochInfoEvent, + first_tx_sequence_number: u64, + ) -> Self { Self { - epoch: e.epoch as i64, - // TODO: Deprecate this. - system_state: bcs::to_bytes(&e.system_state_summary.clone()).unwrap(), - // At epoch end the system state would be the state of the next epoch, so we ignore it. - system_state_summary_json: None, - epoch_total_transactions: e.epoch_total_transactions.map(|v| v as i64), - last_checkpoint_id: e.last_checkpoint_id.map(|v| v as i64), - epoch_end_timestamp: e.epoch_end_timestamp.map(|v| v as i64), - storage_fund_reinvestment: e.storage_fund_reinvestment.map(|v| v as i64), - storage_charge: e.storage_charge.map(|v| v as i64), - storage_rebate: e.storage_rebate.map(|v| v as i64), - stake_subsidy_amount: e.stake_subsidy_amount.map(|v| v as i64), - total_gas_fees: e.total_gas_fees.map(|v| v as i64), - total_stake_rewards_distributed: e.total_stake_rewards_distributed.map(|v| v as i64), - leftover_storage_fund_inflow: e.leftover_storage_fund_inflow.map(|v| v as i64), - epoch_commitments: e - .epoch_commitments - .as_ref() - .map(|v| bcs::to_bytes(&v).unwrap()), - - // For the following fields: - // we don't update these columns when persisting EndOfEpoch data. - // However if the data is partial, diesel would interpret them - // as Null and hence cause errors. - first_checkpoint_id: 0, - epoch_start_timestamp: 0, - reference_gas_price: 0, - protocol_version: 0, - total_stake: 0, - storage_fund_balance: 0, + epoch: last_checkpoint_summary.epoch as i64, + epoch_total_transactions: (last_checkpoint_summary.network_total_transactions + - first_tx_sequence_number) as i64, + last_checkpoint_id: *last_checkpoint_summary.sequence_number() as i64, + epoch_end_timestamp: last_checkpoint_summary.timestamp_ms as i64, + storage_fund_reinvestment: event.storage_fund_reinvestment as i64, + storage_charge: event.storage_charge as i64, + storage_rebate: event.storage_rebate as i64, + leftover_storage_fund_inflow: event.leftover_storage_fund_inflow as i64, + stake_subsidy_amount: event.stake_subsidy_amount as i64, + total_gas_fees: event.total_gas_fees as i64, + total_stake_rewards_distributed: event.total_stake_rewards_distributed as i64, + epoch_commitments: bcs::to_bytes( + &last_checkpoint_summary + .end_of_epoch_data + .clone() + .unwrap() + .epoch_commitments, + ) + .unwrap(), } } } +impl StoredEpochInfo { + pub fn get_json_system_state_summary(&self) -> Result { + let Some(system_state_summary_json) = self.system_state_summary_json.clone() else { + return Err(IndexerError::PersistentStorageDataCorruptionError( + "System state summary is null for the given epoch".into(), + )); + }; + let system_state_summary: SuiSystemStateSummary = + serde_json::from_value(system_state_summary_json).map_err(|_| { + IndexerError::PersistentStorageDataCorruptionError(format!( + "Failed to deserialize `system_state` for epoch {:?}", + self.epoch, + )) + })?; + debug_assert_eq!(system_state_summary.epoch, self.epoch as u64); + Ok(system_state_summary) + } +} + impl From<&StoredEpochInfo> for Option { fn from(info: &StoredEpochInfo) -> Option { Some(EndOfEpochInfo { @@ -157,25 +218,11 @@ impl TryFrom for EpochInfo { type Error = IndexerError; fn try_from(value: StoredEpochInfo) -> Result { - let epoch = value.epoch as u64; let end_of_epoch_info = (&value).into(); - // FIXME: We should not swallow the error here. - // However currently there is a bug in the system_state column where it's always - // off-by-one, and hence there will be a period of time when - // this conversion will fail. - // We should fix this once we replace it with the new json column. - let system_state: Option = bcs::from_bytes(&value.system_state) - .map_err(|_| { - IndexerError::PersistentStorageDataCorruptionError(format!( - "Failed to deserialize `system_state` for epoch {epoch}", - )) - }) - .ok(); + let system_state_summary = value.get_json_system_state_summary()?; Ok(EpochInfo { epoch: value.epoch as u64, - validators: system_state - .map(|s| s.active_validators) - .unwrap_or_default(), + validators: system_state_summary.active_validators, epoch_total_transactions: value.epoch_total_transactions.unwrap_or(0) as u64, first_checkpoint_id: value.first_checkpoint_id as u64, epoch_start_timestamp: value.epoch_start_timestamp as u64, diff --git a/crates/sui-indexer/src/models/events.rs b/crates/sui-indexer/src/models/events.rs index 210aeaf0462f4..6b9c5044c3ba5 100644 --- a/crates/sui-indexer/src/models/events.rs +++ b/crates/sui-indexer/src/models/events.rs @@ -22,32 +22,16 @@ use crate::types::IndexedEvent; #[derive(Queryable, QueryableByName, Selectable, Insertable, Debug, Clone)] #[diesel(table_name = events)] pub struct StoredEvent { - #[diesel(sql_type = diesel::sql_types::BigInt)] pub tx_sequence_number: i64, - - #[diesel(sql_type = diesel::sql_types::BigInt)] pub event_sequence_number: i64, - - #[diesel(sql_type = diesel::sql_types::Binary)] pub transaction_digest: Vec, - - #[diesel(sql_type = diesel::sql_types::Array>)] pub senders: Vec>>, - - #[diesel(sql_type = diesel::sql_types::Binary)] pub package: Vec, - - #[diesel(sql_type = diesel::sql_types::Text)] pub module: String, - - #[diesel(sql_type = diesel::sql_types::Text)] pub event_type: String, - - #[diesel(sql_type = diesel::sql_types::BigInt)] pub timestamp_ms: i64, - - #[diesel(sql_type = diesel::sql_types::Binary)] pub bcs: Vec, + pub sender: Option>, } pub type SendersType = Vec>>; @@ -58,16 +42,13 @@ impl From for StoredEvent { tx_sequence_number: event.tx_sequence_number as i64, event_sequence_number: event.event_sequence_number as i64, transaction_digest: event.transaction_digest.into_inner().to_vec(), - senders: event - .senders - .into_iter() - .map(|sender| Some(sender.to_vec())) - .collect(), + senders: vec![Some(event.sender.to_vec())], package: event.package.to_vec(), module: event.module.clone(), event_type: event.event_type.clone(), bcs: event.bcs.clone(), timestamp_ms: event.timestamp_ms as i64, + sender: Some(event.sender.to_vec()), } } } diff --git a/crates/sui-indexer/src/models/mod.rs b/crates/sui-indexer/src/models/mod.rs index b677e09f1aaad..84e8b308bc0d5 100644 --- a/crates/sui-indexer/src/models/mod.rs +++ b/crates/sui-indexer/src/models/mod.rs @@ -9,5 +9,7 @@ pub mod events; pub mod obj_indices; pub mod objects; pub mod packages; +pub mod raw_checkpoints; pub mod transactions; pub mod tx_indices; +pub mod watermarks; diff --git a/crates/sui-indexer/src/models/objects.rs b/crates/sui-indexer/src/models/objects.rs index c14a69780ad76..321aaebe2d4ed 100644 --- a/crates/sui-indexer/src/models/objects.rs +++ b/crates/sui-indexer/src/models/objects.rs @@ -47,7 +47,6 @@ pub struct StoredObject { pub object_id: Vec, pub object_version: i64, pub object_digest: Vec, - pub checkpoint_sequence_number: i64, pub owner_type: i16, pub owner_id: Option>, /// The full type of this object, including package id, module, name and type parameters. @@ -62,9 +61,61 @@ pub struct StoredObject { // TODO deal with overflow pub coin_balance: Option, pub df_kind: Option, - pub df_name: Option>, - pub df_object_type: Option, - pub df_object_id: Option>, +} + +impl From for StoredObject { + fn from(o: IndexedObject) -> Self { + let IndexedObject { + checkpoint_sequence_number: _, + object, + df_kind, + } = o; + let (owner_type, owner_id) = owner_to_owner_info(&object.owner); + let coin_type = object + .coin_type_maybe() + .map(|t| t.to_canonical_string(/* with_prefix */ true)); + let coin_balance = if coin_type.is_some() { + Some(object.get_coin_value_unsafe()) + } else { + None + }; + Self { + object_id: object.id().to_vec(), + object_version: object.version().value() as i64, + object_digest: object.digest().into_inner().to_vec(), + owner_type: owner_type as i16, + owner_id: owner_id.map(|id| id.to_vec()), + object_type: object + .type_() + .map(|t| t.to_canonical_string(/* with_prefix */ true)), + object_type_package: object.type_().map(|t| t.address().to_vec()), + object_type_module: object.type_().map(|t| t.module().to_string()), + object_type_name: object.type_().map(|t| t.name().to_string()), + serialized_object: bcs::to_bytes(&object).unwrap(), + coin_type, + coin_balance: coin_balance.map(|b| b as i64), + df_kind: df_kind.map(|k| match k { + DynamicFieldType::DynamicField => 0, + DynamicFieldType::DynamicObject => 1, + }), + } + } +} + +#[derive(Queryable, Insertable, Debug, Identifiable, Clone, QueryableByName)] +#[diesel(table_name = objects, primary_key(object_id))] +pub struct StoredDeletedObject { + pub object_id: Vec, + pub object_version: i64, +} + +impl From for StoredDeletedObject { + fn from(o: IndexedDeletedObject) -> Self { + Self { + object_id: o.object_id.to_vec(), + object_version: o.object_version as i64, + } + } } #[derive(Queryable, Insertable, Selectable, Debug, Identifiable, Clone, QueryableByName)] @@ -85,44 +136,58 @@ pub struct StoredObjectSnapshot { pub coin_type: Option, pub coin_balance: Option, pub df_kind: Option, - pub df_name: Option>, - pub df_object_type: Option, - pub df_object_id: Option>, } -impl From for StoredObjectSnapshot { - fn from(o: StoredObject) -> Self { +impl From for StoredObjectSnapshot { + fn from(o: IndexedObject) -> Self { + let IndexedObject { + checkpoint_sequence_number, + object, + df_kind, + } = o; + let (owner_type, owner_id) = owner_to_owner_info(&object.owner); + let coin_type = object + .coin_type_maybe() + .map(|t| t.to_canonical_string(/* with_prefix */ true)); + let coin_balance = if coin_type.is_some() { + Some(object.get_coin_value_unsafe()) + } else { + None + }; + Self { - object_id: o.object_id, - object_version: o.object_version, + object_id: object.id().to_vec(), + object_version: object.version().value() as i64, object_status: ObjectStatus::Active as i16, - object_digest: Some(o.object_digest), - checkpoint_sequence_number: o.checkpoint_sequence_number, - owner_type: Some(o.owner_type), - object_type_package: o.object_type_package, - object_type_module: o.object_type_module, - object_type_name: o.object_type_name, - owner_id: o.owner_id, - object_type: o.object_type, - serialized_object: Some(o.serialized_object), - coin_type: o.coin_type, - coin_balance: o.coin_balance, - df_kind: o.df_kind, - df_name: o.df_name, - df_object_type: o.df_object_type, - df_object_id: o.df_object_id, + object_digest: Some(object.digest().into_inner().to_vec()), + checkpoint_sequence_number: checkpoint_sequence_number as i64, + owner_type: Some(owner_type as i16), + owner_id: owner_id.map(|id| id.to_vec()), + object_type: object + .type_() + .map(|t| t.to_canonical_string(/* with_prefix */ true)), + object_type_package: object.type_().map(|t| t.address().to_vec()), + object_type_module: object.type_().map(|t| t.module().to_string()), + object_type_name: object.type_().map(|t| t.name().to_string()), + serialized_object: Some(bcs::to_bytes(&object).unwrap()), + coin_type, + coin_balance: coin_balance.map(|b| b as i64), + df_kind: df_kind.map(|k| match k { + DynamicFieldType::DynamicField => 0, + DynamicFieldType::DynamicObject => 1, + }), } } } -impl From for StoredObjectSnapshot { - fn from(o: StoredDeletedObject) -> Self { +impl From for StoredObjectSnapshot { + fn from(o: IndexedDeletedObject) -> Self { Self { - object_id: o.object_id, - object_version: o.object_version, + object_id: o.object_id.to_vec(), + object_version: o.object_version as i64, object_status: ObjectStatus::WrappedOrDeleted as i16, object_digest: None, - checkpoint_sequence_number: o.checkpoint_sequence_number, + checkpoint_sequence_number: o.checkpoint_sequence_number as i64, owner_type: None, owner_id: None, object_type: None, @@ -133,9 +198,6 @@ impl From for StoredObjectSnapshot { coin_type: None, coin_balance: None, df_kind: None, - df_name: None, - df_object_type: None, - df_object_id: None, } } } @@ -158,80 +220,14 @@ pub struct StoredHistoryObject { pub coin_type: Option, pub coin_balance: Option, pub df_kind: Option, - pub df_name: Option>, - pub df_object_type: Option, - pub df_object_id: Option>, -} - -impl From for StoredHistoryObject { - fn from(o: StoredObject) -> Self { - Self { - object_id: o.object_id, - object_version: o.object_version, - object_status: ObjectStatus::Active as i16, - object_digest: Some(o.object_digest), - checkpoint_sequence_number: o.checkpoint_sequence_number, - owner_type: Some(o.owner_type), - object_type_package: o.object_type_package, - object_type_module: o.object_type_module, - object_type_name: o.object_type_name, - owner_id: o.owner_id, - object_type: o.object_type, - serialized_object: Some(o.serialized_object), - coin_type: o.coin_type, - coin_balance: o.coin_balance, - df_kind: o.df_kind, - df_name: o.df_name, - df_object_type: o.df_object_type, - df_object_id: o.df_object_id, - } - } } -#[derive(Queryable, Insertable, Debug, Identifiable, Clone, QueryableByName)] -#[diesel(table_name = objects, primary_key(object_id))] -pub struct StoredDeletedObject { - pub object_id: Vec, - pub object_version: i64, - pub checkpoint_sequence_number: i64, -} - -impl From for StoredDeletedObject { - fn from(o: IndexedDeletedObject) -> Self { - Self { - object_id: o.object_id.to_vec(), - object_version: o.object_version as i64, - checkpoint_sequence_number: o.checkpoint_sequence_number as i64, - } - } -} - -#[derive(Queryable, Insertable, Debug, Identifiable, Clone, QueryableByName)] -#[diesel(table_name = objects_history, primary_key(object_id, object_version, checkpoint_sequence_number))] -pub(crate) struct StoredDeletedHistoryObject { - pub object_id: Vec, - pub object_version: i64, - pub object_status: i16, - pub checkpoint_sequence_number: i64, -} - -impl From for StoredDeletedHistoryObject { - fn from(o: StoredDeletedObject) -> Self { - Self { - object_id: o.object_id, - object_version: o.object_version, - object_status: ObjectStatus::WrappedOrDeleted as i16, - checkpoint_sequence_number: o.checkpoint_sequence_number, - } - } -} - -impl From for StoredObject { +impl From for StoredHistoryObject { fn from(o: IndexedObject) -> Self { let IndexedObject { checkpoint_sequence_number, object, - df_info, + df_kind, } = o; let (owner_type, owner_id) = owner_to_owner_info(&object.owner); let coin_type = object @@ -242,12 +238,14 @@ impl From for StoredObject { } else { None }; + Self { object_id: object.id().to_vec(), object_version: object.version().value() as i64, - object_digest: object.digest().into_inner().to_vec(), + object_status: ObjectStatus::Active as i16, + object_digest: Some(object.digest().into_inner().to_vec()), checkpoint_sequence_number: checkpoint_sequence_number as i64, - owner_type: owner_type as i16, + owner_type: Some(owner_type as i16), owner_id: owner_id.map(|id| id.to_vec()), object_type: object .type_() @@ -255,16 +253,35 @@ impl From for StoredObject { object_type_package: object.type_().map(|t| t.address().to_vec()), object_type_module: object.type_().map(|t| t.module().to_string()), object_type_name: object.type_().map(|t| t.name().to_string()), - serialized_object: bcs::to_bytes(&object).unwrap(), + serialized_object: Some(bcs::to_bytes(&object).unwrap()), coin_type, coin_balance: coin_balance.map(|b| b as i64), - df_kind: df_info.as_ref().map(|k| match k.type_ { + df_kind: df_kind.map(|k| match k { DynamicFieldType::DynamicField => 0, DynamicFieldType::DynamicObject => 1, }), - df_name: df_info.as_ref().map(|n| bcs::to_bytes(&n.name).unwrap()), - df_object_type: df_info.as_ref().map(|v| v.object_type.clone()), - df_object_id: df_info.as_ref().map(|v| v.object_id.to_vec()), + } + } +} + +impl From for StoredHistoryObject { + fn from(o: IndexedDeletedObject) -> Self { + Self { + object_id: o.object_id.to_vec(), + object_version: o.object_version as i64, + object_status: ObjectStatus::WrappedOrDeleted as i16, + object_digest: None, + checkpoint_sequence_number: o.checkpoint_sequence_number as i64, + owner_type: None, + owner_id: None, + object_type: None, + object_type_package: None, + object_type_module: None, + object_type_name: None, + serialized_object: None, + coin_type: None, + coin_balance: None, + df_kind: None, } } } diff --git a/crates/sui-indexer/src/models/raw_checkpoints.rs b/crates/sui-indexer/src/models/raw_checkpoints.rs new file mode 100644 index 0000000000000..98fafba928705 --- /dev/null +++ b/crates/sui-indexer/src/models/raw_checkpoints.rs @@ -0,0 +1,26 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::schema::raw_checkpoints; +use crate::types::IndexedCheckpoint; +use diesel::prelude::*; + +#[derive(Queryable, Insertable, Selectable, Debug, Clone, Default)] +#[diesel(table_name = raw_checkpoints)] +pub struct StoredRawCheckpoint { + pub sequence_number: i64, + /// BCS serialized CertifiedCheckpointSummary + pub certified_checkpoint: Vec, + /// BCS serialized CheckpointContents + pub checkpoint_contents: Vec, +} + +impl From<&IndexedCheckpoint> for StoredRawCheckpoint { + fn from(c: &IndexedCheckpoint) -> Self { + Self { + sequence_number: c.sequence_number as i64, + certified_checkpoint: bcs::to_bytes(c.certified_checkpoint.as_ref().unwrap()).unwrap(), + checkpoint_contents: bcs::to_bytes(c.checkpoint_contents.as_ref().unwrap()).unwrap(), + } + } +} diff --git a/crates/sui-indexer/src/models/tx_indices.rs b/crates/sui-indexer/src/models/tx_indices.rs index 89e37c49af88d..a00b715eedf98 100644 --- a/crates/sui-indexer/src/models/tx_indices.rs +++ b/crates/sui-indexer/src/models/tx_indices.rs @@ -4,7 +4,7 @@ use crate::{ schema::{ tx_affected_addresses, tx_affected_objects, tx_calls_fun, tx_calls_mod, tx_calls_pkg, - tx_changed_objects, tx_digests, tx_input_objects, tx_kinds, tx_recipients, tx_senders, + tx_changed_objects, tx_digests, tx_input_objects, tx_kinds, }, types::TxIndex, }; @@ -39,21 +39,6 @@ pub struct StoredTxAffectedObjects { pub sender: Vec, } -#[derive(Queryable, Insertable, Selectable, Debug, Clone, Default)] -#[diesel(table_name = tx_senders)] -pub struct StoredTxSenders { - pub tx_sequence_number: i64, - pub sender: Vec, -} - -#[derive(Queryable, Insertable, Selectable, Debug, Clone, Default)] -#[diesel(table_name = tx_recipients)] -pub struct StoredTxRecipients { - pub tx_sequence_number: i64, - pub recipient: Vec, - pub sender: Vec, -} - #[derive(Queryable, Insertable, Selectable, Debug, Clone, Default)] #[diesel(table_name = tx_input_objects)] pub struct StoredTxInputObject { @@ -118,8 +103,6 @@ impl TxIndex { ) -> ( Vec, Vec, - Vec, - Vec, Vec, Vec, Vec, @@ -144,10 +127,8 @@ impl TxIndex { .collect(); let tx_affected_objects = self - .input_objects + .affected_objects .iter() - .chain(self.changed_objects.iter()) - .unique() .map(|o| StoredTxAffectedObjects { tx_sequence_number, affected: o.to_vec(), @@ -155,21 +136,6 @@ impl TxIndex { }) .collect(); - let tx_sender = StoredTxSenders { - tx_sequence_number, - sender: self.sender.to_vec(), - }; - - let tx_recipients = self - .recipients - .iter() - .map(|s| StoredTxRecipients { - tx_sequence_number, - recipient: s.to_vec(), - sender: self.sender.to_vec(), - }) - .collect(); - let tx_input_objects = self .input_objects .iter() @@ -247,8 +213,6 @@ impl TxIndex { ( tx_affected_addresses, tx_affected_objects, - vec![tx_sender], - tx_recipients, tx_input_objects, tx_changed_objects, tx_pkgs, diff --git a/crates/sui-indexer/src/models/watermarks.rs b/crates/sui-indexer/src/models/watermarks.rs new file mode 100644 index 0000000000000..1ff3d3cfe52ac --- /dev/null +++ b/crates/sui-indexer/src/models/watermarks.rs @@ -0,0 +1,76 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::str::FromStr; + +use diesel::prelude::*; + +use crate::{ + handlers::{pruner::PrunableTable, CommitterWatermark}, + schema::watermarks::{self}, +}; + +/// Represents a row in the `watermarks` table. +#[derive(Queryable, Insertable, Default, QueryableByName, Clone)] +#[diesel(table_name = watermarks, primary_key(entity))] +pub struct StoredWatermark { + /// The table governed by this watermark, i.e `epochs`, `checkpoints`, `transactions`. + pub pipeline: String, + /// Inclusive upper epoch bound for this entity's data. Committer updates this field. Pruner uses + /// this to determine if pruning is necessary based on the retention policy. + pub epoch_hi_inclusive: i64, + /// Inclusive upper checkpoint bound for this entity's data. Committer updates this field. All + /// data of this entity in the checkpoint must be persisted before advancing this watermark. The + /// committer refers to this on disaster recovery to resume writing. + pub checkpoint_hi_inclusive: i64, + /// Exclusive upper transaction sequence number bound for this entity's data. Committer updates + /// this field. + pub tx_hi: i64, + /// Inclusive lower epoch bound for this entity's data. Pruner updates this field when the epoch range exceeds the retention policy. + pub epoch_lo: i64, + /// Inclusive low watermark that the pruner advances. Corresponds to the epoch id, checkpoint + /// sequence number, or tx sequence number depending on the entity. Data before this watermark is + /// considered pruned by a reader. The underlying data may still exist in the db instance. + pub reader_lo: i64, + /// Updated using the database's current timestamp when the pruner sees that some data needs to + /// be dropped. The pruner uses this column to determine whether to prune or wait long enough + /// that all in-flight reads complete or timeout before it acts on an updated watermark. + pub timestamp_ms: i64, + /// Column used by the pruner to track its true progress. Data below this watermark can be + /// immediately pruned. + pub pruner_hi: i64, +} + +impl StoredWatermark { + pub fn from_upper_bound_update(entity: &str, watermark: CommitterWatermark) -> Self { + StoredWatermark { + pipeline: entity.to_string(), + epoch_hi_inclusive: watermark.epoch_hi_inclusive as i64, + checkpoint_hi_inclusive: watermark.checkpoint_hi_inclusive as i64, + tx_hi: watermark.tx_hi as i64, + ..StoredWatermark::default() + } + } + + pub fn from_lower_bound_update(entity: &str, epoch_lo: u64, reader_lo: u64) -> Self { + StoredWatermark { + pipeline: entity.to_string(), + epoch_lo: epoch_lo as i64, + reader_lo: reader_lo as i64, + ..StoredWatermark::default() + } + } + + pub fn entity(&self) -> Option { + PrunableTable::from_str(&self.pipeline).ok() + } + + /// Determine whether to set a new epoch lower bound based on the retention policy. + pub fn new_epoch_lo(&self, retention: u64) -> Option { + if self.epoch_lo as u64 + retention <= self.epoch_hi_inclusive as u64 { + Some((self.epoch_hi_inclusive as u64).saturating_sub(retention - 1)) + } else { + None + } + } +} diff --git a/crates/sui-indexer/src/restorer/archives.rs b/crates/sui-indexer/src/restorer/archives.rs index 1e1fbd5f678d4..f70336f76d3c5 100644 --- a/crates/sui-indexer/src/restorer/archives.rs +++ b/crates/sui-indexer/src/restorer/archives.rs @@ -21,14 +21,12 @@ pub struct RestoreCheckpointInfo { } pub async fn read_restore_checkpoint_info( - cred_path: String, archive_bucket: Option, epoch: u64, ) -> IndexerResult { let archive_store_config = ObjectStoreConfig { object_store: Some(ObjectStoreType::GCS), bucket: archive_bucket, - google_service_account: Some(cred_path.clone()), object_store_connection_limit: 50, no_sign_request: false, ..Default::default() diff --git a/crates/sui-indexer/src/restorer/formal_snapshot.rs b/crates/sui-indexer/src/restorer/formal_snapshot.rs index 6b6696b9d434f..bab43c7303f38 100644 --- a/crates/sui-indexer/src/restorer/formal_snapshot.rs +++ b/crates/sui-indexer/src/restorer/formal_snapshot.rs @@ -46,14 +46,14 @@ impl IndexerFormalSnapshotRestorer { ) -> Result { let remote_store_config = ObjectStoreConfig { object_store: Some(ObjectStoreType::S3), - aws_endpoint: Some(restore_config.s3_endpoint.clone()), + aws_endpoint: Some(restore_config.snapshot_endpoint.clone()), aws_virtual_hosted_style_request: true, object_store_connection_limit: restore_config.object_store_concurrent_limit, no_sign_request: true, ..Default::default() }; - let base_path = PathBuf::from(restore_config.gcs_snapshot_dir.clone()); + let base_path = PathBuf::from(restore_config.snapshot_download_dir.clone()); let snapshot_dir = base_path.join("snapshot"); if snapshot_dir.exists() { fs::remove_dir_all(snapshot_dir.clone()).unwrap(); @@ -179,7 +179,7 @@ impl IndexerFormalSnapshotRestorer { match object { LiveObject::Normal(obj) => { // TODO: placeholder values for df_info and checkpoint_seq_num, - // will clean it up when the columne cleanup is done. + // will clean it up when the column cleanup is done. let indexed_object = IndexedObject::from_object(0, obj, None); move_objects.push(indexed_object); @@ -238,14 +238,12 @@ impl IndexerFormalSnapshotRestorer { } async fn restore_display_table(&self) -> std::result::Result<(), anyhow::Error> { - let cred_path = self.restore_config.gcs_cred_path.clone(); let bucket = self.restore_config.gcs_display_bucket.clone(); let start_epoch = self.restore_config.start_epoch; let remote_store_config = ObjectStoreConfig { object_store: Some(ObjectStoreType::GCS), bucket: Some(bucket), - google_service_account: Some(cred_path), object_store_connection_limit: 200, no_sign_request: false, ..Default::default() @@ -261,7 +259,6 @@ impl IndexerFormalSnapshotRestorer { async fn restore_cp_watermark_and_chain_id(&self) -> Result<(), IndexerError> { let restore_checkpoint_info = read_restore_checkpoint_info( - self.restore_config.gcs_cred_path.clone(), Some(self.restore_config.gcs_archive_bucket.clone()), self.restore_config.start_epoch, ) @@ -274,6 +271,8 @@ impl IndexerFormalSnapshotRestorer { .persist_chain_identifier(chain_identifier.into_inner().to_vec()) .await?; assert!(next_checkpoint_after_epoch > 0); + // FIXME: This is a temporary hack to add a checkpoint watermark. + // Once we have proper watermark tables, we should remove the following code. let last_cp = IndexedCheckpoint { sequence_number: next_checkpoint_after_epoch - 1, ..Default::default() diff --git a/crates/sui-indexer/src/schema.rs b/crates/sui-indexer/src/schema.rs index e960d475fcaca..aceb54597c9c5 100644 --- a/crates/sui-indexer/src/schema.rs +++ b/crates/sui-indexer/src/schema.rs @@ -49,7 +49,7 @@ diesel::table! { protocol_version -> Int8, total_stake -> Int8, storage_fund_balance -> Int8, - system_state -> Bytea, + system_state -> Nullable, epoch_total_transactions -> Nullable, last_checkpoint_id -> Nullable, epoch_end_timestamp -> Nullable, @@ -62,6 +62,7 @@ diesel::table! { leftover_storage_fund_inflow -> Nullable, epoch_commitments -> Nullable, system_state_summary_json -> Nullable, + first_tx_sequence_number -> Nullable, } } @@ -144,6 +145,7 @@ diesel::table! { event_type -> Text, timestamp_ms -> Int8, bcs -> Bytea, + sender -> Nullable, } } @@ -168,7 +170,6 @@ diesel::table! { object_id -> Bytea, object_version -> Int8, object_digest -> Bytea, - checkpoint_sequence_number -> Int8, owner_type -> Int2, owner_id -> Nullable, object_type -> Nullable, @@ -179,9 +180,6 @@ diesel::table! { coin_type -> Nullable, coin_balance -> Nullable, df_kind -> Nullable, - df_name -> Nullable, - df_object_type -> Nullable, - df_object_id -> Nullable, } } @@ -202,9 +200,6 @@ diesel::table! { coin_type -> Nullable, coin_balance -> Nullable, df_kind -> Nullable, - df_name -> Nullable, - df_object_type -> Nullable, - df_object_id -> Nullable, } } @@ -225,9 +220,6 @@ diesel::table! { coin_type -> Nullable, coin_balance -> Nullable, df_kind -> Nullable, - df_name -> Nullable, - df_object_type -> Nullable, - df_object_id -> Nullable, } } @@ -265,6 +257,14 @@ diesel::table! { } } +diesel::table! { + raw_checkpoints (sequence_number) { + sequence_number -> Int8, + certified_checkpoint -> Bytea, + checkpoint_contents -> Bytea, + } +} + diesel::table! { transactions (tx_sequence_number) { tx_sequence_number -> Int8, @@ -369,6 +369,19 @@ diesel::table! { } } +diesel::table! { + watermarks (pipeline) { + pipeline -> Text, + epoch_hi_inclusive -> Int8, + checkpoint_hi_inclusive -> Int8, + tx_hi -> Int8, + epoch_lo -> Int8, + reader_lo -> Int8, + timestamp_ms -> Int8, + pruner_hi -> Int8, + } +} + diesel::allow_tables_to_appear_in_same_query!( chain_identifier, checkpoints, @@ -391,6 +404,7 @@ diesel::allow_tables_to_appear_in_same_query!( packages, protocol_configs, pruner_cp_watermark, + raw_checkpoints, transactions, tx_affected_addresses, tx_affected_objects, @@ -403,4 +417,5 @@ diesel::allow_tables_to_appear_in_same_query!( tx_kinds, tx_recipients, tx_senders, + watermarks, ); diff --git a/crates/sui-indexer/src/store/indexer_store.rs b/crates/sui-indexer/src/store/indexer_store.rs index ab463867ab12f..998b37f286b1e 100644 --- a/crates/sui-indexer/src/store/indexer_store.rs +++ b/crates/sui-indexer/src/store/indexer_store.rs @@ -4,18 +4,22 @@ use std::collections::BTreeMap; use async_trait::async_trait; +use strum::IntoEnumIterator; use crate::errors::IndexerError; -use crate::handlers::{EpochToCommit, TransactionObjectChangesToCommit}; +use crate::handlers::pruner::PrunableTable; +use crate::handlers::{CommitterWatermark, EpochToCommit, TransactionObjectChangesToCommit}; use crate::models::display::StoredDisplay; use crate::models::obj_indices::StoredObjectVersion; use crate::models::objects::{StoredDeletedObject, StoredObject}; +use crate::models::raw_checkpoints::StoredRawCheckpoint; +use crate::models::watermarks::StoredWatermark; use crate::types::{ EventIndex, IndexedCheckpoint, IndexedEvent, IndexedPackage, IndexedTransaction, TxIndex, }; #[allow(clippy::large_enum_variant)] -pub enum ObjectChangeToCommit { +pub enum ObjectsToCommit { MutatedObject(StoredObject), DeletedObject(StoredDeletedObject), } @@ -54,7 +58,7 @@ pub trait IndexerStore: Clone + Sync + Send + 'static { object_changes: Vec, ) -> Result<(), IndexerError>; - async fn persist_object_versions( + async fn persist_objects_version( &self, object_versions: Vec, ) -> Result<(), IndexerError>; @@ -94,8 +98,10 @@ pub trait IndexerStore: Clone + Sync + Send + 'static { async fn persist_packages(&self, packages: Vec) -> Result<(), IndexerError>; + /// Updates the current epoch with end-of-epoch data, and writes a new epoch to the database. async fn persist_epoch(&self, epoch: EpochToCommit) -> Result<(), IndexerError>; + /// Updates epoch-partitioned tables to accept data from the new epoch. async fn advance_epoch(&self, epoch: EpochToCommit) -> Result<(), IndexerError>; async fn prune_epoch(&self, epoch: u64) -> Result<(), IndexerError>; @@ -103,8 +109,32 @@ pub trait IndexerStore: Clone + Sync + Send + 'static { async fn get_network_total_transactions_by_end_of_epoch( &self, epoch: u64, - ) -> Result; + ) -> Result, IndexerError>; async fn upload_display(&self, epoch: u64) -> Result<(), IndexerError>; + async fn restore_display(&self, bytes: bytes::Bytes) -> Result<(), IndexerError>; + + async fn persist_raw_checkpoints( + &self, + checkpoints: Vec, + ) -> Result<(), IndexerError>; + + /// Update the upper bound of the watermarks for the given tables. + async fn update_watermarks_upper_bound( + &self, + watermark: CommitterWatermark, + ) -> Result<(), IndexerError> + where + E::Iterator: Iterator>; + + /// Updates each watermark entry's lower bounds per the list of tables and their new epoch lower + /// bounds. + async fn update_watermarks_lower_bound( + &self, + watermarks: Vec<(PrunableTable, u64)>, + ) -> Result<(), IndexerError>; + + /// Load all watermark entries from the store, and the latest timestamp from the db. + async fn get_watermarks(&self) -> Result<(Vec, i64), IndexerError>; } diff --git a/crates/sui-indexer/src/store/mod.rs b/crates/sui-indexer/src/store/mod.rs index 7c1aa9abcc107..9d6bf65cc26b4 100644 --- a/crates/sui-indexer/src/store/mod.rs +++ b/crates/sui-indexer/src/store/mod.rs @@ -52,3 +52,42 @@ where }) .await } + +pub async fn read_with_retry<'a, Q, T>( + pool: &ConnectionPool, + timeout: Duration, + query: Q, +) -> Result +where + Q: for<'r> FnOnce( + &'r mut AsyncPgConnection, + ) -> ScopedBoxFuture<'a, 'r, Result> + + Send, + Q: Clone, + T: 'a, +{ + let backoff = backoff::ExponentialBackoff { + max_elapsed_time: Some(timeout), + ..Default::default() + }; + backoff::future::retry(backoff, || async { + let mut connection = pool.get().await.map_err(|e| backoff::Error::Transient { + err: IndexerError::PostgresWriteError(e.to_string()), + retry_after: None, + })?; + + connection + .build_transaction() + .read_only() + .run(query.clone()) + .await + .map_err(|e| { + tracing::error!("Error with reading data from DB: {:?}, retrying...", e); + backoff::Error::Transient { + err: IndexerError::PostgresWriteError(e.to_string()), + retry_after: None, + } + }) + }) + .await +} diff --git a/crates/sui-indexer/src/store/package_resolver.rs b/crates/sui-indexer/src/store/package_resolver.rs index de03c80df9362..f4cedd6500871 100644 --- a/crates/sui-indexer/src/store/package_resolver.rs +++ b/crates/sui-indexer/src/store/package_resolver.rs @@ -1,11 +1,9 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::sync::{Arc, Mutex}; +use std::sync::Arc; use crate::database::ConnectionPool; -use crate::handlers::tx_processor::IndexingPackageBuffer; -use crate::metrics::IndexerMetrics; use crate::schema::objects; use anyhow::anyhow; use async_trait::async_trait; @@ -14,7 +12,6 @@ use diesel::QueryDsl; use diesel_async::RunQueryDsl; use move_core_types::account_address::AccountAddress; use sui_package_resolver::{error::Error as PackageResolverError, Package, PackageStore}; -use sui_types::base_types::ObjectID; use sui_types::object::Object; /// A package resolver that reads packages from the database. @@ -59,44 +56,3 @@ impl IndexerStorePackageResolver { .map_err(|e| anyhow!("Failed parsing object to package: {e}")) } } - -pub struct InterimPackageResolver { - package_db_resolver: IndexerStorePackageResolver, - package_buffer: Arc>, - metrics: IndexerMetrics, -} - -impl InterimPackageResolver { - pub fn new( - package_db_resolver: IndexerStorePackageResolver, - package_buffer: Arc>, - metrics: IndexerMetrics, - ) -> Self { - Self { - package_db_resolver, - package_buffer, - metrics, - } - } -} - -#[async_trait] -impl PackageStore for InterimPackageResolver { - async fn fetch(&self, addr: AccountAddress) -> Result, PackageResolverError> { - let package_id = ObjectID::from(addr); - let maybe_obj = { - let buffer_guard = self.package_buffer.lock().unwrap(); - buffer_guard.get_package(&package_id) - }; - if let Some(obj) = maybe_obj { - self.metrics.indexing_package_resolver_in_mem_hit.inc(); - let pkg = Package::read_from_object(&obj).map_err(|e| PackageResolverError::Store { - store: "InMemoryPackageBuffer", - error: e.to_string(), - })?; - Ok(Arc::new(pkg)) - } else { - self.package_db_resolver.fetch(addr).await - } - } -} diff --git a/crates/sui-indexer/src/store/pg_indexer_store.rs b/crates/sui-indexer/src/store/pg_indexer_store.rs index 13d6f63c4d946..2f9aaa5d81cb3 100644 --- a/crates/sui-indexer/src/store/pg_indexer_store.rs +++ b/crates/sui-indexer/src/store/pg_indexer_store.rs @@ -1,7 +1,6 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::collections::hash_map::Entry; use std::collections::{BTreeMap, HashMap}; use std::io::Cursor; use std::time::Duration; @@ -14,21 +13,24 @@ use diesel::ExpressionMethods; use diesel::OptionalExtension; use diesel::QueryDsl; use diesel_async::scoped_futures::ScopedFutureExt; +use futures::future::Either; use itertools::Itertools; use object_store::path::Path; +use strum::IntoEnumIterator; +use sui_types::base_types::ObjectID; use tap::TapFallible; use tracing::{info, warn}; use sui_config::object_storage_config::{ObjectStoreConfig, ObjectStoreType}; use sui_protocol_config::ProtocolConfig; use sui_storage::object_store::util::put; -use sui_types::base_types::ObjectID; use crate::config::UploadOptions; use crate::database::ConnectionPool; use crate::errors::{Context, IndexerError}; -use crate::handlers::EpochToCommit; +use crate::handlers::pruner::PrunableTable; use crate::handlers::TransactionObjectChangesToCommit; +use crate::handlers::{CommitterWatermark, EpochToCommit}; use crate::metrics::IndexerMetrics; use crate::models::checkpoints::StoredChainIdentifier; use crate::models::checkpoints::StoredCheckpoint; @@ -38,30 +40,30 @@ use crate::models::epoch::StoredEpochInfo; use crate::models::epoch::{StoredFeatureFlag, StoredProtocolConfig}; use crate::models::events::StoredEvent; use crate::models::obj_indices::StoredObjectVersion; -use crate::models::objects::StoredFullHistoryObject; use crate::models::objects::{ - StoredDeletedHistoryObject, StoredDeletedObject, StoredHistoryObject, StoredObject, + StoredDeletedObject, StoredFullHistoryObject, StoredHistoryObject, StoredObject, StoredObjectSnapshot, }; use crate::models::packages::StoredPackage; use crate::models::transactions::StoredTransaction; +use crate::models::watermarks::StoredWatermark; use crate::schema::{ chain_identifier, checkpoints, display, epochs, event_emit_module, event_emit_package, event_senders, event_struct_instantiation, event_struct_module, event_struct_name, event_struct_package, events, feature_flags, full_objects_history, objects, objects_history, objects_snapshot, objects_version, packages, protocol_configs, pruner_cp_watermark, - transactions, tx_affected_addresses, tx_affected_objects, tx_calls_fun, tx_calls_mod, - tx_calls_pkg, tx_changed_objects, tx_digests, tx_input_objects, tx_kinds, tx_recipients, - tx_senders, + raw_checkpoints, transactions, tx_affected_addresses, tx_affected_objects, tx_calls_fun, + tx_calls_mod, tx_calls_pkg, tx_changed_objects, tx_digests, tx_input_objects, tx_kinds, + watermarks, }; -use crate::store::transaction_with_retry; -use crate::types::EventIndex; +use crate::store::{read_with_retry, transaction_with_retry}; +use crate::types::{EventIndex, IndexedDeletedObject, IndexedObject}; use crate::types::{IndexedCheckpoint, IndexedEvent, IndexedPackage, IndexedTransaction, TxIndex}; use super::pg_partition_manager::{EpochPartitionData, PgPartitionManager}; use super::IndexerStore; -use super::ObjectChangeToCommit; +use crate::models::raw_checkpoints::StoredRawCheckpoint; use diesel::upsert::excluded; use sui_types::digests::{ChainIdentifier, CheckpointDigest}; @@ -180,7 +182,8 @@ impl PgIndexerStore { .context("Failed reading chain id from PostgresDB") } - async fn get_latest_checkpoint_sequence_number(&self) -> Result, IndexerError> { + // `pub` is needed for wait_for_checkpoint in tests + pub async fn get_latest_checkpoint_sequence_number(&self) -> Result, IndexerError> { use diesel_async::RunQueryDsl; let mut connection = self.pool.get().await?; @@ -249,7 +252,7 @@ impl PgIndexerStore { .context("Failed reading min prunable checkpoint sequence number from PostgresDB") } - async fn get_checkpoint_range_for_epoch( + pub async fn get_checkpoint_range_for_epoch( &self, epoch: u64, ) -> Result<(u64, Option), IndexerError> { @@ -267,7 +270,7 @@ impl PgIndexerStore { .context("Failed reading checkpoint range from PostgresDB") } - async fn get_transaction_range_for_checkpoint( + pub async fn get_transaction_range_for_checkpoint( &self, checkpoint: u64, ) -> Result<(u64, u64), IndexerError> { @@ -288,17 +291,20 @@ impl PgIndexerStore { .context("Failed reading transaction range from PostgresDB") } - async fn get_latest_object_snapshot_checkpoint_sequence_number( + pub async fn get_latest_object_snapshot_checkpoint_sequence_number( &self, ) -> Result, IndexerError> { use diesel_async::RunQueryDsl; let mut connection = self.pool.get().await?; - objects_snapshot::table - .select(max(objects_snapshot::checkpoint_sequence_number)) - .first::>(&mut connection) + watermarks::table + .select(watermarks::checkpoint_hi_inclusive) + .filter(watermarks::pipeline.eq("objects_snapshot")) + .first::(&mut connection) .await + // Handle case where the watermark is not set yet + .optional() .map_err(Into::into) .map(|v| v.map(|v| v as u64)) .context( @@ -355,8 +361,6 @@ impl PgIndexerStore { objects::object_id.eq(excluded(objects::object_id)), objects::object_version.eq(excluded(objects::object_version)), objects::object_digest.eq(excluded(objects::object_digest)), - objects::checkpoint_sequence_number - .eq(excluded(objects::checkpoint_sequence_number)), objects::owner_type.eq(excluded(objects::owner_type)), objects::owner_id.eq(excluded(objects::owner_id)), objects::object_type.eq(excluded(objects::object_type)), @@ -364,9 +368,6 @@ impl PgIndexerStore { objects::coin_type.eq(excluded(objects::coin_type)), objects::coin_balance.eq(excluded(objects::coin_balance)), objects::df_kind.eq(excluded(objects::df_kind)), - objects::df_name.eq(excluded(objects::df_name)), - objects::df_object_type.eq(excluded(objects::df_object_type)), - objects::df_object_id.eq(excluded(objects::df_object_id)), )) .execute(conn) .await?; @@ -422,34 +423,22 @@ impl PgIndexerStore { }) } - async fn persist_objects_snapshot_chunk( + async fn persist_object_snapshot_mutation_chunk( &self, - objects: Vec, + objects_snapshot_mutations: Vec, ) -> Result<(), IndexerError> { use diesel_async::RunQueryDsl; let guard = self .metrics .checkpoint_db_commit_latency_objects_snapshot_chunks .start_timer(); - let mut objects_snapshot: Vec = vec![]; - for object in objects { - match object { - ObjectChangeToCommit::MutatedObject(stored_object) => { - objects_snapshot.push(stored_object.into()); - } - ObjectChangeToCommit::DeletedObject(stored_deleted_object) => { - objects_snapshot.push(stored_deleted_object.into()); - } - } - } - transaction_with_retry(&self.pool, PG_DB_COMMIT_SLEEP_DURATION, |conn| { async { - for objects_snapshot_chunk in - objects_snapshot.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) + for mutation_chunk in + objects_snapshot_mutations.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) { diesel::insert_into(objects_snapshot::table) - .values(objects_snapshot_chunk) + .values(mutation_chunk) .on_conflict(objects_snapshot::object_id) .do_update() .set(( @@ -459,8 +448,6 @@ impl PgIndexerStore { .eq(excluded(objects_snapshot::object_status)), objects_snapshot::object_digest .eq(excluded(objects_snapshot::object_digest)), - objects_snapshot::checkpoint_sequence_number - .eq(excluded(objects_snapshot::checkpoint_sequence_number)), objects_snapshot::owner_type.eq(excluded(objects_snapshot::owner_type)), objects_snapshot::owner_id.eq(excluded(objects_snapshot::owner_id)), objects_snapshot::object_type_package @@ -477,11 +464,8 @@ impl PgIndexerStore { objects_snapshot::coin_balance .eq(excluded(objects_snapshot::coin_balance)), objects_snapshot::df_kind.eq(excluded(objects_snapshot::df_kind)), - objects_snapshot::df_name.eq(excluded(objects_snapshot::df_name)), - objects_snapshot::df_object_type - .eq(excluded(objects_snapshot::df_object_type)), - objects_snapshot::df_object_id - .eq(excluded(objects_snapshot::df_object_id)), + objects_snapshot::checkpoint_sequence_number + .eq(excluded(objects_snapshot::checkpoint_sequence_number)), )) .execute(conn) .await?; @@ -499,48 +483,70 @@ impl PgIndexerStore { }) } - async fn persist_objects_history_chunk( + async fn persist_object_snapshot_deletion_chunk( &self, - objects: Vec, + objects_snapshot_deletions: Vec, ) -> Result<(), IndexerError> { use diesel_async::RunQueryDsl; let guard = self .metrics - .checkpoint_db_commit_latency_objects_history_chunks + .checkpoint_db_commit_latency_objects_snapshot_chunks .start_timer(); - let mut mutated_objects: Vec = vec![]; - let mut deleted_object_ids: Vec = vec![]; - for object in objects { - match object { - ObjectChangeToCommit::MutatedObject(stored_object) => { - mutated_objects.push(stored_object.into()); - } - ObjectChangeToCommit::DeletedObject(stored_deleted_object) => { - deleted_object_ids.push(stored_deleted_object.into()); - } - } - } + transaction_with_retry(&self.pool, PG_DB_COMMIT_SLEEP_DURATION, |conn| { async { - for mutated_object_change_chunk in - mutated_objects.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) + for deletion_chunk in + objects_snapshot_deletions.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) { - let error_message = concat!( - "Failed to write to ", - stringify!((objects_history::table)), - " DB" - ); - diesel::insert_into(objects_history::table) - .values(mutated_object_change_chunk) - .on_conflict_do_nothing() - .execute(conn) - .await - .map_err(IndexerError::from) - .context(error_message)?; + diesel::delete( + objects_snapshot::table.filter( + objects_snapshot::object_id.eq_any( + deletion_chunk + .iter() + .map(|o| o.object_id.clone()) + .collect::>(), + ), + ), + ) + .execute(conn) + .await + .map_err(IndexerError::from) + .context("Failed to write object deletion to PostgresDB")?; } + Ok::<(), IndexerError>(()) + } + .scope_boxed() + }) + .await + .tap_ok(|_| { + let elapsed = guard.stop_and_record(); + info!( + elapsed, + "Deleted {} chunked object snapshots", + objects_snapshot_deletions.len(), + ); + }) + .tap_err(|e| { + tracing::error!( + "Failed to persist object snapshot deletions with error: {}", + e + ); + }) + } - for deleted_objects_chunk in - deleted_object_ids.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) + async fn persist_objects_history_chunk( + &self, + stored_objects_history: Vec, + ) -> Result<(), IndexerError> { + use diesel_async::RunQueryDsl; + let guard = self + .metrics + .checkpoint_db_commit_latency_objects_history_chunks + .start_timer(); + transaction_with_retry(&self.pool, PG_DB_COMMIT_SLEEP_DURATION, |conn| { + async { + for stored_objects_history_chunk in + stored_objects_history.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) { let error_message = concat!( "Failed to write to ", @@ -548,14 +554,13 @@ impl PgIndexerStore { " DB" ); diesel::insert_into(objects_history::table) - .values(deleted_objects_chunk) + .values(stored_objects_history_chunk) .on_conflict_do_nothing() .execute(conn) .await .map_err(IndexerError::from) .context(error_message)?; } - Ok::<(), IndexerError>(()) } .scope_boxed() @@ -610,12 +615,17 @@ impl PgIndexerStore { }) } - async fn persist_object_version_chunk( + async fn persist_objects_version_chunk( &self, object_versions: Vec, ) -> Result<(), IndexerError> { use diesel_async::RunQueryDsl; + let guard = self + .metrics + .checkpoint_db_commit_latency_objects_version_chunks + .start_timer(); + transaction_with_retry(&self.pool, PG_DB_COMMIT_SLEEP_DURATION, |conn| { async { for object_version_chunk in object_versions.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) @@ -633,6 +643,39 @@ impl PgIndexerStore { .scope_boxed() }) .await + .tap_ok(|_| { + let elapsed = guard.stop_and_record(); + info!( + elapsed, + "Persisted {} chunked object versions", + object_versions.len(), + ); + }) + .tap_err(|e| { + tracing::error!("Failed to persist object versions with error: {}", e); + }) + } + + async fn persist_raw_checkpoints_impl( + &self, + raw_checkpoints: &[StoredRawCheckpoint], + ) -> Result<(), IndexerError> { + use diesel_async::RunQueryDsl; + + transaction_with_retry(&self.pool, PG_DB_COMMIT_SLEEP_DURATION, |conn| { + async { + diesel::insert_into(raw_checkpoints::table) + .values(raw_checkpoints) + .on_conflict_do_nothing() + .execute(conn) + .await + .map_err(IndexerError::from) + .context("Failed to write to raw_checkpoints table")?; + Ok::<(), IndexerError>(()) + } + .scope_boxed() + }) + .await } async fn persist_checkpoints( @@ -856,7 +899,10 @@ impl PgIndexerStore { .do_update() .set(( packages::package_id.eq(excluded(packages::package_id)), + packages::package_version.eq(excluded(packages::package_version)), packages::move_package.eq(excluded(packages::move_package)), + packages::checkpoint_sequence_number + .eq(excluded(packages::checkpoint_sequence_number)), )) .execute(conn) .await?; @@ -935,48 +981,73 @@ impl PgIndexerStore { transaction_with_retry(&self.pool, PG_DB_COMMIT_SLEEP_DURATION, |conn| { async { - diesel::insert_into(event_emit_package::table) - .values(&event_emit_packages) - .on_conflict_do_nothing() - .execute(conn) - .await?; - - diesel::insert_into(event_emit_module::table) - .values(&event_emit_modules) - .on_conflict_do_nothing() - .execute(conn) - .await?; + for event_emit_packages_chunk in + event_emit_packages.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) + { + diesel::insert_into(event_emit_package::table) + .values(event_emit_packages_chunk) + .on_conflict_do_nothing() + .execute(conn) + .await?; + } - diesel::insert_into(event_senders::table) - .values(&event_senders) - .on_conflict_do_nothing() - .execute(conn) - .await?; + for event_emit_modules_chunk in + event_emit_modules.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) + { + diesel::insert_into(event_emit_module::table) + .values(event_emit_modules_chunk) + .on_conflict_do_nothing() + .execute(conn) + .await?; + } - diesel::insert_into(event_struct_package::table) - .values(&event_struct_packages) - .on_conflict_do_nothing() - .execute(conn) - .await?; + for event_senders_chunk in event_senders.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) { + diesel::insert_into(event_senders::table) + .values(event_senders_chunk) + .on_conflict_do_nothing() + .execute(conn) + .await?; + } - diesel::insert_into(event_struct_module::table) - .values(&event_struct_modules) - .on_conflict_do_nothing() - .execute(conn) - .await?; + for event_struct_packages_chunk in + event_struct_packages.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) + { + diesel::insert_into(event_struct_package::table) + .values(event_struct_packages_chunk) + .on_conflict_do_nothing() + .execute(conn) + .await?; + } - diesel::insert_into(event_struct_name::table) - .values(&event_struct_names) - .on_conflict_do_nothing() - .execute(conn) - .await?; + for event_struct_modules_chunk in + event_struct_modules.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) + { + diesel::insert_into(event_struct_module::table) + .values(event_struct_modules_chunk) + .on_conflict_do_nothing() + .execute(conn) + .await?; + } - diesel::insert_into(event_struct_instantiation::table) - .values(&event_struct_instantiations) - .on_conflict_do_nothing() - .execute(conn) - .await?; + for event_struct_names_chunk in + event_struct_names.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) + { + diesel::insert_into(event_struct_name::table) + .values(event_struct_names_chunk) + .on_conflict_do_nothing() + .execute(conn) + .await?; + } + for event_struct_instantiations_chunk in + event_struct_instantiations.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) + { + diesel::insert_into(event_struct_instantiation::table) + .values(event_struct_instantiations_chunk) + .on_conflict_do_nothing() + .execute(conn) + .await?; + } Ok(()) } .scope_boxed() @@ -999,8 +1070,6 @@ impl PgIndexerStore { let ( affected_addresses, affected_objects, - senders, - recipients, input_objects, changed_objects, pkgs, @@ -1019,14 +1088,10 @@ impl PgIndexerStore { Vec::new(), Vec::new(), Vec::new(), - Vec::new(), - Vec::new(), ), |( mut tx_affected_addresses, mut tx_affected_objects, - mut tx_senders, - mut tx_recipients, mut tx_input_objects, mut tx_changed_objects, mut tx_pkgs, @@ -1038,20 +1103,16 @@ impl PgIndexerStore { index| { tx_affected_addresses.extend(index.0); tx_affected_objects.extend(index.1); - tx_senders.extend(index.2); - tx_recipients.extend(index.3); - tx_input_objects.extend(index.4); - tx_changed_objects.extend(index.5); - tx_pkgs.extend(index.6); - tx_mods.extend(index.7); - tx_funs.extend(index.8); - tx_digests.extend(index.9); - tx_kinds.extend(index.10); + tx_input_objects.extend(index.2); + tx_changed_objects.extend(index.3); + tx_pkgs.extend(index.4); + tx_mods.extend(index.5); + tx_funs.extend(index.6); + tx_digests.extend(index.7); + tx_kinds.extend(index.8); ( tx_affected_addresses, tx_affected_objects, - tx_senders, - tx_recipients, tx_input_objects, tx_changed_objects, tx_pkgs, @@ -1065,71 +1126,83 @@ impl PgIndexerStore { transaction_with_retry(&self.pool, PG_DB_COMMIT_SLEEP_DURATION, |conn| { async { - diesel::insert_into(tx_affected_addresses::table) - .values(&affected_addresses) - .on_conflict_do_nothing() - .execute(conn) - .await?; - - diesel::insert_into(tx_affected_objects::table) - .values(&affected_objects) - .on_conflict_do_nothing() - .execute(conn) - .await?; - - diesel::insert_into(tx_senders::table) - .values(&senders) - .on_conflict_do_nothing() - .execute(conn) - .await?; + for affected_addresses_chunk in + affected_addresses.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) + { + diesel::insert_into(tx_affected_addresses::table) + .values(affected_addresses_chunk) + .on_conflict_do_nothing() + .execute(conn) + .await?; + } - diesel::insert_into(tx_recipients::table) - .values(&recipients) - .on_conflict_do_nothing() - .execute(conn) - .await?; + for affected_objects_chunk in + affected_objects.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) + { + diesel::insert_into(tx_affected_objects::table) + .values(affected_objects_chunk) + .on_conflict_do_nothing() + .execute(conn) + .await?; + } - diesel::insert_into(tx_input_objects::table) - .values(&input_objects) - .on_conflict_do_nothing() - .execute(conn) - .await?; + for input_objects_chunk in input_objects.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) { + diesel::insert_into(tx_input_objects::table) + .values(input_objects_chunk) + .on_conflict_do_nothing() + .execute(conn) + .await?; + } - diesel::insert_into(tx_changed_objects::table) - .values(&changed_objects) - .on_conflict_do_nothing() - .execute(conn) - .await?; + for changed_objects_chunk in + changed_objects.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) + { + diesel::insert_into(tx_changed_objects::table) + .values(changed_objects_chunk) + .on_conflict_do_nothing() + .execute(conn) + .await?; + } - diesel::insert_into(tx_calls_pkg::table) - .values(&pkgs) - .on_conflict_do_nothing() - .execute(conn) - .await?; + for pkgs_chunk in pkgs.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) { + diesel::insert_into(tx_calls_pkg::table) + .values(pkgs_chunk) + .on_conflict_do_nothing() + .execute(conn) + .await?; + } - diesel::insert_into(tx_calls_mod::table) - .values(&mods) - .on_conflict_do_nothing() - .execute(conn) - .await?; + for mods_chunk in mods.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) { + diesel::insert_into(tx_calls_mod::table) + .values(mods_chunk) + .on_conflict_do_nothing() + .execute(conn) + .await?; + } - diesel::insert_into(tx_calls_fun::table) - .values(&funs) - .on_conflict_do_nothing() - .execute(conn) - .await?; + for funs_chunk in funs.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) { + diesel::insert_into(tx_calls_fun::table) + .values(funs_chunk) + .on_conflict_do_nothing() + .execute(conn) + .await?; + } - diesel::insert_into(tx_digests::table) - .values(&digests) - .on_conflict_do_nothing() - .execute(conn) - .await?; + for digests_chunk in digests.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) { + diesel::insert_into(tx_digests::table) + .values(digests_chunk) + .on_conflict_do_nothing() + .execute(conn) + .await?; + } - diesel::insert_into(tx_kinds::table) - .values(&kinds) - .on_conflict_do_nothing() - .execute(conn) - .await?; + for kinds_chunk in kinds.chunks(PG_COMMIT_CHUNK_SIZE_INTRA_DB_TX) { + diesel::insert_into(tx_kinds::table) + .values(kinds_chunk) + .on_conflict_do_nothing() + .execute(conn) + .await?; + } Ok(()) } @@ -1154,64 +1227,20 @@ impl PgIndexerStore { async { if let Some(last_epoch) = &epoch.last_epoch { let last_epoch_id = last_epoch.epoch; - // Overwrites the `epoch_total_transactions` field on `epoch.last_epoch` because - // we are not guaranteed to have the latest data in db when this is set on - // indexer's chain-reading side. However, when we `persist_epoch`, the - // checkpoints from an epoch ago must have been indexed. - let previous_epoch_network_total_transactions = match epoch_id { - 0 | 1 => 0, - _ => { - let prev_epoch_id = epoch_id - 2; - let result = checkpoints::table - .filter(checkpoints::epoch.eq(prev_epoch_id as i64)) - .select(max(checkpoints::network_total_transactions)) - .first::>(conn) - .await - .map(|o| o.unwrap_or(0))?; - - result as u64 - } - }; - - let epoch_total_transactions = epoch.network_total_transactions - - previous_epoch_network_total_transactions; - let mut last_epoch = StoredEpochInfo::from_epoch_end_info(last_epoch); - last_epoch.epoch_total_transactions = Some(epoch_total_transactions as i64); info!(last_epoch_id, "Persisting epoch end data."); - diesel::insert_into(epochs::table) - .values(vec![last_epoch]) - .on_conflict(epochs::epoch) - .do_update() - .set(( - epochs::system_state.eq(excluded(epochs::system_state)), - epochs::epoch_total_transactions - .eq(excluded(epochs::epoch_total_transactions)), - epochs::last_checkpoint_id.eq(excluded(epochs::last_checkpoint_id)), - epochs::epoch_end_timestamp.eq(excluded(epochs::epoch_end_timestamp)), - epochs::storage_fund_reinvestment - .eq(excluded(epochs::storage_fund_reinvestment)), - epochs::storage_charge.eq(excluded(epochs::storage_charge)), - epochs::storage_rebate.eq(excluded(epochs::storage_rebate)), - epochs::stake_subsidy_amount.eq(excluded(epochs::stake_subsidy_amount)), - epochs::total_gas_fees.eq(excluded(epochs::total_gas_fees)), - epochs::total_stake_rewards_distributed - .eq(excluded(epochs::total_stake_rewards_distributed)), - epochs::leftover_storage_fund_inflow - .eq(excluded(epochs::leftover_storage_fund_inflow)), - epochs::epoch_commitments.eq(excluded(epochs::epoch_commitments)), - )) + diesel::update(epochs::table.filter(epochs::epoch.eq(last_epoch_id))) + .set(last_epoch) .execute(conn) .await?; } let epoch_id = epoch.new_epoch.epoch; info!(epoch_id, "Persisting epoch beginning info"); - let new_epoch = StoredEpochInfo::from_epoch_beginning_info(&epoch.new_epoch); let error_message = concat!("Failed to write to ", stringify!((epochs::table)), " DB"); diesel::insert_into(epochs::table) - .values(new_epoch) + .values(epoch.new_epoch) .on_conflict_do_nothing() .execute(conn) .await @@ -1240,7 +1269,7 @@ impl PgIndexerStore { // partition_0 has been created, so no need to advance it. if let Some(last_epoch_id) = last_epoch_id { let last_db_epoch: Option = epochs::table - .filter(epochs::epoch.eq(last_epoch_id as i64)) + .filter(epochs::epoch.eq(last_epoch_id)) .first::(&mut connection) .await .optional() @@ -1298,22 +1327,6 @@ impl PgIndexerStore { .await } - async fn prune_epochs_table(&self, epoch: u64) -> Result<(), IndexerError> { - use diesel_async::RunQueryDsl; - transaction_with_retry(&self.pool, PG_DB_COMMIT_SLEEP_DURATION, |conn| { - async { - diesel::delete(epochs::table.filter(epochs::epoch.eq(epoch as i64))) - .execute(conn) - .await - .map_err(IndexerError::from) - .context("Failed to prune epochs table")?; - Ok::<(), IndexerError>(()) - } - .scope_boxed() - }) - .await - } - async fn prune_event_indices_table( &self, min_tx: u64, @@ -1399,20 +1412,6 @@ impl PgIndexerStore { .execute(conn) .await?; - diesel::delete( - tx_senders::table - .filter(tx_senders::tx_sequence_number.between(min_tx, max_tx)), - ) - .execute(conn) - .await?; - - diesel::delete( - tx_recipients::table - .filter(tx_recipients::tx_sequence_number.between(min_tx, max_tx)), - ) - .execute(conn) - .await?; - diesel::delete( tx_input_objects::table .filter(tx_input_objects::tx_sequence_number.between(min_tx, max_tx)), @@ -1485,20 +1484,204 @@ impl PgIndexerStore { async fn get_network_total_transactions_by_end_of_epoch( &self, epoch: u64, - ) -> Result { + ) -> Result, IndexerError> { use diesel_async::RunQueryDsl; let mut connection = self.pool.get().await?; - checkpoints::table - .filter(checkpoints::epoch.eq(epoch as i64)) - .select(checkpoints::network_total_transactions) - .order_by(checkpoints::sequence_number.desc()) - .first::(&mut connection) + // TODO: (wlmyng) update to read from epochs::network_total_transactions + + Ok(Some( + checkpoints::table + .filter(checkpoints::epoch.eq(epoch as i64)) + .select(checkpoints::network_total_transactions) + .order_by(checkpoints::sequence_number.desc()) + .first::(&mut connection) + .await + .map_err(Into::into) + .context("Failed to get network total transactions in epoch") + .map(|v| v as u64)?, + )) + } + + async fn update_watermarks_upper_bound( + &self, + watermark: CommitterWatermark, + ) -> Result<(), IndexerError> + where + E::Iterator: Iterator>, + { + use diesel_async::RunQueryDsl; + + let guard = self + .metrics + .checkpoint_db_commit_latency_watermarks + .start_timer(); + + transaction_with_retry(&self.pool, PG_DB_COMMIT_SLEEP_DURATION, |conn| { + let upper_bound_updates = E::iter() + .map(|table| StoredWatermark::from_upper_bound_update(table.as_ref(), watermark)) + .collect::>(); + async { + diesel::insert_into(watermarks::table) + .values(upper_bound_updates) + .on_conflict(watermarks::pipeline) + .do_update() + .set(( + watermarks::epoch_hi_inclusive.eq(excluded(watermarks::epoch_hi_inclusive)), + watermarks::checkpoint_hi_inclusive + .eq(excluded(watermarks::checkpoint_hi_inclusive)), + watermarks::tx_hi.eq(excluded(watermarks::tx_hi)), + )) + .execute(conn) + .await + .map_err(IndexerError::from) + .context("Failed to update watermarks upper bound")?; + + Ok::<(), IndexerError>(()) + } + .scope_boxed() + }) + .await + .tap_ok(|_| { + let elapsed = guard.stop_and_record(); + info!(elapsed, "Persisted watermarks"); + }) + .tap_err(|e| { + tracing::error!("Failed to persist watermarks with error: {}", e); + }) + } + + async fn map_epochs_to_cp_tx( + &self, + epochs: &[u64], + ) -> Result, IndexerError> { + use diesel_async::RunQueryDsl; + + let mut connection = self.pool.get().await?; + + let results: Vec<(i64, i64, Option)> = epochs::table + .filter(epochs::epoch.eq_any(epochs.iter().map(|&e| e as i64))) + .select(( + epochs::epoch, + epochs::first_checkpoint_id, + epochs::first_tx_sequence_number, + )) + .load::<(i64, i64, Option)>(&mut connection) .await .map_err(Into::into) - .context("Failed to get network total transactions in epoch") - .map(|v| v as u64) + .context("Failed to fetch first checkpoint and tx seq num for epochs")?; + + Ok(results + .into_iter() + .map(|(epoch, checkpoint, tx)| { + ( + epoch as u64, + (checkpoint as u64, tx.unwrap_or_default() as u64), + ) + }) + .collect()) + } + + async fn update_watermarks_lower_bound( + &self, + watermarks: Vec<(PrunableTable, u64)>, + ) -> Result<(), IndexerError> { + use diesel_async::RunQueryDsl; + + let epochs: Vec = watermarks.iter().map(|(_table, epoch)| *epoch).collect(); + let epoch_mapping = self.map_epochs_to_cp_tx(&epochs).await?; + let lookups: Result, IndexerError> = watermarks + .into_iter() + .map(|(table, epoch)| { + let (checkpoint, tx) = epoch_mapping.get(&epoch).ok_or_else(|| { + IndexerError::PersistentStorageDataCorruptionError(format!( + "Epoch {} not found in epoch mapping", + epoch + )) + })?; + + Ok(StoredWatermark::from_lower_bound_update( + table.as_ref(), + epoch, + table.select_reader_lo(*checkpoint, *tx), + )) + }) + .collect(); + let lower_bound_updates = lookups?; + + let guard = self + .metrics + .checkpoint_db_commit_latency_watermarks + .start_timer(); + + transaction_with_retry(&self.pool, PG_DB_COMMIT_SLEEP_DURATION, |conn| { + async { + use diesel::dsl::sql; + use diesel::query_dsl::methods::FilterDsl; + + diesel::insert_into(watermarks::table) + .values(lower_bound_updates) + .on_conflict(watermarks::pipeline) + .do_update() + .set(( + watermarks::reader_lo.eq(excluded(watermarks::reader_lo)), + watermarks::epoch_lo.eq(excluded(watermarks::epoch_lo)), + watermarks::timestamp_ms.eq(sql::( + "(EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) * 1000)::bigint", + )), + )) + .filter(excluded(watermarks::reader_lo).gt(watermarks::reader_lo)) + .filter(excluded(watermarks::epoch_lo).gt(watermarks::epoch_lo)) + .filter( + diesel::dsl::sql::( + "(EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) * 1000)::bigint", + ) + .gt(watermarks::timestamp_ms), + ) + .execute(conn) + .await?; + + Ok::<(), IndexerError>(()) + } + .scope_boxed() + }) + .await + .tap_ok(|_| { + let elapsed = guard.stop_and_record(); + info!(elapsed, "Persisted watermarks"); + }) + .tap_err(|e| { + tracing::error!("Failed to persist watermarks with error: {}", e); + }) + } + + async fn get_watermarks(&self) -> Result<(Vec, i64), IndexerError> { + use diesel_async::RunQueryDsl; + + // read_only transaction, otherwise this will block and get blocked by write transactions to + // the same table. + read_with_retry(&self.pool, PG_DB_COMMIT_SLEEP_DURATION, |conn| { + async { + let stored = watermarks::table + .load::(conn) + .await + .map_err(Into::into) + .context("Failed reading watermarks from PostgresDB")?; + + let timestamp = diesel::select(diesel::dsl::sql::( + "(EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) * 1000)::bigint", + )) + .get_result(conn) + .await + .map_err(Into::into) + .context("Failed reading current timestamp from PostgresDB")?; + + Ok((stored, timestamp)) + } + .scope_boxed() + }) + .await } } @@ -1538,21 +1721,15 @@ impl IndexerStore for PgIndexerStore { .metrics .checkpoint_db_commit_latency_objects .start_timer(); - let objects = make_final_list_of_objects_to_commit(object_changes); - let len = objects.len(); - - let mut object_mutations = vec![]; - let mut object_deletions = vec![]; - for object in objects { - match object { - ObjectChangeToCommit::MutatedObject(mutation) => { - object_mutations.push(mutation); - } - ObjectChangeToCommit::DeletedObject(deletion) => { - object_deletions.push(deletion); - } - } - } + let (indexed_mutations, indexed_deletions) = retain_latest_indexed_objects(object_changes); + let object_mutations = indexed_mutations + .into_iter() + .map(StoredObject::from) + .collect::>(); + let object_deletions = indexed_deletions + .into_iter() + .map(StoredDeletedObject::from) + .collect::>(); let mutation_len = object_mutations.len(); let deletion_len = object_deletions.len(); @@ -1563,39 +1740,27 @@ impl IndexerStore for PgIndexerStore { let mutation_futures = object_mutation_chunks .into_iter() .map(|c| self.persist_object_mutation_chunk(c)) - .collect::>(); - futures::future::join_all(mutation_futures) - .await - .into_iter() - .collect::, _>>() - .map_err(|e| { - IndexerError::PostgresWriteError(format!( - "Failed to persist all object mutation chunks: {:?}", - e - )) - })?; + .map(Either::Left); let deletion_futures = object_deletion_chunks .into_iter() .map(|c| self.persist_object_deletion_chunk(c)) - .collect::>(); - futures::future::join_all(deletion_futures) + .map(Either::Right); + let all_futures = mutation_futures.chain(deletion_futures).collect::>(); + + futures::future::join_all(all_futures) .await .into_iter() .collect::, _>>() .map_err(|e| { IndexerError::PostgresWriteError(format!( - "Failed to persist all object deletion chunks: {:?}", + "Failed to persist all object mutation or deletion chunks: {:?}", e )) })?; - let elapsed = guard.stop_and_record(); info!( elapsed, - "Persisted {} objects with {} mutations and {} deletions ", - len, - mutation_len, - deletion_len, + "Persisted {} objects mutations and {} deletions", mutation_len, deletion_len ); Ok(()) } @@ -1611,26 +1776,64 @@ impl IndexerStore for PgIndexerStore { .metrics .checkpoint_db_commit_latency_objects_snapshot .start_timer(); - let objects = make_final_list_of_objects_to_commit(object_changes); - let len = objects.len(); - let chunks = chunk!(objects, self.config.parallel_objects_chunk_size); - let futures = chunks + let (indexed_mutations, indexed_deletions) = retain_latest_indexed_objects(object_changes); + let object_snapshot_mutations: Vec = indexed_mutations + .into_iter() + .map(StoredObjectSnapshot::from) + .collect(); + let object_snapshot_deletions: Vec = indexed_deletions + .into_iter() + .map(StoredObjectSnapshot::from) + .collect(); + let mutation_len = object_snapshot_mutations.len(); + let deletion_len = object_snapshot_deletions.len(); + let object_snapshot_mutation_chunks = chunk!( + object_snapshot_mutations, + self.config.parallel_objects_chunk_size + ); + let object_snapshot_deletion_chunks = chunk!( + object_snapshot_deletions, + self.config.parallel_objects_chunk_size + ); + let mutation_futures = object_snapshot_mutation_chunks .into_iter() - .map(|c| self.persist_objects_snapshot_chunk(c)) + .map(|c| self.persist_object_snapshot_mutation_chunk(c)) + .map(Either::Left) .collect::>(); - - futures::future::join_all(futures) + let deletion_futures = object_snapshot_deletion_chunks + .into_iter() + .map(|c| self.persist_object_snapshot_deletion_chunk(c)) + .map(Either::Right) + .collect::>(); + let all_futures = mutation_futures + .into_iter() + .chain(deletion_futures) + .collect::>(); + futures::future::join_all(all_futures) .await .into_iter() .collect::, _>>() .map_err(|e| { IndexerError::PostgresWriteError(format!( - "Failed to persist all objects snapshot chunks: {:?}", + "Failed to persist object snapshot mutation or deletion chunks: {:?}", e )) + }) + .tap_ok(|_| { + let elapsed = guard.stop_and_record(); + info!( + elapsed, + "Persisted {} objects snapshot mutations and {} deletions", + mutation_len, + deletion_len + ); + }) + .tap_err(|e| { + tracing::error!( + "Failed to persist object snapshot mutation or deletion chunks: {:?}", + e + ) })?; - let elapsed = guard.stop_and_record(); - info!(elapsed, "Persisted {} objects snapshot", len); Ok(()) } @@ -1734,18 +1937,24 @@ impl IndexerStore for PgIndexerStore { Ok(()) } - async fn persist_object_versions( + async fn persist_objects_version( &self, object_versions: Vec, ) -> Result<(), IndexerError> { if object_versions.is_empty() { return Ok(()); } - let object_versions_count = object_versions.len(); + + let guard = self + .metrics + .checkpoint_db_commit_latency_objects_version + .start_timer(); + + let len = object_versions.len(); let chunks = chunk!(object_versions, self.config.parallel_objects_chunk_size); let futures = chunks .into_iter() - .map(|c| self.persist_object_version_chunk(c)) + .map(|c| self.persist_objects_version_chunk(c)) .collect::>(); futures::future::join_all(futures) @@ -1754,11 +1963,13 @@ impl IndexerStore for PgIndexerStore { .collect::, _>>() .map_err(|e| { IndexerError::PostgresWriteError(format!( - "Failed to persist all object version chunks: {:?}", + "Failed to persist all objects version chunks: {:?}", e )) })?; - info!("Persisted {} object versions", object_versions_count); + + let elapsed = guard.stop_and_record(); + info!(elapsed, "Persisted {} object versions", len); Ok(()) } @@ -1872,9 +2083,12 @@ impl IndexerStore for PgIndexerStore { "Failed to persist all event_indices chunks: {:?}", e )) - })?; - let elapsed = guard.stop_and_record(); - info!(elapsed, "Persisted {} event_indices chunks", len); + }) + .tap_ok(|_| { + let elapsed = guard.stop_and_record(); + info!(elapsed, "Persisted {} event_indices chunks", len); + }) + .tap_err(|e| tracing::error!("Failed to persist all event_indices chunks: {:?}", e))?; Ok(()) } @@ -1902,9 +2116,12 @@ impl IndexerStore for PgIndexerStore { "Failed to persist all tx_indices chunks: {:?}", e )) - })?; - let elapsed = guard.stop_and_record(); - info!(elapsed, "Persisted {} tx_indices chunks", len); + }) + .tap_ok(|_| { + let elapsed = guard.stop_and_record(); + info!(elapsed, "Persisted {} tx_indices chunks", len); + }) + .tap_err(|e| tracing::error!("Failed to persist all tx_indices chunks: {:?}", e))?; Ok(()) } @@ -1963,8 +2180,6 @@ impl IndexerStore for PgIndexerStore { self.metrics.last_pruned_checkpoint.set(cp as i64); } - // NOTE: prune epochs table last, otherwise get_checkpoint_range_for_epoch would fail. - self.prune_epochs_table(epoch).await?; Ok(()) } @@ -2040,7 +2255,7 @@ impl IndexerStore for PgIndexerStore { async fn get_network_total_transactions_by_end_of_epoch( &self, epoch: u64, - ) -> Result { + ) -> Result, IndexerError> { self.get_network_total_transactions_by_end_of_epoch(epoch) .await } @@ -2151,73 +2366,133 @@ impl IndexerStore for PgIndexerStore { .await?; Ok(()) } -} -/// Construct deleted objects and mutated objects to commit. -/// In particular, filter mutated objects updates that would -/// be override immediately. -fn make_final_list_of_objects_to_commit( - tx_object_changes: Vec, -) -> Vec { - let deleted_objects = tx_object_changes - .clone() - .into_iter() - .flat_map(|changes| changes.deleted_objects) - .map(|o| (o.object_id, o.into())) - .collect::>(); + async fn persist_raw_checkpoints( + &self, + checkpoints: Vec, + ) -> Result<(), IndexerError> { + self.persist_raw_checkpoints_impl(&checkpoints).await + } - let mutated_objects = tx_object_changes - .into_iter() - .flat_map(|changes| changes.changed_objects); - let mut latest_objects = HashMap::new(); - for object in mutated_objects { - if deleted_objects.contains_key(&object.object.id()) { - continue; - } - match latest_objects.entry(object.object.id()) { - Entry::Vacant(e) => { - e.insert(object); - } - Entry::Occupied(mut e) => { - if object.object.version() > e.get().object.version() { - e.insert(object); - } - } - } + async fn update_watermarks_upper_bound( + &self, + watermark: CommitterWatermark, + ) -> Result<(), IndexerError> + where + E::Iterator: Iterator>, + { + self.update_watermarks_upper_bound::(watermark).await + } + + async fn update_watermarks_lower_bound( + &self, + watermarks: Vec<(PrunableTable, u64)>, + ) -> Result<(), IndexerError> { + self.update_watermarks_lower_bound(watermarks).await + } + + async fn get_watermarks(&self) -> Result<(Vec, i64), IndexerError> { + self.get_watermarks().await } - deleted_objects - .into_values() - .map(ObjectChangeToCommit::DeletedObject) - .chain( - latest_objects - .into_values() - .map(StoredObject::from) - .map(ObjectChangeToCommit::MutatedObject), - ) - .collect() } fn make_objects_history_to_commit( tx_object_changes: Vec, -) -> Vec { - let deleted_objects: Vec = tx_object_changes +) -> Vec { + let deleted_objects: Vec = tx_object_changes .clone() .into_iter() .flat_map(|changes| changes.deleted_objects) .map(|o| o.into()) .collect(); - let mutated_objects: Vec = tx_object_changes + let mutated_objects: Vec = tx_object_changes .into_iter() .flat_map(|changes| changes.changed_objects) .map(|o| o.into()) .collect(); - deleted_objects + deleted_objects.into_iter().chain(mutated_objects).collect() +} + +// Partition object changes into deletions and mutations, +// within partition of mutations or deletions, retain the latest with highest version; +// For overlappings of mutations and deletions, only keep one with higher version. +// This is necessary b/c after this step, DB commit will be done in parallel and not in order. +fn retain_latest_indexed_objects( + tx_object_changes: Vec, +) -> (Vec, Vec) { + // Only the last deleted / mutated object will be in the map, + // b/c tx_object_changes are in order and versions always increment, + let (mutations, deletions) = tx_object_changes .into_iter() - .map(ObjectChangeToCommit::DeletedObject) - .chain( - mutated_objects + .flat_map(|change| { + change + .changed_objects .into_iter() - .map(ObjectChangeToCommit::MutatedObject), - ) - .collect() + .map(Either::Left) + .chain( + change + .deleted_objects + .into_iter() + .map(Either::Right), + ) + }) + .fold( + (HashMap::::new(), HashMap::::new()), + |(mut mutations, mut deletions), either_change| { + match either_change { + // Remove mutation / deletion with a following deletion / mutation, + // b/c following deletion / mutation always has a higher version. + // Technically, assertions below are not required, double check just in case. + Either::Left(mutation) => { + let id = mutation.object.id(); + let mutation_version = mutation.object.version(); + if let Some(existing) = deletions.remove(&id) { + assert!( + existing.object_version < mutation_version.value(), + "Mutation version ({:?}) should be greater than existing deletion version ({:?}) for object {:?}", + mutation_version, + existing.object_version, + id + ); + } + if let Some(existing) = mutations.insert(id, mutation) { + assert!( + existing.object.version() < mutation_version, + "Mutation version ({:?}) should be greater than existing mutation version ({:?}) for object {:?}", + mutation_version, + existing.object.version(), + id + ); + } + } + Either::Right(deletion) => { + let id = deletion.object_id; + let deletion_version = deletion.object_version; + if let Some(existing) = mutations.remove(&id) { + assert!( + existing.object.version().value() < deletion_version, + "Deletion version ({:?}) should be greater than existing mutation version ({:?}) for object {:?}", + deletion_version, + existing.object.version(), + id + ); + } + if let Some(existing) = deletions.insert(id, deletion) { + assert!( + existing.object_version < deletion_version, + "Deletion version ({:?}) should be greater than existing deletion version ({:?}) for object {:?}", + deletion_version, + existing.object_version, + id + ); + } + } + } + (mutations, deletions) + }, + ); + ( + mutations.into_values().collect(), + deletions.into_values().collect(), + ) } diff --git a/crates/sui-indexer/src/store/pg_partition_manager.rs b/crates/sui-indexer/src/store/pg_partition_manager.rs index 2dbc031cc5f07..876a1b9c56146 100644 --- a/crates/sui-indexer/src/store/pg_partition_manager.rs +++ b/crates/sui-indexer/src/store/pg_partition_manager.rs @@ -64,15 +64,11 @@ impl EpochPartitionData { pub fn compose_data(epoch: EpochToCommit, last_db_epoch: StoredEpochInfo) -> Self { let last_epoch = last_db_epoch.epoch as u64; let last_epoch_start_cp = last_db_epoch.first_checkpoint_id as u64; - let next_epoch = epoch.new_epoch.epoch; - let next_epoch_start_cp = epoch.new_epoch.first_checkpoint_id; - - // Determining the tx_sequence_number range for the epoch partition differs from the - // checkpoint_sequence_number range, because the former is a sum of total transactions - - // this sum already addresses the off-by-one. - let next_epoch_start_tx = epoch.network_total_transactions; + let next_epoch = epoch.new_epoch_id(); + let next_epoch_start_cp = epoch.new_epoch_first_checkpoint_id(); + let next_epoch_start_tx = epoch.new_epoch_first_tx_sequence_number(); let last_epoch_start_tx = - next_epoch_start_tx - last_db_epoch.epoch_total_transactions.unwrap() as u64; + next_epoch_start_tx - epoch.last_epoch_total_transactions().unwrap(); Self { last_epoch, diff --git a/crates/sui-indexer/src/test_utils.rs b/crates/sui-indexer/src/test_utils.rs index 98d4d3a04aff9..6a208f8e4c6db 100644 --- a/crates/sui-indexer/src/test_utils.rs +++ b/crates/sui-indexer/src/test_utils.rs @@ -5,81 +5,88 @@ use mysten_metrics::init_metrics; use tokio::task::JoinHandle; use tokio_util::sync::CancellationToken; +use simulacrum::Simulacrum; +use std::net::SocketAddr; use std::path::PathBuf; +use std::sync::Arc; use std::time::Duration; use sui_json_rpc_types::SuiTransactionBlockResponse; -use crate::config::{IngestionConfig, PruningOptions, SnapshotLagConfig, UploadOptions}; +use crate::config::{IngestionConfig, RetentionConfig, SnapshotLagConfig, UploadOptions}; use crate::database::Connection; use crate::database::ConnectionPool; use crate::db::ConnectionPoolConfig; use crate::errors::IndexerError; use crate::indexer::Indexer; use crate::store::PgIndexerStore; +use crate::tempdb::get_available_port; +use crate::tempdb::TempDb; use crate::IndexerMetrics; -pub enum ReaderWriterConfig { - Reader { - reader_mode_rpc_url: String, - }, - Writer { - snapshot_config: SnapshotLagConfig, - pruning_options: PruningOptions, - }, -} +/// Wrapper over `Indexer::start_reader` to make it easier to configure an indexer jsonrpc reader +/// for testing. +pub async fn start_indexer_jsonrpc_for_testing( + db_url: String, + fullnode_url: String, + json_rpc_url: String, + cancel: Option, +) -> (JoinHandle>, CancellationToken) { + let token = cancel.unwrap_or_default(); -impl ReaderWriterConfig { - pub fn reader_mode(reader_mode_rpc_url: String) -> Self { - Self::Reader { - reader_mode_rpc_url, - } - } + // Reduce the connection pool size to 10 for testing + // to prevent maxing out + let pool_config = ConnectionPoolConfig { + pool_size: 5, + connection_timeout: Duration::from_secs(10), + statement_timeout: Duration::from_secs(30), + }; - /// Instantiates a config for indexer in writer mode with the given snapshot config and epochs - /// to keep. - pub fn writer_mode( - snapshot_config: Option, - epochs_to_keep: Option, - ) -> Self { - Self::Writer { - snapshot_config: snapshot_config.unwrap_or_default(), - pruning_options: PruningOptions { epochs_to_keep }, - } - } + println!("db_url: {db_url}"); + println!("pool_config: {pool_config:?}"); + + let registry = prometheus::Registry::default(); + init_metrics(®istry); + + let pool = ConnectionPool::new(db_url.parse().unwrap(), pool_config) + .await + .unwrap(); + + let handle = { + let config = crate::config::JsonRpcConfig { + name_service_options: crate::config::NameServiceOptions::default(), + rpc_address: json_rpc_url.parse().unwrap(), + rpc_client_url: fullnode_url, + }; + let token_clone = token.clone(); + tokio::spawn( + async move { Indexer::start_reader(&config, ®istry, pool, token_clone).await }, + ) + }; + + (handle, token) } -pub async fn start_test_indexer( +/// Wrapper over `Indexer::start_writer_with_config` to make it easier to configure an indexer +/// writer for testing. If the config options are null, default values that have historically worked +/// for testing will be used. +pub async fn start_indexer_writer_for_testing( db_url: String, - rpc_url: String, - reader_writer_config: ReaderWriterConfig, - data_ingestion_path: PathBuf, + snapshot_config: Option, + retention_config: Option, + data_ingestion_path: Option, + cancel: Option, ) -> ( PgIndexerStore, JoinHandle>, CancellationToken, ) { - let token = CancellationToken::new(); - let (store, handle) = start_test_indexer_impl( - db_url, - rpc_url, - reader_writer_config, - Some(data_ingestion_path), - token.clone(), - ) - .await; - (store, handle, token) -} + let token = cancel.unwrap_or_default(); + let snapshot_config = snapshot_config.unwrap_or(SnapshotLagConfig { + snapshot_min_lag: 5, + sleep_duration: 0, + }); -/// Starts an indexer reader or writer for testing depending on the `reader_writer_config`. -pub async fn start_test_indexer_impl( - db_url: String, - rpc_url: String, - reader_writer_config: ReaderWriterConfig, - data_ingestion_path: Option, - cancel: CancellationToken, -) -> (PgIndexerStore, JoinHandle>) { - // Reduce the connection pool size to 10 for testing - // to prevent maxing out + // Reduce the connection pool size to 10 for testing to prevent maxing out let pool_config = ConnectionPoolConfig { pool_size: 5, connection_timeout: Duration::from_secs(10), @@ -91,9 +98,7 @@ pub async fn start_test_indexer_impl( println!("{data_ingestion_path:?}"); let registry = prometheus::Registry::default(); - init_metrics(®istry); - let indexer_metrics = IndexerMetrics::new(®istry); let pool = ConnectionPool::new(db_url.parse().unwrap(), pool_config) @@ -105,45 +110,31 @@ pub async fn start_test_indexer_impl( indexer_metrics.clone(), ); - let handle = match reader_writer_config { - ReaderWriterConfig::Reader { - reader_mode_rpc_url, - } => { - let config = crate::config::JsonRpcConfig { - name_service_options: crate::config::NameServiceOptions::default(), - rpc_address: reader_mode_rpc_url.parse().unwrap(), - rpc_client_url: rpc_url, - }; - tokio::spawn(async move { Indexer::start_reader(&config, ®istry, pool).await }) - } - ReaderWriterConfig::Writer { - snapshot_config, - pruning_options, - } => { - let connection = Connection::dedicated(&db_url.parse().unwrap()) - .await - .unwrap(); - crate::db::reset_database(connection).await.unwrap(); - - let store_clone = store.clone(); - let mut ingestion_config = IngestionConfig::default(); - ingestion_config.sources.data_ingestion_path = data_ingestion_path; - - tokio::spawn(async move { - Indexer::start_writer_with_config( - &ingestion_config, - store_clone, - indexer_metrics, - snapshot_config, - pruning_options, - cancel, - ) - .await - }) - } + let handle = { + let connection = Connection::dedicated(&db_url.parse().unwrap()) + .await + .unwrap(); + crate::db::reset_database(connection).await.unwrap(); + + let store_clone = store.clone(); + let mut ingestion_config = IngestionConfig::default(); + ingestion_config.sources.data_ingestion_path = data_ingestion_path; + let token_clone = token.clone(); + + tokio::spawn(async move { + Indexer::start_writer( + &ingestion_config, + store_clone, + indexer_metrics, + snapshot_config, + retention_config, + token_clone, + ) + .await + }) }; - (store, handle) + (store, handle, token) } #[derive(Clone)] @@ -231,3 +222,77 @@ impl<'a> SuiTransactionBlockResponseBuilder<'a> { } } } + +/// Set up a test indexer fetching from a REST endpoint served by the given Simulacrum. +pub async fn set_up( + sim: Arc, + data_ingestion_path: PathBuf, +) -> ( + JoinHandle<()>, + PgIndexerStore, + JoinHandle>, + TempDb, +) { + let database = TempDb::new().unwrap(); + let server_url: SocketAddr = format!("127.0.0.1:{}", get_available_port()) + .parse() + .unwrap(); + + let server_handle = tokio::spawn(async move { + sui_rest_api::RestService::new_without_version(sim) + .start_service(server_url) + .await; + }); + // Starts indexer + let (pg_store, pg_handle, _) = start_indexer_writer_for_testing( + database.database().url().as_str().to_owned(), + None, + None, + Some(data_ingestion_path), + None, /* cancel */ + ) + .await; + (server_handle, pg_store, pg_handle, database) +} + +/// Wait for the indexer to catch up to the given checkpoint sequence number. +pub async fn wait_for_checkpoint( + pg_store: &PgIndexerStore, + checkpoint_sequence_number: u64, +) -> Result<(), IndexerError> { + tokio::time::timeout(Duration::from_secs(30), async { + while { + let cp_opt = pg_store + .get_latest_checkpoint_sequence_number() + .await + .unwrap(); + cp_opt.is_none() || (cp_opt.unwrap() < checkpoint_sequence_number) + } { + tokio::time::sleep(Duration::from_millis(100)).await; + } + }) + .await + .expect("Timeout waiting for indexer to catchup to checkpoint"); + Ok(()) +} + +/// Wait for the indexer to catch up to the given checkpoint sequence number for objects snapshot. +pub async fn wait_for_objects_snapshot( + pg_store: &PgIndexerStore, + checkpoint_sequence_number: u64, +) -> Result<(), IndexerError> { + tokio::time::timeout(Duration::from_secs(30), async { + while { + let cp_opt = pg_store + .get_latest_object_snapshot_checkpoint_sequence_number() + .await + .unwrap(); + cp_opt.is_none() || (cp_opt.unwrap() < checkpoint_sequence_number) + } { + tokio::time::sleep(Duration::from_millis(100)).await; + } + }) + .await + .expect("Timeout waiting for indexer to catchup to checkpoint for objects snapshot"); + Ok(()) +} diff --git a/crates/sui-indexer/src/types.rs b/crates/sui-indexer/src/types.rs index 9e5cd640d8ab0..6c88e3d27641a 100644 --- a/crates/sui-indexer/src/types.rs +++ b/crates/sui-indexer/src/types.rs @@ -1,8 +1,8 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::errors::IndexerError; use move_core_types::language_storage::StructTag; +use rand::Rng; use serde::{Deserialize, Serialize}; use serde_with::serde_as; use sui_json_rpc_types::{ @@ -12,23 +12,24 @@ use sui_types::base_types::{ObjectDigest, SequenceNumber}; use sui_types::base_types::{ObjectID, SuiAddress}; use sui_types::crypto::AggregateAuthoritySignature; use sui_types::digests::TransactionDigest; -use sui_types::dynamic_field::DynamicFieldInfo; +use sui_types::dynamic_field::DynamicFieldType; use sui_types::effects::TransactionEffects; -use sui_types::event::SystemEpochInfoEvent; use sui_types::messages_checkpoint::{ - CertifiedCheckpointSummary, CheckpointCommitment, CheckpointDigest, CheckpointSequenceNumber, - EndOfEpochData, + CertifiedCheckpointSummary, CheckpointCommitment, CheckpointContents, CheckpointDigest, + CheckpointSequenceNumber, EndOfEpochData, }; use sui_types::move_package::MovePackage; use sui_types::object::{Object, Owner}; use sui_types::sui_serde::SuiStructTag; -use sui_types::sui_system_state::sui_system_state_summary::SuiSystemStateSummary; use sui_types::transaction::SenderSignedData; +use crate::errors::IndexerError; + pub type IndexerResult = Result; #[derive(Debug, Default)] pub struct IndexedCheckpoint { + // TODO: A lot of fields are now redundant with certified_checkpoint and checkpoint_contents. pub sequence_number: u64, pub checkpoint_digest: CheckpointDigest, pub epoch: u64, @@ -48,12 +49,15 @@ pub struct IndexedCheckpoint { pub end_of_epoch: bool, pub min_tx_sequence_number: u64, pub max_tx_sequence_number: u64, + // FIXME: Remove the Default derive and make these fields mandatory. + pub certified_checkpoint: Option, + pub checkpoint_contents: Option, } impl IndexedCheckpoint { pub fn from_sui_checkpoint( - checkpoint: &sui_types::messages_checkpoint::CertifiedCheckpointSummary, - contents: &sui_types::messages_checkpoint::CheckpointContents, + checkpoint: &CertifiedCheckpointSummary, + contents: &CheckpointContents, successful_tx_num: usize, ) -> Self { let total_gas_cost = checkpoint.epoch_rolling_gas_cost_summary.computation_cost as i64 @@ -86,104 +90,8 @@ impl IndexedCheckpoint { checkpoint_commitments: checkpoint.checkpoint_commitments.clone(), min_tx_sequence_number, max_tx_sequence_number, - } - } -} - -/// Represents system state and summary info at the start and end of an epoch. Optional fields are -/// populated at epoch boundary, since they cannot be determined at the start of the epoch. -#[derive(Clone, Debug)] -pub struct IndexedEpochInfo { - pub epoch: u64, - pub first_checkpoint_id: u64, - pub epoch_start_timestamp: u64, - pub reference_gas_price: u64, - pub protocol_version: u64, - pub total_stake: u64, - pub storage_fund_balance: u64, - pub system_state_summary: SuiSystemStateSummary, - pub epoch_total_transactions: Option, - pub last_checkpoint_id: Option, - pub epoch_end_timestamp: Option, - pub storage_fund_reinvestment: Option, - pub storage_charge: Option, - pub storage_rebate: Option, - pub stake_subsidy_amount: Option, - pub total_gas_fees: Option, - pub total_stake_rewards_distributed: Option, - pub leftover_storage_fund_inflow: Option, - pub epoch_commitments: Option>, -} - -impl IndexedEpochInfo { - pub fn from_new_system_state_summary( - new_system_state_summary: SuiSystemStateSummary, - first_checkpoint_id: u64, - event: Option<&SystemEpochInfoEvent>, - ) -> IndexedEpochInfo { - Self { - epoch: new_system_state_summary.epoch, - first_checkpoint_id, - epoch_start_timestamp: new_system_state_summary.epoch_start_timestamp_ms, - reference_gas_price: new_system_state_summary.reference_gas_price, - protocol_version: new_system_state_summary.protocol_version, - // NOTE: total_stake and storage_fund_balance are about new epoch, - // although the event is generated at the end of the previous epoch, - // the event is optional b/c no such event for the first epoch. - total_stake: event.map(|e| e.total_stake).unwrap_or(0), - storage_fund_balance: event.map(|e| e.storage_fund_balance).unwrap_or(0), - system_state_summary: new_system_state_summary, - epoch_total_transactions: None, - last_checkpoint_id: None, - epoch_end_timestamp: None, - storage_fund_reinvestment: None, - storage_charge: None, - storage_rebate: None, - stake_subsidy_amount: None, - total_gas_fees: None, - total_stake_rewards_distributed: None, - leftover_storage_fund_inflow: None, - epoch_commitments: None, - } - } - - /// Creates `IndexedEpochInfo` for epoch X-1 at the boundary of epoch X-1 to X. - /// `network_total_tx_num_at_last_epoch_end` is needed to determine the number of transactions - /// that occurred in the epoch X-1. - pub fn from_end_of_epoch_data( - system_state_summary: SuiSystemStateSummary, - last_checkpoint_summary: &CertifiedCheckpointSummary, - event: &SystemEpochInfoEvent, - network_total_tx_num_at_last_epoch_end: u64, - ) -> IndexedEpochInfo { - Self { - epoch: last_checkpoint_summary.epoch, - epoch_total_transactions: Some( - last_checkpoint_summary.network_total_transactions - - network_total_tx_num_at_last_epoch_end, - ), - last_checkpoint_id: Some(*last_checkpoint_summary.sequence_number()), - epoch_end_timestamp: Some(last_checkpoint_summary.timestamp_ms), - storage_fund_reinvestment: Some(event.storage_fund_reinvestment), - storage_charge: Some(event.storage_charge), - storage_rebate: Some(event.storage_rebate), - leftover_storage_fund_inflow: Some(event.leftover_storage_fund_inflow), - stake_subsidy_amount: Some(event.stake_subsidy_amount), - total_gas_fees: Some(event.total_gas_fees), - total_stake_rewards_distributed: Some(event.total_stake_rewards_distributed), - epoch_commitments: last_checkpoint_summary - .end_of_epoch_data - .as_ref() - .map(|e| e.epoch_commitments.clone()), - system_state_summary, - // The following felds will not and shall not be upserted - // into DB. We have them below to make compiler and diesel happy - first_checkpoint_id: 0, - epoch_start_timestamp: 0, - reference_gas_price: 0, - protocol_version: 0, - total_stake: 0, - storage_fund_balance: 0, + certified_checkpoint: Some(checkpoint.clone()), + checkpoint_contents: Some(contents.clone()), } } } @@ -194,7 +102,7 @@ pub struct IndexedEvent { pub event_sequence_number: u64, pub checkpoint_sequence_number: u64, pub transaction_digest: TransactionDigest, - pub senders: Vec, + pub sender: SuiAddress, pub package: ObjectID, pub module: String, pub event_type: String, @@ -220,7 +128,7 @@ impl IndexedEvent { event_sequence_number, checkpoint_sequence_number, transaction_digest, - senders: vec![event.sender], + sender: event.sender, package: event.package_id, module: event.transaction_module.to_string(), event_type: event.type_.to_canonical_string(/* with_prefix */ true), @@ -248,6 +156,24 @@ pub struct EventIndex { pub type_instantiation: String, } +// for ingestion test +impl EventIndex { + pub fn random() -> Self { + let mut rng = rand::thread_rng(); + EventIndex { + tx_sequence_number: rng.gen(), + event_sequence_number: rng.gen(), + sender: SuiAddress::random_for_testing_only(), + emit_package: ObjectID::random(), + emit_module: rng.gen::().to_string(), + type_package: ObjectID::random(), + type_module: rng.gen::().to_string(), + type_name: rng.gen::().to_string(), + type_instantiation: rng.gen::().to_string(), + } + } +} + impl EventIndex { pub fn from_event( tx_sequence_number: u64, @@ -341,19 +267,38 @@ pub enum DynamicFieldKind { pub struct IndexedObject { pub checkpoint_sequence_number: CheckpointSequenceNumber, pub object: Object, - pub df_info: Option, + pub df_kind: Option, +} + +impl IndexedObject { + pub fn random() -> Self { + let mut rng = rand::thread_rng(); + let random_address = SuiAddress::random_for_testing_only(); + IndexedObject { + checkpoint_sequence_number: rng.gen(), + object: Object::with_owner_for_testing(random_address), + df_kind: { + let random_value = rng.gen_range(0..3); + match random_value { + 0 => Some(DynamicFieldType::DynamicField), + 1 => Some(DynamicFieldType::DynamicObject), + _ => None, + } + }, + } + } } impl IndexedObject { pub fn from_object( checkpoint_sequence_number: CheckpointSequenceNumber, object: Object, - df_info: Option, + df_kind: Option, ) -> Self { Self { checkpoint_sequence_number, object, - df_info, + df_kind, } } } @@ -365,6 +310,17 @@ pub struct IndexedDeletedObject { pub checkpoint_sequence_number: u64, } +impl IndexedDeletedObject { + pub fn random() -> Self { + let mut rng = rand::thread_rng(); + IndexedDeletedObject { + object_id: ObjectID::random(), + object_version: rng.gen(), + checkpoint_sequence_number: rng.gen(), + } + } +} + #[derive(Debug)] pub struct IndexedPackage { pub package_id: ObjectID, @@ -401,12 +357,48 @@ pub struct TxIndex { pub checkpoint_sequence_number: u64, pub input_objects: Vec, pub changed_objects: Vec, + pub affected_objects: Vec, pub payers: Vec, pub sender: SuiAddress, pub recipients: Vec, pub move_calls: Vec<(ObjectID, String, String)>, } +impl TxIndex { + pub fn random() -> Self { + let mut rng = rand::thread_rng(); + TxIndex { + tx_sequence_number: rng.gen(), + tx_kind: if rng.gen_bool(0.5) { + TransactionKind::SystemTransaction + } else { + TransactionKind::ProgrammableTransaction + }, + transaction_digest: TransactionDigest::random(), + checkpoint_sequence_number: rng.gen(), + input_objects: (0..1000).map(|_| ObjectID::random()).collect(), + changed_objects: (0..1000).map(|_| ObjectID::random()).collect(), + affected_objects: (0..1000).map(|_| ObjectID::random()).collect(), + payers: (0..rng.gen_range(0..100)) + .map(|_| SuiAddress::random_for_testing_only()) + .collect(), + sender: SuiAddress::random_for_testing_only(), + recipients: (0..rng.gen_range(0..1000)) + .map(|_| SuiAddress::random_for_testing_only()) + .collect(), + move_calls: (0..rng.gen_range(0..1000)) + .map(|_| { + ( + ObjectID::random(), + rng.gen::().to_string(), + rng.gen::().to_string(), + ) + }) + .collect(), + } + } +} + // ObjectChange is not bcs deserializable, IndexedObjectChange is. #[serde_as] #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] diff --git a/crates/sui-indexer/tests/ingestion_tests.rs b/crates/sui-indexer/tests/ingestion_tests.rs index 1d8004fbce639..f2207d5091783 100644 --- a/crates/sui-indexer/tests/ingestion_tests.rs +++ b/crates/sui-indexer/tests/ingestion_tests.rs @@ -1,79 +1,29 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use std::sync::Arc; use diesel::ExpressionMethods; use diesel::QueryDsl; use diesel_async::RunQueryDsl; use simulacrum::Simulacrum; -use std::net::SocketAddr; -use std::path::PathBuf; -use std::sync::Arc; -use std::time::Duration; use sui_indexer::errors::IndexerError; -use sui_indexer::models::{objects::StoredObject, transactions::StoredTransaction}; -use sui_indexer::schema::{objects, transactions}; -use sui_indexer::store::{indexer_store::IndexerStore, PgIndexerStore}; -use sui_indexer::tempdb::get_available_port; -use sui_indexer::tempdb::TempDb; -use sui_indexer::test_utils::{start_test_indexer, ReaderWriterConfig}; +use sui_indexer::handlers::TransactionObjectChangesToCommit; +use sui_indexer::models::{ + checkpoints::StoredCheckpoint, objects::StoredObject, objects::StoredObjectSnapshot, + transactions::StoredTransaction, +}; +use sui_indexer::schema::{checkpoints, objects, objects_snapshot, transactions}; +use sui_indexer::store::indexer_store::IndexerStore; +use sui_indexer::test_utils::{set_up, wait_for_checkpoint, wait_for_objects_snapshot}; +use sui_indexer::types::EventIndex; +use sui_indexer::types::IndexedDeletedObject; +use sui_indexer::types::IndexedObject; +use sui_indexer::types::TxIndex; use sui_types::base_types::SuiAddress; use sui_types::effects::TransactionEffectsAPI; use sui_types::gas_coin::GasCoin; use sui_types::SUI_FRAMEWORK_PACKAGE_ID; use tempfile::tempdir; -use tokio::task::JoinHandle; - -/// Set up a test indexer fetching from a REST endpoint served by the given Simulacrum. -async fn set_up( - sim: Arc, - data_ingestion_path: PathBuf, -) -> ( - JoinHandle<()>, - PgIndexerStore, - JoinHandle>, - TempDb, -) { - let database = TempDb::new().unwrap(); - let server_url: SocketAddr = format!("127.0.0.1:{}", get_available_port()) - .parse() - .unwrap(); - - let server_handle = tokio::spawn(async move { - sui_rest_api::RestService::new_without_version(sim) - .start_service(server_url) - .await; - }); - // Starts indexer - let (pg_store, pg_handle, _) = start_test_indexer( - database.database().url().as_str().to_owned(), - format!("http://{}", server_url), - ReaderWriterConfig::writer_mode(None, None), - data_ingestion_path, - ) - .await; - (server_handle, pg_store, pg_handle, database) -} - -/// Wait for the indexer to catch up to the given checkpoint sequence number. -async fn wait_for_checkpoint( - pg_store: &PgIndexerStore, - checkpoint_sequence_number: u64, -) -> Result<(), IndexerError> { - tokio::time::timeout(Duration::from_secs(30), async { - while { - let cp_opt = pg_store - .get_latest_checkpoint_sequence_number() - .await - .unwrap(); - cp_opt.is_none() || (cp_opt.unwrap() < checkpoint_sequence_number) - } { - tokio::time::sleep(Duration::from_millis(100)).await; - } - }) - .await - .expect("Timeout waiting for indexer to catchup to checkpoint"); - Ok(()) -} #[tokio::test] pub async fn test_transaction_table() -> Result<(), IndexerError> { @@ -167,3 +117,157 @@ pub async fn test_object_type() -> Result<(), IndexerError> { assert_eq!(db_object.object_type_name, Some("Coin".to_string())); Ok(()) } + +#[tokio::test] +pub async fn test_objects_snapshot() -> Result<(), IndexerError> { + let tempdir = tempdir().unwrap(); + let mut sim = Simulacrum::new(); + let data_ingestion_path = tempdir.path().to_path_buf(); + sim.set_data_ingestion_path(data_ingestion_path.clone()); + + // Run 10 transfer transactions and create 10 checkpoints + let mut last_transaction = None; + let total_checkpoint_sequence_number = 7usize; + for _ in 0..total_checkpoint_sequence_number { + let transfer_recipient = SuiAddress::random_for_testing_only(); + let (transaction, _) = sim.transfer_txn(transfer_recipient); + let (_, err) = sim.execute_transaction(transaction.clone()).unwrap(); + assert!(err.is_none()); + last_transaction = Some(transaction); + let _ = sim.create_checkpoint(); + } + + let (_, pg_store, _, _database) = set_up(Arc::new(sim), data_ingestion_path).await; + + // Wait for objects snapshot at checkpoint max_expected_checkpoint_sequence_number + let max_expected_checkpoint_sequence_number = total_checkpoint_sequence_number - 5; + wait_for_objects_snapshot(&pg_store, max_expected_checkpoint_sequence_number as u64).await?; + + let mut connection = pg_store.pool().dedicated_connection().await.unwrap(); + // Get max checkpoint_sequence_number from objects_snapshot table and assert it's expected + let max_checkpoint_sequence_number = objects_snapshot::table + .select(objects_snapshot::checkpoint_sequence_number) + .order(objects_snapshot::checkpoint_sequence_number.desc()) + .limit(1) + .first::(&mut connection) + .await + .expect("Failed to read max checkpoint_sequence_number from objects_snapshot"); + assert_eq!( + max_checkpoint_sequence_number, + max_expected_checkpoint_sequence_number as i64 + ); + + // Get the object state at max_expected_checkpoint_sequence_number and assert. + let last_tx = last_transaction.unwrap(); + let obj_id = last_tx.gas()[0].0; + let gas_owner_id = last_tx.sender_address(); + + let snapshot_object = objects_snapshot::table + .filter(objects_snapshot::object_id.eq(obj_id.to_vec())) + .filter( + objects_snapshot::checkpoint_sequence_number + .eq(max_expected_checkpoint_sequence_number as i64), + ) + .first::(&mut connection) + .await + .expect("Failed reading object from objects_snapshot"); + // Assert that the object state is as expected at checkpoint max_expected_checkpoint_sequence_number + assert_eq!(snapshot_object.object_id, obj_id.to_vec()); + assert_eq!( + snapshot_object.checkpoint_sequence_number, + max_expected_checkpoint_sequence_number as i64 + ); + assert_eq!(snapshot_object.owner_type, Some(1)); + assert_eq!(snapshot_object.owner_id, Some(gas_owner_id.to_vec())); + Ok(()) +} + +#[tokio::test] +pub async fn test_objects_ingestion() -> Result<(), IndexerError> { + let tempdir = tempdir().unwrap(); + let mut sim = Simulacrum::new(); + let data_ingestion_path = tempdir.path().to_path_buf(); + sim.set_data_ingestion_path(data_ingestion_path.clone()); + + let (_, pg_store, _, _database) = set_up(Arc::new(sim), data_ingestion_path).await; + + let mut objects = Vec::new(); + for _ in 0..1000 { + objects.push(TransactionObjectChangesToCommit { + changed_objects: vec![IndexedObject::random()], + deleted_objects: vec![IndexedDeletedObject::random()], + }); + } + pg_store.persist_objects(objects).await?; + Ok(()) +} + +// test insert large batch of tx_indices +#[tokio::test] +pub async fn test_insert_large_batch_tx_indices() -> Result<(), IndexerError> { + let tempdir = tempdir().unwrap(); + let mut sim = Simulacrum::new(); + let data_ingestion_path = tempdir.path().to_path_buf(); + sim.set_data_ingestion_path(data_ingestion_path.clone()); + + let (_, pg_store, _, _database) = set_up(Arc::new(sim), data_ingestion_path).await; + + let mut v = Vec::new(); + for _ in 0..1000 { + v.push(TxIndex::random()); + } + pg_store.persist_tx_indices(v).await?; + Ok(()) +} + +// test insert large batch of event_indices +#[tokio::test] +pub async fn test_insert_large_batch_event_indices() -> Result<(), IndexerError> { + let tempdir = tempdir().unwrap(); + let mut sim = Simulacrum::new(); + let data_ingestion_path = tempdir.path().to_path_buf(); + sim.set_data_ingestion_path(data_ingestion_path.clone()); + + let (_, pg_store, _, _database) = set_up(Arc::new(sim), data_ingestion_path).await; + + let mut v = Vec::new(); + for _ in 0..1000 { + v.push(EventIndex::random()); + } + pg_store.persist_event_indices(v).await?; + Ok(()) +} + +#[tokio::test] +pub async fn test_epoch_boundary() -> Result<(), IndexerError> { + println!("test_epoch_boundary"); + let tempdir = tempdir().unwrap(); + let mut sim = Simulacrum::new(); + let data_ingestion_path = tempdir.path().to_path_buf(); + sim.set_data_ingestion_path(data_ingestion_path.clone()); + + let transfer_recipient = SuiAddress::random_for_testing_only(); + let (transaction, _) = sim.transfer_txn(transfer_recipient); + let (_, err) = sim.execute_transaction(transaction.clone()).unwrap(); + assert!(err.is_none()); + + sim.create_checkpoint(); // checkpoint 1 + sim.advance_epoch(true); // checkpoint 2 and epoch 1 + + let (transaction, _) = sim.transfer_txn(transfer_recipient); + let (_, err) = sim.execute_transaction(transaction.clone()).unwrap(); + sim.create_checkpoint(); // checkpoint 3 + assert!(err.is_none()); + + let (_, pg_store, _, _database) = set_up(Arc::new(sim), data_ingestion_path).await; + wait_for_checkpoint(&pg_store, 3).await?; + let mut connection = pg_store.pool().dedicated_connection().await.unwrap(); + let db_checkpoint: StoredCheckpoint = checkpoints::table + .order(checkpoints::sequence_number.desc()) + .first::(&mut connection) + .await + .expect("Failed reading checkpoint from PostgresDB"); + assert_eq!(db_checkpoint.sequence_number, 3); + assert_eq!(db_checkpoint.epoch, 1); + Ok(()) +} diff --git a/crates/sui-indexer/tests/read_api_tests.rs b/crates/sui-indexer/tests/read_api_tests.rs new file mode 100644 index 0000000000000..7b855800202db --- /dev/null +++ b/crates/sui-indexer/tests/read_api_tests.rs @@ -0,0 +1,50 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use jsonrpsee::core::RpcResult; +use simulacrum::Simulacrum; +use std::sync::Arc; +use sui_indexer::apis::read_api::ReadApi; +use sui_indexer::indexer_reader::IndexerReader; +use sui_indexer::test_utils::{set_up, wait_for_checkpoint}; +use sui_json_rpc_api::ReadApiServer; +use tempfile::tempdir; + +#[tokio::test] +async fn test_checkpoint_apis() -> RpcResult<()> { + let tempdir = tempdir().unwrap(); + let mut sim = Simulacrum::new(); + let data_ingestion_path = tempdir.path().to_path_buf(); + sim.set_data_ingestion_path(data_ingestion_path.clone()); + sim.create_checkpoint(); + sim.create_checkpoint(); + + let (_, pg_store, _, _database) = set_up(Arc::new(sim), data_ingestion_path).await; + wait_for_checkpoint(&pg_store, 2).await.unwrap(); + + // Test get_latest_checkpoint_sequence_number + let read_api = ReadApi::new(IndexerReader::new(pg_store.pool())); + let latest_checkpoint = read_api.get_latest_checkpoint_sequence_number().await?; + assert_eq!(latest_checkpoint.into_inner(), 2); + + // Test get_checkpoint + let checkpoint_id = sui_json_rpc_types::CheckpointId::SequenceNumber(1); + let checkpoint = read_api.get_checkpoint(checkpoint_id).await?; + assert_eq!(checkpoint.sequence_number, 1); + + // Test get_checkpoints + let checkpoints = read_api.get_checkpoints(None, Some(10), false).await?; + assert_eq!(checkpoints.data.len(), 3); // 0, 1, 2 + assert!(!checkpoints.has_next_page); + assert_eq!(checkpoints.next_cursor, Some(2.into())); + + let checkpoints = read_api + .get_checkpoints(Some(2.into()), Some(2), true) + .await?; + assert_eq!(checkpoints.data.len(), 2); + assert!(!checkpoints.has_next_page); + assert_eq!(checkpoints.next_cursor, Some(0.into())); + assert_eq!(checkpoints.data[0].sequence_number, 1); + assert_eq!(checkpoints.data[1].sequence_number, 0); + Ok(()) +} diff --git a/crates/sui-json-rpc-api/src/deepbook.rs b/crates/sui-json-rpc-api/src/deepbook.rs new file mode 100644 index 0000000000000..4f2f098918a3e --- /dev/null +++ b/crates/sui-json-rpc-api/src/deepbook.rs @@ -0,0 +1,14 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use jsonrpsee::core::RpcResult; +use jsonrpsee::proc_macros::rpc; + +use sui_open_rpc_macros::open_rpc; + +#[open_rpc(namespace = "suix", tag = "DeepBook Read API")] +#[rpc(server, client, namespace = "suix")] +pub trait DeepBookApi { + #[method(name = "ping")] + async fn ping(&self) -> RpcResult; +} diff --git a/crates/sui-json-rpc-tests/tests/rpc_server_tests.rs b/crates/sui-json-rpc-tests/tests/rpc_server_tests.rs index 3d648666047e1..73d319b6eb5ac 100644 --- a/crates/sui-json-rpc-tests/tests/rpc_server_tests.rs +++ b/crates/sui-json-rpc-tests/tests/rpc_server_tests.rs @@ -21,9 +21,10 @@ use sui_json_rpc_types::{ use sui_macros::sim_test; use sui_move_build::BuildConfig; use sui_swarm_config::genesis_config::{DEFAULT_GAS_AMOUNT, DEFAULT_NUMBER_OF_OBJECT_PER_ACCOUNT}; +use sui_test_transaction_builder::make_transfer_sui_transaction; use sui_types::balance::Supply; -use sui_types::base_types::ObjectID; use sui_types::base_types::SequenceNumber; +use sui_types::base_types::{ObjectID, SuiAddress}; use sui_types::coin::{TreasuryCap, COIN_MODULE_NAME}; use sui_types::digests::ObjectDigest; use sui_types::gas_coin::GAS; @@ -402,6 +403,46 @@ async fn test_get_coins() -> Result<(), anyhow::Error> { Ok(()) } +#[sim_test] +async fn test_sorted_get_coin_response() { + let cluster = TestClusterBuilder::new().build().await; + let http_client = cluster.rpc_client(); + + let address = SuiAddress::random_for_testing_only(); + + // send 5 coins to address `address` with different values + let amounts = [1, 2, 3, 4, 5]; + for amount in amounts { + let tx = make_transfer_sui_transaction(&cluster.wallet, Some(address), Some(amount)).await; + let (tx_bytes, signatures) = tx.to_tx_bytes_and_signatures(); + + http_client + .execute_transaction_block( + tx_bytes, + signatures, + None, + Some(ExecuteTransactionRequestType::WaitForLocalExecution), + ) + .await + .unwrap(); + } + + let coins: CoinPage = http_client + .get_coins(address, None, None, None) + .await + .unwrap(); + assert_eq!(amounts.len(), coins.data.len()); + + let balances = coins + .data + .iter() + .map(|coin| coin.balance) + .collect::>(); + let mut sorted_amounts = amounts; + sorted_amounts.reverse(); + assert_eq!(sorted_amounts.as_slice(), balances.as_slice()); +} + #[sim_test] async fn test_get_balance() -> Result<(), anyhow::Error> { let cluster = TestClusterBuilder::new().build().await; diff --git a/crates/sui-json-rpc-types/src/sui_event.rs b/crates/sui-json-rpc-types/src/sui_event.rs index 5b909215e4bbd..86aab9194a608 100644 --- a/crates/sui-json-rpc-types/src/sui_event.rs +++ b/crates/sui-json-rpc-types/src/sui_event.rs @@ -203,6 +203,12 @@ fn try_into_byte(v: &Value) -> Option { #[serde_as] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] pub enum EventFilter { + /// Return all events. + All([Box; 0]), + + /// Return events that match any of the given filters. Only supported on event subscriptions. + Any(Vec), + /// Query by sender address. Sender(SuiAddress), /// Return events emitted by the given transaction. @@ -210,8 +216,6 @@ pub enum EventFilter { ///digest of the transaction, as base-64 encoded string TransactionDigest, ), - /// Return events emitted in a specified Package. - Package(ObjectID), /// Return events emitted in a specified Move module. /// If the event is defined in Module A but emitted in a tx with Module B, /// query `MoveModule` by module B returns the event. @@ -244,10 +248,6 @@ pub enum EventFilter { #[serde_as(as = "DisplayFromStr")] module: Identifier, }, - MoveEventField { - path: String, - value: Value, - }, /// Return events emitted in [start_time, end_time] interval #[serde(rename_all = "camelCase")] TimeRange { @@ -260,33 +260,19 @@ pub enum EventFilter { #[serde_as(as = "BigInt")] end_time: u64, }, - - All(Vec), - Any(Vec), - And(Box, Box), - Or(Box, Box), } -impl EventFilter { - fn try_matches(&self, item: &SuiEvent) -> SuiResult { - Ok(match self { +impl Filter for EventFilter { + fn matches(&self, item: &SuiEvent) -> bool { + let _scope = monitored_scope("EventFilter::matches"); + match self { + EventFilter::All([]) => true, + EventFilter::Any(filters) => filters.iter().any(|f| f.matches(item)), EventFilter::MoveEventType(event_type) => &item.type_ == event_type, - EventFilter::MoveEventField { path, value } => { - matches!(item.parsed_json.pointer(path), Some(v) if v == value) - } EventFilter::Sender(sender) => &item.sender == sender, - EventFilter::Package(object_id) => &item.package_id == object_id, EventFilter::MoveModule { package, module } => { &item.transaction_module == module && &item.package_id == package } - EventFilter::All(filters) => filters.iter().all(|f| f.matches(item)), - EventFilter::Any(filters) => filters.iter().any(|f| f.matches(item)), - EventFilter::And(f1, f2) => { - EventFilter::All(vec![*(*f1).clone(), *(*f2).clone()]).matches(item) - } - EventFilter::Or(f1, f2) => { - EventFilter::Any(vec![*(*f1).clone(), *(*f2).clone()]).matches(item) - } EventFilter::Transaction(digest) => digest == &item.id.tx_digest, EventFilter::TimeRange { @@ -302,21 +288,7 @@ impl EventFilter { EventFilter::MoveEventModule { package, module } => { &item.type_.module == module && &ObjectID::from(item.type_.address) == package } - }) - } - - pub fn and(self, other_filter: EventFilter) -> Self { - Self::All(vec![self, other_filter]) - } - pub fn or(self, other_filter: EventFilter) -> Self { - Self::Any(vec![self, other_filter]) - } -} - -impl Filter for EventFilter { - fn matches(&self, item: &SuiEvent) -> bool { - let _scope = monitored_scope("EventFilter::matches"); - self.try_matches(item).unwrap_or_default() + } } } diff --git a/crates/sui-json-rpc-types/src/sui_transaction.rs b/crates/sui-json-rpc-types/src/sui_transaction.rs index c10936041645e..67859f5591354 100644 --- a/crates/sui-json-rpc-types/src/sui_transaction.rs +++ b/crates/sui-json-rpc-types/src/sui_transaction.rs @@ -2361,6 +2361,8 @@ pub enum TransactionFilter { InputObject(ObjectID), /// Query by changed object, including created, mutated and unwrapped objects. ChangedObject(ObjectID), + /// Query for transactions that touch this object. + AffectedObject(ObjectID), /// Query by sender address. FromAddress(SuiAddress), /// Query by recipient address. @@ -2390,6 +2392,18 @@ impl Filter for TransactionFilter { .mutated() .iter() .any(|oref: &OwnedObjectRef| &oref.reference.object_id == o), + TransactionFilter::AffectedObject(o) => item + .effects + .created() + .iter() + .chain(item.effects.mutated().iter()) + .chain(item.effects.unwrapped().iter()) + .map(|oref: &OwnedObjectRef| &oref.reference) + .chain(item.effects.shared_objects().iter()) + .chain(item.effects.deleted().iter()) + .chain(item.effects.unwrapped_then_deleted().iter()) + .chain(item.effects.wrapped().iter()) + .any(|oref| &oref.object_id == o), TransactionFilter::FromAddress(a) => &item.input.sender() == a, TransactionFilter::ToAddress(a) => { let mutated: &[OwnedObjectRef] = item.effects.mutated(); diff --git a/crates/sui-json-rpc/Cargo.toml b/crates/sui-json-rpc/Cargo.toml index 8afaf8f974122..60b90513b390e 100644 --- a/crates/sui-json-rpc/Cargo.toml +++ b/crates/sui-json-rpc/Cargo.toml @@ -8,6 +8,7 @@ edition = "2021" [dependencies] arc-swap.workspace = true +backoff.workspace = true chrono.workspace = true fastcrypto.workspace = true jsonrpsee.workspace = true diff --git a/crates/sui-json-rpc/src/authority_state.rs b/crates/sui-json-rpc/src/authority_state.rs index d3a694041bb90..600cd71a8b7a8 100644 --- a/crates/sui-json-rpc/src/authority_state.rs +++ b/crates/sui-json-rpc/src/authority_state.rs @@ -10,12 +10,12 @@ use std::sync::Arc; use sui_core::authority::authority_per_epoch_store::AuthorityPerEpochStore; use sui_core::authority::AuthorityState; use sui_core::execution_cache::ObjectCacheRead; +use sui_core::jsonrpc_index::TotalBalance; use sui_core::subscription_handler::SubscriptionHandler; use sui_json_rpc_types::{ Coin as SuiCoin, DevInspectResults, DryRunTransactionBlockResponse, EventFilter, SuiEvent, SuiObjectDataFilter, TransactionFilter, }; -use sui_storage::indexes::TotalBalance; use sui_storage::key_value_store::{ KVStoreCheckpointData, KVStoreTransactionData, TransactionKeyValueStore, TransactionKeyValueStoreTrait, @@ -176,7 +176,7 @@ pub trait StateRead: Send + Sync { fn get_owned_coins( &self, owner: SuiAddress, - cursor: (String, ObjectID), + cursor: (String, u64, ObjectID), limit: usize, one_coin_type_only: bool, ) -> StateReadResult>; @@ -448,15 +448,15 @@ impl StateRead for AuthorityState { fn get_owned_coins( &self, owner: SuiAddress, - cursor: (String, ObjectID), + cursor: (String, u64, ObjectID), limit: usize, one_coin_type_only: bool, ) -> StateReadResult> { Ok(self .get_owned_coins_iterator_with_cursor(owner, cursor, limit, one_coin_type_only)? - .map(|(coin_type, coin_object_id, coin)| SuiCoin { - coin_type, - coin_object_id, + .map(|(key, coin)| SuiCoin { + coin_type: key.coin_type, + coin_object_id: key.object_id, version: coin.version, digest: coin.digest, balance: coin.balance, diff --git a/crates/sui-json-rpc/src/axum_router.rs b/crates/sui-json-rpc/src/axum_router.rs index 0fb6be835c835..aa2e1e4edcadb 100644 --- a/crates/sui-json-rpc/src/axum_router.rs +++ b/crates/sui-json-rpc/src/axum_router.rs @@ -22,7 +22,7 @@ use jsonrpsee::types::{ErrorObject, Id, InvalidRequest, Params, Request}; use jsonrpsee::{core::server::rpc_module::Methods, server::logger::Logger}; use serde_json::value::RawValue; use sui_core::traffic_controller::{ - metrics::TrafficControllerMetrics, policies::TrafficTally, TrafficController, + metrics::TrafficControllerMetrics, parse_ip, policies::TrafficTally, TrafficController, }; use sui_json_rpc_api::TRANSACTION_EXECUTION_CLIENT_ERROR_CODE; use sui_types::traffic_control::ClientIdSource; @@ -63,7 +63,7 @@ impl JsonRpcService { logger, id_provider: Arc::new(RandomIntegerIdProvider), traffic_controller: policy_config.clone().map(|policy| { - Arc::new(TrafficController::spawn( + Arc::new(TrafficController::init( policy, traffic_controller_metrics, remote_fw_config, @@ -183,17 +183,7 @@ async fn process_raw_request( ); return None; }; - client_ip.parse::().ok().or_else(|| { - client_ip.parse::().ok().map(|socket_addr| socket_addr.ip()).or_else(|| { - error!( - "Failed to parse x-forwarded-for header value of {:?} to ip address or socket. \ - Please ensure that your proxy is configured to resolve client domains to an \ - IP address before writing header", - client_ip, - ); - None - }) - }) + parse_ip(client_ip) } Err(e) => { error!("Invalid UTF-8 in x-forwarded-for header: {:?}", e); diff --git a/crates/sui-json-rpc/src/balance_changes.rs b/crates/sui-json-rpc/src/balance_changes.rs index ff4e445713623..eaf4480832d08 100644 --- a/crates/sui-json-rpc/src/balance_changes.rs +++ b/crates/sui-json-rpc/src/balance_changes.rs @@ -18,7 +18,9 @@ use sui_types::gas_coin::GAS; use sui_types::object::{Object, Owner}; use sui_types::storage::WriteKind; use sui_types::transaction::InputObjectKind; +use tracing::instrument; +#[instrument(skip_all, fields(transaction_digest = %effects.transaction_digest()))] pub async fn get_balance_changes_from_effect, E>( object_provider: &P, effects: &TransactionEffects, @@ -80,6 +82,7 @@ pub async fn get_balance_changes_from_effect, E>( .await } +#[instrument(skip_all)] pub async fn get_balance_changes, E>( object_provider: &P, modified_at_version: &[(ObjectID, SequenceNumber, Option)], @@ -120,6 +123,7 @@ pub async fn get_balance_changes, E>( .collect()) } +#[instrument(skip_all)] async fn fetch_coins, E>( object_provider: &P, objects: &[(ObjectID, SequenceNumber, Option)], @@ -182,6 +186,30 @@ impl

ObjectProviderCache

{ } } + pub fn insert_objects_into_cache(&mut self, objects: Vec) { + let object_cache = self.object_cache.get_mut(); + let last_version_cache = self.last_version_cache.get_mut(); + + for object in objects { + let object_id = object.id(); + let version = object.version(); + + let key = (object_id, version); + object_cache.insert(key, object.clone()); + + match last_version_cache.get_mut(&key) { + Some(existing_seq_number) => { + if version > *existing_seq_number { + *existing_seq_number = version + } + } + None => { + last_version_cache.insert(key, version); + } + } + } + } + pub fn new_with_cache( provider: P, written_objects: BTreeMap, diff --git a/crates/sui-json-rpc/src/coin_api.rs b/crates/sui-json-rpc/src/coin_api.rs index 99d7613d7ce61..aeda2cb6dcb38 100644 --- a/crates/sui-json-rpc/src/coin_api.rs +++ b/crates/sui-json-rpc/src/coin_api.rs @@ -10,7 +10,7 @@ use cached::SizedCache; use jsonrpsee::core::RpcResult; use jsonrpsee::RpcModule; use move_core_types::language_storage::{StructTag, TypeTag}; -use sui_storage::indexes::TotalBalance; +use sui_core::jsonrpc_index::TotalBalance; use tap::TapFallible; use tracing::{debug, info, instrument}; @@ -94,9 +94,17 @@ impl CoinReadApiServer for CoinReadApi { let coin_type_tag = parse_to_type_tag(coin_type)?; let cursor = match cursor { - Some(c) => (coin_type_tag.to_string(), c), + Some(c) => { + let obj = self.internal.get_object(&c).await?.ok_or_else(|| { + SuiRpcInputError::GenericInvalid("cursor not found".to_string()) + })?; + let coin = obj.as_coin_maybe().ok_or_else(|| { + SuiRpcInputError::GenericInvalid("cursor is not a coin".to_string()) + })?; + (coin_type_tag.to_string(), !coin.balance.value(), c) + } // If cursor is not specified, we need to start from the beginning of the coin type, which is the minimal possible ObjectID. - None => (coin_type_tag.to_string(), ObjectID::ZERO), + None => (coin_type_tag.to_string(), 0, ObjectID::ZERO), }; self.internal @@ -127,7 +135,16 @@ impl CoinReadApiServer for CoinReadApi { "cursor is not a coin".to_string(), )) } else { - Ok((coin_type.unwrap().to_string(), object_id)) + let coin = obj.as_coin_maybe().ok_or_else(|| { + SuiRpcInputError::GenericInvalid( + "cursor is not a coin".to_string(), + ) + })?; + Ok(( + coin_type.unwrap().to_string(), + !coin.balance.value(), + object_id, + )) } } None => Err(SuiRpcInputError::GenericInvalid( @@ -137,7 +154,11 @@ impl CoinReadApiServer for CoinReadApi { } None => { // If cursor is None, start from the beginning - Ok((String::from_utf8([0u8].to_vec()).unwrap(), ObjectID::ZERO)) + Ok(( + String::from_utf8([0u8].to_vec()).unwrap(), + 0, + ObjectID::ZERO, + )) } }?; @@ -301,7 +322,7 @@ pub trait CoinReadInternal { async fn get_coins_iterator( &self, owner: SuiAddress, - cursor: (String, ObjectID), + cursor: (String, u64, ObjectID), limit: Option, one_coin_type_only: bool, ) -> RpcInterimResult; @@ -368,7 +389,7 @@ impl CoinReadInternal for CoinReadInternalImpl { async fn get_coins_iterator( &self, owner: SuiAddress, - cursor: (String, ObjectID), + cursor: (String, u64, ObjectID), limit: Option, one_coin_type_only: bool, ) -> RpcInterimResult { @@ -424,7 +445,9 @@ mod tests { use sui_types::messages_checkpoint::{ CheckpointContentsDigest, CheckpointDigest, CheckpointSequenceNumber, }; + use sui_types::object::MoveObject; use sui_types::object::Object; + use sui_types::object::Owner; use sui_types::utils::create_fake_transaction; use sui_types::{parse_sui_struct_tag, TypeTag}; @@ -511,7 +534,7 @@ mod tests { Usdc, } - fn get_test_coin(id_hex_literal: Option<&str>, coin_type: CoinType) -> Coin { + fn get_test_coin(id_hex_literal: Option<&str>, coin_type: CoinType) -> (Object, Coin) { let (arr, coin_type_string, balance, default_hex) = match coin_type { CoinType::Gas => ([0; 32], GAS::type_().to_string(), 42, "0xA"), CoinType::Usdc => ( @@ -527,15 +550,29 @@ mod tests { } else { ObjectID::from_hex_literal(default_hex).unwrap() }; + let owner = get_test_owner(); + let previous_transaction = TransactionDigest::from(arr); + let object = Object::new_move( + MoveObject::new_coin( + coin_type_string.parse::().unwrap().into(), + 1.into(), + object_id, + balance, + ), + Owner::AddressOwner(owner), + previous_transaction, + ); - Coin { + let coin = Coin { coin_type: coin_type_string, coin_object_id: object_id, version: SequenceNumber::from_u64(1), digest: ObjectDigest::from(arr), balance, - previous_transaction: TransactionDigest::from(arr), - } + previous_transaction, + }; + + (object, coin) } fn get_test_treasury_cap_peripherals( @@ -568,14 +605,14 @@ mod tests { #[tokio::test] async fn test_gas_coin_no_cursor() { let owner = get_test_owner(); - let gas_coin = get_test_coin(None, CoinType::Gas); + let gas_coin = get_test_coin(None, CoinType::Gas).1; let gas_coin_clone = gas_coin.clone(); let mut mock_state = MockStateRead::new(); mock_state .expect_get_owned_coins() .with( predicate::eq(owner), - predicate::eq((GAS::type_().to_string(), ObjectID::ZERO)), + predicate::eq((GAS::type_().to_string(), 0, ObjectID::ZERO)), predicate::eq(51), predicate::eq(true), ) @@ -600,9 +637,9 @@ mod tests { let owner = get_test_owner(); let limit = 2; let coins = vec![ - get_test_coin(Some("0xA"), CoinType::Gas), - get_test_coin(Some("0xAA"), CoinType::Gas), - get_test_coin(Some("0xAAA"), CoinType::Gas), + get_test_coin(Some("0xA"), CoinType::Gas).1, + get_test_coin(Some("0xAA"), CoinType::Gas).1, + get_test_coin(Some("0xAAA"), CoinType::Gas).1, ]; let coins_clone = coins.clone(); let mut mock_state = MockStateRead::new(); @@ -610,11 +647,19 @@ mod tests { .expect_get_owned_coins() .with( predicate::eq(owner), - predicate::eq((GAS::type_().to_string(), coins[0].coin_object_id)), + predicate::eq(( + GAS::type_().to_string(), + !coins[0].balance, + coins[0].coin_object_id, + )), predicate::eq(limit + 1), predicate::eq(true), ) .return_once(move |_, _, _, _| Ok(coins_clone)); + mock_state + .expect_get_object() + .with(predicate::eq(coins[0].coin_object_id)) + .return_once(|_| Ok(Some(get_test_coin(Some("0xA"), CoinType::Gas).0))); let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api @@ -634,7 +679,7 @@ mod tests { #[tokio::test] async fn test_coin_no_cursor() { - let coin = get_test_coin(None, CoinType::Usdc); + let coin = get_test_coin(None, CoinType::Usdc).1; let coin_clone = coin.clone(); // Build request params let owner = get_test_owner(); @@ -647,7 +692,7 @@ mod tests { .expect_get_owned_coins() .with( predicate::eq(owner), - predicate::eq((coin_type_tag.to_string(), ObjectID::ZERO)), + predicate::eq((coin_type_tag.to_string(), 0, ObjectID::ZERO)), predicate::eq(51), predicate::eq(true), ) @@ -673,9 +718,9 @@ mod tests { #[tokio::test] async fn test_coin_with_cursor() { let coins = vec![ - get_test_coin(Some("0xB"), CoinType::Usdc), - get_test_coin(Some("0xBB"), CoinType::Usdc), - get_test_coin(Some("0xBBB"), CoinType::Usdc), + get_test_coin(Some("0xB"), CoinType::Usdc).1, + get_test_coin(Some("0xBB"), CoinType::Usdc).1, + get_test_coin(Some("0xBBB"), CoinType::Usdc).1, ]; let coins_clone = coins.clone(); // Build request params @@ -691,11 +736,19 @@ mod tests { .expect_get_owned_coins() .with( predicate::eq(owner), - predicate::eq((coin_type_tag.to_string(), coins[0].coin_object_id)), + predicate::eq(( + coin_type_tag.to_string(), + !coins[0].balance, + coins[0].coin_object_id, + )), predicate::eq(limit + 1), predicate::eq(true), ) .return_once(move |_, _, _, _| Ok(coins_clone)); + mock_state + .expect_get_object() + .with(predicate::eq(coins[0].coin_object_id)) + .return_once(|_| Ok(Some(get_test_coin(Some("0xBB"), CoinType::Usdc).0))); let coin_read_api = CoinReadApi::new_for_tests(Arc::new(mock_state), None); let response = coin_read_api @@ -820,14 +873,18 @@ mod tests { #[tokio::test] async fn test_no_cursor() { let owner = get_test_owner(); - let gas_coin = get_test_coin(None, CoinType::Gas); + let gas_coin = get_test_coin(None, CoinType::Gas).1; let gas_coin_clone = gas_coin.clone(); let mut mock_state = MockStateRead::new(); mock_state .expect_get_owned_coins() .with( predicate::eq(owner), - predicate::eq((String::from_utf8([0u8].to_vec()).unwrap(), ObjectID::ZERO)), + predicate::eq(( + String::from_utf8([0u8].to_vec()).unwrap(), + 0, + ObjectID::ZERO, + )), predicate::eq(51), predicate::eq(false), ) @@ -846,9 +903,9 @@ mod tests { let owner = get_test_owner(); let limit = 2; let coins = vec![ - get_test_coin(Some("0xA"), CoinType::Gas), - get_test_coin(Some("0xAA"), CoinType::Gas), - get_test_coin(Some("0xAAA"), CoinType::Gas), + get_test_coin(Some("0xA"), CoinType::Gas).1, + get_test_coin(Some("0xAA"), CoinType::Gas).1, + get_test_coin(Some("0xAAA"), CoinType::Gas).1, ]; let coins_clone = coins.clone(); let coin_move_object = MoveObject::new_gas_coin( @@ -869,7 +926,11 @@ mod tests { .expect_get_owned_coins() .with( predicate::eq(owner), - predicate::eq((coins[0].coin_type.clone(), coins[0].coin_object_id)), + predicate::eq(( + coins[0].coin_type.clone(), + !coins[0].balance, + coins[0].coin_object_id, + )), predicate::eq(limit + 1), predicate::eq(false), ) @@ -942,7 +1003,7 @@ mod tests { #[tokio::test] async fn test_gas_coin() { let owner = get_test_owner(); - let gas_coin = get_test_coin(None, CoinType::Gas); + let gas_coin = get_test_coin(None, CoinType::Gas).1; let gas_coin_clone = gas_coin.clone(); let mut mock_state = MockStateRead::new(); mock_state @@ -976,7 +1037,7 @@ mod tests { #[tokio::test] async fn test_with_coin_type() { let owner = get_test_owner(); - let coin = get_test_coin(None, CoinType::Usdc); + let coin = get_test_coin(None, CoinType::Usdc).1; let coin_clone = coin.clone(); let mut mock_state = MockStateRead::new(); mock_state @@ -1092,9 +1153,9 @@ mod tests { #[tokio::test] async fn test_success_scenario() { let owner = get_test_owner(); - let gas_coin = get_test_coin(None, CoinType::Gas); + let gas_coin = get_test_coin(None, CoinType::Gas).1; let gas_coin_type_tag = get_test_coin_type_tag(gas_coin.coin_type.clone()); - let usdc_coin = get_test_coin(None, CoinType::Usdc); + let usdc_coin = get_test_coin(None, CoinType::Usdc).1; let usdc_coin_type_tag = get_test_coin_type_tag(usdc_coin.coin_type.clone()); let mut mock_state = MockStateRead::new(); mock_state diff --git a/crates/sui-json-rpc/src/error.rs b/crates/sui-json-rpc/src/error.rs index f56d0cde9ebf4..d4abdb05dfe06 100644 --- a/crates/sui-json-rpc/src/error.rs +++ b/crates/sui-json-rpc/src/error.rs @@ -252,7 +252,7 @@ impl From for RpcError { error_list.push(format!("- {}", err)); } - let error_msg = format!("Transaction execution failed due to issues with transaction inputs, please review the errors and try again:\n{}", error_list.join("\n")); + let error_msg = format!("Transaction validator signing failed due to issues with transaction inputs, please review the errors and try again:\n{}", error_list.join("\n")); let error_object = ErrorObject::owned( TRANSACTION_EXECUTION_CLIENT_ERROR_CODE, @@ -516,7 +516,7 @@ mod tests { let expected_code = expect!["-32002"]; expected_code.assert_eq(&error_object.code().to_string()); let expected_message = - expect!["Transaction execution failed due to issues with transaction inputs, please review the errors and try again:\n- Balance of gas object 10 is lower than the needed amount: 100\n- Object ID 0x0000000000000000000000000000000000000000000000000000000000000000 Version 0x0 Digest 11111111111111111111111111111111 is not available for consumption, current version: 0xa"]; + expect!["Transaction validator signing failed due to issues with transaction inputs, please review the errors and try again:\n- Balance of gas object 10 is lower than the needed amount: 100\n- Object ID 0x0000000000000000000000000000000000000000000000000000000000000000 Version 0x0 Digest 11111111111111111111111111111111 is not available for consumption, current version: 0xa"]; expected_message.assert_eq(error_object.message()); } @@ -548,14 +548,15 @@ mod tests { let expected_code = expect!["-32002"]; expected_code.assert_eq(&error_object.code().to_string()); let expected_message = - expect!["Transaction execution failed due to issues with transaction inputs, please review the errors and try again:\n- Could not find the referenced object 0x0000000000000000000000000000000000000000000000000000000000000000 at version None"]; + expect!["Transaction validator signing failed due to issues with transaction inputs, please review the errors and try again:\n- Could not find the referenced object 0x0000000000000000000000000000000000000000000000000000000000000000 at version None"]; expected_message.assert_eq(error_object.message()); } #[test] fn test_quorum_driver_internal_error() { - let quorum_driver_error = - QuorumDriverError::QuorumDriverInternalError(SuiError::UnexpectedMessage); + let quorum_driver_error = QuorumDriverError::QuorumDriverInternalError( + SuiError::UnexpectedMessage("test".to_string()), + ); let rpc_error: RpcError = Error::QuorumDriverError(quorum_driver_error).into(); @@ -570,7 +571,7 @@ mod tests { fn test_system_overload() { let quorum_driver_error = QuorumDriverError::SystemOverload { overloaded_stake: 10, - errors: vec![(SuiError::UnexpectedMessage, 0, vec![])], + errors: vec![(SuiError::UnexpectedMessage("test".to_string()), 0, vec![])], }; let rpc_error: RpcError = Error::QuorumDriverError(quorum_driver_error).into(); diff --git a/crates/sui-json-rpc/src/lib.rs b/crates/sui-json-rpc/src/lib.rs index 08adfcfa44469..3075471af9d1b 100644 --- a/crates/sui-json-rpc/src/lib.rs +++ b/crates/sui-json-rpc/src/lib.rs @@ -142,7 +142,17 @@ impl JsonRpcServerBuilder { .and_then(|v| v.to_str().ok()) .map(tracing::field::display); - tracing::info_span!("json-rpc-request", "x-req-id" = request_id) + let origin = request + .headers() + .get("origin") + .and_then(|v| v.to_str().ok()) + .map(tracing::field::display); + + tracing::info_span!( + "json-rpc-request", + "x-req-id" = request_id, + "origin" = origin + ) }) .on_request(()) .on_response(()) diff --git a/crates/sui-json-rpc/src/object_changes.rs b/crates/sui-json-rpc/src/object_changes.rs index c3fbdf26ad67b..33e1d307ba308 100644 --- a/crates/sui-json-rpc/src/object_changes.rs +++ b/crates/sui-json-rpc/src/object_changes.rs @@ -6,13 +6,17 @@ use std::collections::BTreeMap; use sui_json_rpc_types::ObjectChange; use sui_types::base_types::{ObjectID, ObjectRef, SequenceNumber, SuiAddress}; use sui_types::effects::ObjectRemoveKind; +use sui_types::effects::{TransactionEffects, TransactionEffectsAPI}; use sui_types::object::Owner; use sui_types::storage::WriteKind; +use tracing::instrument; use crate::ObjectProvider; +#[instrument(skip_all, fields(transaction_digest = %effects.transaction_digest()))] pub async fn get_object_changes, E>( object_provider: &P, + effects: &TransactionEffects, sender: SuiAddress, modified_at_versions: Vec<(ObjectID, SequenceNumber)>, all_changed_objects: Vec<(ObjectRef, Owner, WriteKind)>, diff --git a/crates/sui-json-rpc/src/read_api.rs b/crates/sui-json-rpc/src/read_api.rs index 4bfc9bfb29f3e..69921be79a625 100644 --- a/crates/sui-json-rpc/src/read_api.rs +++ b/crates/sui-json-rpc/src/read_api.rs @@ -3,9 +3,12 @@ use std::collections::HashMap; use std::sync::Arc; +use std::time::Duration; use anyhow::anyhow; use async_trait::async_trait; +use backoff::future::retry; +use backoff::ExponentialBackoff; use futures::future::join_all; use indexmap::map::IndexMap; use itertools::Itertools; @@ -195,6 +198,7 @@ impl ReadApi { Ok(checkpoints) } + #[instrument(skip_all)] async fn multi_get_transaction_blocks_internal( &self, digests: Vec, @@ -316,54 +320,96 @@ impl ReadApi { if opts.show_events { trace!("getting events"); - - let event_digests_list = temp_response + let events_digests_list = temp_response .values() .filter_map(|cache_entry| match &cache_entry.effects { Some(eff) => eff.events_digest().cloned(), None => None, }) .collect::>(); + // filter out empty events digest, as they do not have to be read from the DB + let empty_events_digest = TransactionEvents::default().digest(); + let events_digests_list = events_digests_list + .into_iter() + .filter(|d| d != &empty_events_digest) + .collect::>(); - // fetch events from the DB - let events = self - .transaction_kv_store - .multi_get_events(&event_digests_list) + let mut events_digest_to_events = if events_digests_list.is_empty() { + HashMap::new() + } else { + // fetch events from the DB with retry, retry each 0.5s for 3s + let backoff = ExponentialBackoff { + max_elapsed_time: Some(Duration::from_secs(3)), + multiplier: 1.0, + ..ExponentialBackoff::default() + }; + let events = retry(backoff, || async { + match self + .transaction_kv_store + .multi_get_events(&events_digests_list) + .await + { + // Only return Ok when all the queried transaction events are found, otherwise retry + // until timeout, then return Err. + Ok(events) if !events.contains(&None) => Ok(events), + Ok(_) => Err(backoff::Error::transient(Error::UnexpectedError( + "Events not found, transaction execution may be incomplete.".into(), + ))), + Err(e) => Err(backoff::Error::permanent(Error::UnexpectedError(format!( + "Failed to call multi_get_events: {e:?}" + )))), + } + }) .await .map_err(|e| { - Error::UnexpectedError(format!("Failed to call multi_get_events for transactions {digests:?} with event digests {event_digests_list:?}: {e:?}")) + Error::UnexpectedError(format!( + "Retrieving events with retry failed for events digests {events_digests_list:?}: {e:?}" + )) })? .into_iter(); - // construct a hashmap of event digests -> events for fast lookup - let event_digest_to_events = event_digests_list - .into_iter() - .zip(events) - .collect::>(); + // construct a hashmap of events digests -> events for fast lookup + let events_map = events_digests_list + .into_iter() + .zip(events) + .collect::>(); + // Double check that all events are `Some` and their digests match the key + for (events_digest, events) in events_map.iter() { + if let Some(events) = events { + if &events.digest() != events_digest { + return Err(Error::UnexpectedError(format!( + "Events digest {events_digest:?} does not match the key {:?}", + events.digest() + ))); + } + } else { + return Err(Error::UnexpectedError(format!( + "Events of digest {events_digest:?} is None, but it should not be" + ))); + } + } + events_map + }; + events_digest_to_events.insert(empty_events_digest, Some(TransactionEvents::default())); // fill cache with the events for (_, cache_entry) in temp_response.iter_mut() { let transaction_digest = cache_entry.digest; - let event_digest: Option> = cache_entry - .effects - .as_ref() - .map(|e| e.events_digest().cloned()); - let event_digest = event_digest.flatten(); - if event_digest.is_some() { - // safe to unwrap because `is_some` is checked - let event_digest = event_digest.as_ref().unwrap(); - let events= event_digest_to_events - .get(event_digest) + if let Some(events_digest) = + cache_entry.effects.as_ref().and_then(|e| e.events_digest()) + { + let events = events_digest_to_events + .get(events_digest) .cloned() - .unwrap_or_else(|| panic!("Expect event digest {event_digest:?} to be found in cache for transaction {transaction_digest}")) + .unwrap_or_else(|| panic!("Expect event digest {events_digest:?} to be found in cache for transaction {transaction_digest}")) .map(|events| to_sui_transaction_events(self, cache_entry.digest, events)); match events { Some(Ok(e)) => cache_entry.events = Some(e), Some(Err(e)) => cache_entry.errors.push(e.to_string()), None => { - error!("Failed to fetch events with event digest {event_digest:?} for txn {transaction_digest}"); + error!("Failed to fetch events with event digest {events_digest:?} for txn {transaction_digest}"); cache_entry.errors.push(format!( - "Failed to fetch events with event digest {event_digest:?}", + "Failed to fetch events with event digest {events_digest:?}", )) } } @@ -429,6 +475,7 @@ impl ReadApi { results.push(get_object_changes( &object_cache, + effects, resp.transaction .as_ref() .ok_or_else(|| { @@ -846,6 +893,7 @@ impl ReadApiServer for ReadApi { let sender = input.data().intent_message().value.sender(); let object_changes = get_object_changes( &object_cache, + effects, sender, effects.modified_at_versions(), effects.all_changed_objects(), @@ -1061,6 +1109,7 @@ impl SuiRpcModule for ReadApi { } } +#[instrument(skip_all)] fn to_sui_transaction_events( fullnode_api: &ReadApi, tx_digest: TransactionDigest, @@ -1100,6 +1149,7 @@ pub enum ObjectDisplayError { StateReadError(#[from] StateReadError), } +#[instrument(skip(fullnode_api, kv_store))] async fn get_display_fields( fullnode_api: &ReadApi, kv_store: &Arc, @@ -1124,6 +1174,7 @@ async fn get_display_fields( }) } +#[instrument(skip(kv_store, fullnode_api))] async fn get_display_object_by_type( kv_store: &Arc, fullnode_api: &ReadApi, @@ -1317,6 +1368,7 @@ fn get_value_from_move_struct( } } +#[instrument(skip_all)] fn convert_to_response( cache: IntermediateTransactionResponse, opts: &SuiTransactionBlockResponseOptions, diff --git a/crates/sui-json-rpc/src/transaction_execution_api.rs b/crates/sui-json-rpc/src/transaction_execution_api.rs index d38a70be4c553..215d06c5c717a 100644 --- a/crates/sui-json-rpc/src/transaction_execution_api.rs +++ b/crates/sui-json-rpc/src/transaction_execution_api.rs @@ -202,9 +202,15 @@ impl TransactionExecutionApi { None }; - let object_cache = response.output_objects.map(|output_objects| { - ObjectProviderCache::new_with_output_objects(self.state.clone(), output_objects) - }); + let object_cache = match (response.input_objects, response.output_objects) { + (Some(input_objects), Some(output_objects)) => { + let mut object_cache = ObjectProviderCache::new(self.state.clone()); + object_cache.insert_objects_into_cache(input_objects); + object_cache.insert_objects_into_cache(output_objects); + Some(object_cache) + } + _ => None, + }; let balance_changes = match &object_cache { Some(object_cache) if opts.show_balance_changes => Some( @@ -223,6 +229,7 @@ impl TransactionExecutionApi { Some(object_cache) if opts.show_object_changes => Some( get_object_changes( object_cache, + &response.effects.effects, sender, response.effects.effects.modified_at_versions(), response.effects.effects.all_changed_objects(), @@ -296,6 +303,7 @@ impl TransactionExecutionApi { .await?; let object_changes = get_object_changes( &object_cache, + &transaction_effects, sender, transaction_effects.modified_at_versions(), transaction_effects.all_changed_objects(), diff --git a/crates/sui-move-build/src/lib.rs b/crates/sui-move-build/src/lib.rs index 5d8e36a1b8520..ce628c3894e75 100644 --- a/crates/sui-move-build/src/lib.rs +++ b/crates/sui-move-build/src/lib.rs @@ -37,7 +37,7 @@ use move_package::{ BuildConfig as MoveBuildConfig, }; use move_package::{ - resolution::resolution_graph::Package, source_package::parsed_manifest::CustomDepInfo, + resolution::resolution_graph::Package, source_package::parsed_manifest::OnChainInfo, source_package::parsed_manifest::SourceManifest, }; use move_symbol_pool::Symbol; @@ -631,14 +631,10 @@ impl PackageHooks for SuiPackageHooks { ] } - fn custom_dependency_key(&self) -> Option { - None - } - - fn resolve_custom_dependency( + fn resolve_on_chain_dependency( &self, _dep_name: move_symbol_pool::Symbol, - _info: &CustomDepInfo, + _info: &OnChainInfo, ) -> anyhow::Result<()> { Ok(()) } diff --git a/crates/sui-move/src/new.rs b/crates/sui-move/src/new.rs index 3adf024d43e8d..939ff7c3740d6 100644 --- a/crates/sui-move/src/new.rs +++ b/crates/sui-move/src/new.rs @@ -33,9 +33,7 @@ impl New { w, r#"/* /// Module: {name} -module {name}::{name} {{ - -}} +module {name}::{name}; */"#, name = name )?; @@ -49,21 +47,20 @@ module {name}::{name} {{ w, r#"/* #[test_only] -module {name}::{name}_tests {{ - // uncomment this line to import the module - // use {name}::{name}; +module {name}::{name}_tests; +// uncomment this line to import the module +// use {name}::{name}; - const ENotImplemented: u64 = 0; +const ENotImplemented: u64 = 0; - #[test] - fun test_{name}() {{ - // pass - }} +#[test] +fun test_{name}() {{ + // pass +}} - #[test, expected_failure(abort_code = ::{name}::{name}_tests::ENotImplemented)] - fun test_{name}_fail() {{ - abort ENotImplemented - }} +#[test, expected_failure(abort_code = ::{name}::{name}_tests::ENotImplemented)] +fun test_{name}_fail() {{ + abort ENotImplemented }} */"#, name = name diff --git a/crates/sui-network/Cargo.toml b/crates/sui-network/Cargo.toml index 1f4b471d46c32..4473279ad0ac8 100644 --- a/crates/sui-network/Cargo.toml +++ b/crates/sui-network/Cargo.toml @@ -14,6 +14,7 @@ serde.workspace = true tonic.workspace = true dashmap.workspace = true tower.workspace = true +shared-crypto.workspace = true sui-archival.workspace = true sui-macros.workspace = true @@ -27,6 +28,7 @@ bcs.workspace = true bytes.workspace = true fastcrypto.workspace = true fastcrypto-tbls.workspace = true +mysten-common.workspace = true mysten-network.workspace = true tokio = { workspace = true, features = ["full"] } tracing.workspace = true diff --git a/crates/sui-network/build.rs b/crates/sui-network/build.rs index 846fb75130f22..3b6926e0fae15 100644 --- a/crates/sui-network/build.rs +++ b/crates/sui-network/build.rs @@ -31,6 +31,15 @@ fn main() -> Result<()> { .codec_path(codec_path) .build(), ) + .method( + Method::builder() + .name("transaction_v2") + .route_name("TransactionV2") + .input_type("sui_types::messages_grpc::HandleTransactionRequestV2") + .output_type("sui_types::messages_grpc::HandleTransactionResponseV2") + .codec_path(codec_path) + .build(), + ) .method( Method::builder() .name("handle_certificate_v2") @@ -141,6 +150,15 @@ fn build_anemo_services(out_dir: &Path) { .codec_path(codec_path) .build(), ) + .method( + anemo_build::manual::Method::builder() + .name("get_known_peers_v2") + .route_name("GetKnownPeersV2") + .request_type("()") + .response_type("crate::discovery::GetKnownPeersResponseV2") + .codec_path(codec_path) + .build(), + ) .build(); let state_sync = anemo_build::manual::Service::builder() diff --git a/crates/sui-network/src/discovery/builder.rs b/crates/sui-network/src/discovery/builder.rs index 71ce064154034..ef56e208f9567 100644 --- a/crates/sui-network/src/discovery/builder.rs +++ b/crates/sui-network/src/discovery/builder.rs @@ -7,11 +7,13 @@ use super::{ use crate::discovery::TrustedPeerChangeEvent; use anemo::codegen::InboundRequestLayer; use anemo_tower::rate_limit; +use fastcrypto::traits::KeyPair; use std::{ collections::HashMap, sync::{Arc, RwLock}, }; use sui_config::p2p::P2pConfig; +use sui_types::crypto::NetworkKeyPair; use tap::Pipe; use tokio::{ sync::{oneshot, watch}, @@ -117,7 +119,11 @@ pub struct UnstartedDiscovery { } impl UnstartedDiscovery { - pub(super) fn build(self, network: anemo::Network) -> (DiscoveryEventLoop, Handle) { + pub(super) fn build( + self, + network: anemo::Network, + keypair: NetworkKeyPair, + ) -> (DiscoveryEventLoop, Handle) { let Self { handle, config, @@ -146,6 +152,7 @@ impl UnstartedDiscovery { discovery_config: Arc::new(discovery_config), allowlisted_peers, network, + keypair, tasks: JoinSet::new(), pending_dials: Default::default(), dial_seed_peers_task: None, @@ -158,8 +165,9 @@ impl UnstartedDiscovery { ) } - pub fn start(self, network: anemo::Network) -> Handle { - let (event_loop, handle) = self.build(network); + pub fn start(self, network: anemo::Network, keypair: NetworkKeyPair) -> Handle { + assert_eq!(network.peer_id().0, *keypair.public().0.as_bytes()); + let (event_loop, handle) = self.build(network, keypair); tokio::spawn(event_loop.start()); handle diff --git a/crates/sui-network/src/discovery/mod.rs b/crates/sui-network/src/discovery/mod.rs index b770ff691f435..d701c4f3fb412 100644 --- a/crates/sui-network/src/discovery/mod.rs +++ b/crates/sui-network/src/discovery/mod.rs @@ -3,14 +3,20 @@ use anemo::types::PeerInfo; use anemo::{types::PeerEvent, Network, Peer, PeerId, Request, Response}; +use fastcrypto::ed25519::{Ed25519PublicKey, Ed25519Signature}; use futures::StreamExt; +use mysten_common::debug_fatal; use serde::{Deserialize, Serialize}; +use shared_crypto::intent::IntentScope; use std::{ collections::HashMap, sync::{Arc, RwLock}, time::Duration, }; use sui_config::p2p::{AccessType, DiscoveryConfig, P2pConfig, SeedPeer}; +use sui_types::crypto::{NetworkKeyPair, Signer, ToFromBytes, VerifyingKey}; +use sui_types::digests::Digest; +use sui_types::message_envelope::{Envelope, Message, VerifiedEnvelope}; use sui_types::multiaddr::Multiaddr; use tap::{Pipe, TapFallible}; use tokio::sync::broadcast::error::RecvError; @@ -23,6 +29,9 @@ use tracing::{debug, info, trace}; const TIMEOUT: Duration = Duration::from_secs(1); const ONE_DAY_MILLISECONDS: u64 = 24 * 60 * 60 * 1_000; +const MAX_ADDRESS_LENGTH: usize = 300; +const MAX_PEERS_TO_SEND: usize = 200; +const MAX_ADDRESSES_PER_PEER: usize = 2; mod generated { include!(concat!(env!("OUT_DIR"), "/sui.Discovery.rs")); @@ -39,14 +48,15 @@ pub use generated::{ discovery_server::{Discovery, DiscoveryServer}, }; pub use server::GetKnownPeersResponse; +pub use server::GetKnownPeersResponseV2; use self::metrics::Metrics; /// The internal discovery state shared between the main event loop and the request handler struct State { - our_info: Option, + our_info: Option, connected_peers: HashMap, - known_peers: HashMap, + known_peers: HashMap, } /// The information necessary to dial another peer. @@ -67,6 +77,36 @@ pub struct NodeInfo { pub access_type: AccessType, } +impl NodeInfo { + fn sign(self, keypair: &NetworkKeyPair) -> SignedNodeInfo { + let msg = bcs::to_bytes(&self).expect("BCS serialization should not fail"); + let sig = keypair.sign(&msg); + SignedNodeInfo::new_from_data_and_sig(self, sig) + } +} + +pub type SignedNodeInfo = Envelope; + +pub type VerifiedSignedNodeInfo = VerifiedEnvelope; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub struct NodeInfoDigest(Digest); + +impl NodeInfoDigest { + pub const fn new(digest: [u8; 32]) -> Self { + Self(Digest::new(digest)) + } +} + +impl Message for NodeInfo { + type DigestType = NodeInfoDigest; + const SCOPE: IntentScope = IntentScope::DiscoveryPeers; + + fn digest(&self) -> Self::DigestType { + unreachable!("NodeInfoDigest is not used today") + } +} + #[derive(Clone, Debug, Default)] pub struct TrustedPeerChangeEvent { pub new_peers: Vec, @@ -77,6 +117,7 @@ struct DiscoveryEventLoop { discovery_config: Arc, allowlisted_peers: Arc>>, network: Network, + keypair: NetworkKeyPair, tasks: JoinSet<()>, pending_dials: HashMap, dial_seed_peers_task: Option, @@ -154,7 +195,8 @@ impl DiscoveryEventLoop { addresses: address, timestamp_ms: now_unix(), access_type: self.discovery_config.access_type(), - }; + } + .sign(&self.keypair); self.state.write().unwrap().our_info = Some(our_info); } @@ -193,8 +235,11 @@ impl DiscoveryEventLoop { } fn update_our_info_timestamp(&mut self, now_unix: u64) { - if let Some(our_info) = &mut self.state.write().unwrap().our_info { - our_info.timestamp_ms = now_unix; + let state = &mut self.state.write().unwrap(); + if let Some(our_info) = &state.our_info { + let mut data = our_info.data().clone(); + data.timestamp_ms = now_unix; + state.our_info = Some(data.sign(&self.keypair)); } } @@ -223,6 +268,7 @@ impl DiscoveryEventLoop { // Query the new node for any peers self.tasks.spawn(query_peer_for_their_known_peers( peer, + self.discovery_config.clone(), self.state.clone(), self.metrics.clone(), self.allowlisted_peers.clone(), @@ -301,7 +347,7 @@ impl DiscoveryEventLoop { ) { let abort_handle = self.tasks.spawn(try_to_connect_to_peer( self.network.clone(), - info.to_owned(), + info.data().to_owned(), )); self.pending_dials.insert(*peer_id, abort_handle); } @@ -378,6 +424,7 @@ async fn try_to_connect_to_seed_peers( async fn query_peer_for_their_known_peers( peer: Peer, + config: Arc, state: Arc>, metrics: Metrics, allowlisted_peers: Arc>>, @@ -385,24 +432,50 @@ async fn query_peer_for_their_known_peers( let mut client = DiscoveryClient::new(peer); let request = Request::new(()).with_timeout(TIMEOUT); - if let Some(found_peers) = client - .get_known_peers(request) - .await - .ok() - .map(Response::into_inner) - .map( - |GetKnownPeersResponse { - own_info, - mut known_peers, - }| { - if !own_info.addresses.is_empty() { - known_peers.push(own_info) - } - known_peers - }, - ) - { - update_known_peers(state, metrics, found_peers, allowlisted_peers); + let found_peers = if config.enable_node_info_signatures() { + client + .get_known_peers_v2(request) + .await + .ok() + .map(Response::into_inner) + .map( + |GetKnownPeersResponseV2 { + own_info, + mut known_peers, + }| { + if !own_info.addresses.is_empty() { + known_peers.push(own_info) + } + known_peers + }, + ) + } else { + client + .get_known_peers(request) + .await + .ok() + .map(Response::into_inner) + .map( + |GetKnownPeersResponse { + own_info, + mut known_peers, + }| { + if !own_info.addresses.is_empty() { + known_peers.push(own_info) + } + known_peers + .into_iter() + .map(|info| { + // SignedNodeInfo with fake default signatures will only work if + // signature verification is disabled. + SignedNodeInfo::new_from_data_and_sig(info, Ed25519Signature::default()) + }) + .collect() + }, + ) + }; + if let Some(found_peers) = found_peers { + update_known_peers(config, state, metrics, found_peers, allowlisted_peers); } } @@ -421,25 +494,57 @@ async fn query_connected_peers_for_their_known_peers( .flat_map(|id| network.peer(id)) .choose_multiple(&mut rand::thread_rng(), config.peers_to_query()); + let enable_node_info_signatures = config.enable_node_info_signatures(); let found_peers = peers_to_query .into_iter() .map(DiscoveryClient::new) .map(|mut client| async move { let request = Request::new(()).with_timeout(TIMEOUT); - client - .get_known_peers(request) - .await - .ok() - .map(Response::into_inner) - .map( - |GetKnownPeersResponse { - own_info, - mut known_peers, - }| { - known_peers.push(own_info); - known_peers - }, - ) + if enable_node_info_signatures { + client + .get_known_peers_v2(request) + .await + .ok() + .map(Response::into_inner) + .map( + |GetKnownPeersResponseV2 { + own_info, + mut known_peers, + }| { + if !own_info.addresses.is_empty() { + known_peers.push(own_info) + } + known_peers + }, + ) + } else { + client + .get_known_peers(request) + .await + .ok() + .map(Response::into_inner) + .map( + |GetKnownPeersResponse { + own_info, + mut known_peers, + }| { + if !own_info.addresses.is_empty() { + known_peers.push(own_info) + } + known_peers + .into_iter() + .map(|info| { + // SignedNodeInfo with fake default signatures will only work if + // signature verification is disabled. + SignedNodeInfo::new_from_data_and_sig( + info, + Ed25519Signature::default(), + ) + }) + .collect() + }, + ) + } }) .pipe(futures::stream::iter) .buffer_unordered(config.peers_to_query()) @@ -448,13 +553,14 @@ async fn query_connected_peers_for_their_known_peers( .collect::>() .await; - update_known_peers(state, metrics, found_peers, allowlisted_peers); + update_known_peers(config, state, metrics, found_peers, allowlisted_peers); } fn update_known_peers( + config: Arc, state: Arc>, metrics: Metrics, - found_peers: Vec, + found_peers: Vec, allowlisted_peers: Arc>>, ) { use std::collections::hash_map::Entry; @@ -462,25 +568,61 @@ fn update_known_peers( let now_unix = now_unix(); let our_peer_id = state.read().unwrap().our_info.clone().unwrap().peer_id; let known_peers = &mut state.write().unwrap().known_peers; - for peer in found_peers { + // only take the first MAX_PEERS_TO_SEND peers + for peer_info in found_peers.into_iter().take(MAX_PEERS_TO_SEND) { // Skip peers whose timestamp is too far in the future from our clock // or that are too old - if peer.timestamp_ms > now_unix.saturating_add(30 * 1_000) // 30 seconds - || now_unix.saturating_sub(peer.timestamp_ms) > ONE_DAY_MILLISECONDS + if peer_info.timestamp_ms > now_unix.saturating_add(30 * 1_000) // 30 seconds + || now_unix.saturating_sub(peer_info.timestamp_ms) > ONE_DAY_MILLISECONDS { continue; } - if peer.peer_id == our_peer_id { + if peer_info.peer_id == our_peer_id { continue; } // If Peer is Private, and not in our allowlist, skip it. - if peer.access_type == AccessType::Private && !allowlisted_peers.contains_key(&peer.peer_id) + if peer_info.access_type == AccessType::Private + && !allowlisted_peers.contains_key(&peer_info.peer_id) { continue; } + // Skip entries that have too many addresses as a means to cap the size of a node's info + if peer_info.addresses.len() > MAX_ADDRESSES_PER_PEER { + continue; + } + + // verify that all addresses provided are valid anemo addresses + if !peer_info + .addresses + .iter() + .all(|addr| addr.len() < MAX_ADDRESS_LENGTH && addr.to_anemo_address().is_ok()) + { + continue; + } + if config.enable_node_info_signatures() { + let Ok(public_key) = Ed25519PublicKey::from_bytes(&peer_info.peer_id.0) else { + debug_fatal!( + // This should never happen. + "Failed to convert anemo PeerId {:?} to Ed25519PublicKey", + peer_info.peer_id + ); + continue; + }; + let msg = bcs::to_bytes(peer_info.data()).expect("BCS serialization should not fail"); + if let Err(e) = public_key.verify(&msg, peer_info.auth_sig()) { + info!( + "Discovery failed to verify signature for NodeInfo for peer {:?}: {e:?}", + peer_info.peer_id + ); + // TODO: consider denylisting the source of bad NodeInfo from future requests. + continue; + } + } + let peer = VerifiedSignedNodeInfo::new_from_verified(peer_info); + match known_peers.entry(peer.peer_id) { Entry::Occupied(mut o) => { if peer.timestamp_ms > o.get().timestamp_ms { diff --git a/crates/sui-network/src/discovery/server.rs b/crates/sui-network/src/discovery/server.rs index 9bf0314ff3a4e..1a1993273689a 100644 --- a/crates/sui-network/src/discovery/server.rs +++ b/crates/sui-network/src/discovery/server.rs @@ -1,10 +1,14 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use super::{Discovery, NodeInfo, State}; +use super::{Discovery, NodeInfo, SignedNodeInfo, State, MAX_PEERS_TO_SEND}; use anemo::{Request, Response}; +use rand::seq::IteratorRandom; use serde::{Deserialize, Serialize}; -use std::sync::{Arc, RwLock}; +use std::{ + collections::HashMap, + sync::{Arc, RwLock}, +}; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct GetKnownPeersResponse { @@ -12,6 +16,12 @@ pub struct GetKnownPeersResponse { pub known_peers: Vec, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct GetKnownPeersResponseV2 { + pub own_info: SignedNodeInfo, + pub known_peers: Vec, +} + pub(super) struct Server { pub(super) state: Arc>, } @@ -20,16 +30,74 @@ pub(super) struct Server { impl Discovery for Server { async fn get_known_peers( &self, - _request: Request<()>, + request: Request<()>, ) -> Result, anemo::rpc::Status> { + let resp = self.get_known_peers_v2(request).await?; + Ok(resp.map(|body| GetKnownPeersResponse { + own_info: body.own_info.into_data(), + known_peers: body + .known_peers + .into_iter() + .map(|e| e.into_data()) + .collect(), + })) + } + + async fn get_known_peers_v2( + &self, + _request: Request<()>, + ) -> Result, anemo::rpc::Status> { let state = self.state.read().unwrap(); let own_info = state .our_info .clone() .ok_or_else(|| anemo::rpc::Status::internal("own_info has not been initialized yet"))?; - let known_peers = state.known_peers.values().cloned().collect(); - Ok(Response::new(GetKnownPeersResponse { + let known_peers = if state.known_peers.len() < MAX_PEERS_TO_SEND { + state + .known_peers + .values() + .map(|e| e.inner()) + .cloned() + .collect() + } else { + let mut rng = rand::thread_rng(); + // prefer returning peers that we are connected to as they are known-good + let mut known_peers = state + .connected_peers + .keys() + .filter_map(|peer_id| state.known_peers.get(peer_id)) + .map(|info| (info.peer_id, info)) + .choose_multiple(&mut rng, MAX_PEERS_TO_SEND) + .into_iter() + .collect::>(); + + if known_peers.len() <= MAX_PEERS_TO_SEND { + // Fill the remaining space with other peers, randomly sampling at most MAX_PEERS_TO_SEND + for info in state + .known_peers + .values() + // This randomly samples the iterator stream but the order of elements after + // sampling may not be random, this is ok though since we're just trying to do + // best-effort on sharing info of peers we haven't connected with ourselves. + .choose_multiple(&mut rng, MAX_PEERS_TO_SEND) + { + if known_peers.len() >= MAX_PEERS_TO_SEND { + break; + } + + known_peers.insert(info.peer_id, info); + } + } + + known_peers + .into_values() + .map(|e| e.inner()) + .cloned() + .collect() + }; + + Ok(Response::new(GetKnownPeersResponseV2 { own_info, known_peers, })) diff --git a/crates/sui-network/src/discovery/tests.rs b/crates/sui-network/src/discovery/tests.rs index 7d5fcbcdbfa19..01704e7600f9d 100644 --- a/crates/sui-network/src/discovery/tests.rs +++ b/crates/sui-network/src/discovery/tests.rs @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use super::*; -use crate::utils::{build_network, build_network_with_anemo_config}; +use crate::utils::{build_network_and_key, build_network_with_anemo_config}; use anemo::types::PeerAffinity; use anemo::Result; use fastcrypto::ed25519::Ed25519PublicKey; @@ -28,7 +28,10 @@ async fn get_known_peers() -> Result<()> { timestamp_ms: now_unix(), access_type: AccessType::Public, }; - state.write().unwrap().our_info = Some(our_info.clone()); + state.write().unwrap().our_info = Some(SignedNodeInfo::new_from_data_and_sig( + our_info.clone(), + Ed25519Signature::default(), + )); let response = server .get_known_peers(Request::new(())) .await @@ -44,11 +47,13 @@ async fn get_known_peers() -> Result<()> { timestamp_ms: now_unix(), access_type: AccessType::Public, }; - state - .write() - .unwrap() - .known_peers - .insert(other_peer.peer_id, other_peer.clone()); + state.write().unwrap().known_peers.insert( + other_peer.peer_id, + VerifiedSignedNodeInfo::new_unchecked(SignedNodeInfo::new_from_data_and_sig( + other_peer.clone(), + Ed25519Signature::default(), + )), + ); let response = server .get_known_peers(Request::new(())) .await @@ -62,10 +67,13 @@ async fn get_known_peers() -> Result<()> { #[tokio::test] async fn make_connection_to_seed_peer() -> Result<()> { - let config = P2pConfig::default(); + let config = P2pConfig::default().set_discovery_config(DiscoveryConfig { + enable_node_info_signatures: Some(true), + ..DiscoveryConfig::default() + }); let (builder, server) = Builder::new(create_test_channel().1).config(config).build(); - let network_1 = build_network(|router| router.add_rpc_service(server)); - let (_event_loop_1, _handle_1) = builder.build(network_1.clone()); + let (network_1, key_1) = build_network_and_key(|router| router.add_rpc_service(server)); + let (_event_loop_1, _handle_1) = builder.build(network_1.clone(), key_1); let mut config = P2pConfig::default(); config.seed_peers.push(SeedPeer { @@ -73,8 +81,8 @@ async fn make_connection_to_seed_peer() -> Result<()> { address: format!("/dns/localhost/udp/{}", network_1.local_addr().port()).parse()?, }); let (builder, server) = Builder::new(create_test_channel().1).config(config).build(); - let network_2 = build_network(|router| router.add_rpc_service(server)); - let (mut event_loop_2, _handle_2) = builder.build(network_2.clone()); + let (network_2, key_2) = build_network_and_key(|router| router.add_rpc_service(server)); + let (mut event_loop_2, _handle_2) = builder.build(network_2.clone(), key_2); let (mut subscriber_1, _) = network_1.subscribe()?; let (mut subscriber_2, _) = network_2.subscribe()?; @@ -95,10 +103,13 @@ async fn make_connection_to_seed_peer() -> Result<()> { #[tokio::test] async fn make_connection_to_seed_peer_with_peer_id() -> Result<()> { - let config = P2pConfig::default(); + let config = P2pConfig::default().set_discovery_config(DiscoveryConfig { + enable_node_info_signatures: Some(true), + ..DiscoveryConfig::default() + }); let (builder, server) = Builder::new(create_test_channel().1).config(config).build(); - let network_1 = build_network(|router| router.add_rpc_service(server)); - let (_event_loop_1, _handle_1) = builder.build(network_1.clone()); + let (network_1, key_1) = build_network_and_key(|router| router.add_rpc_service(server)); + let (_event_loop_1, _handle_1) = builder.build(network_1.clone(), key_1); let mut config = P2pConfig::default(); config.seed_peers.push(SeedPeer { @@ -106,8 +117,8 @@ async fn make_connection_to_seed_peer_with_peer_id() -> Result<()> { address: format!("/dns/localhost/udp/{}", network_1.local_addr().port()).parse()?, }); let (builder, server) = Builder::new(create_test_channel().1).config(config).build(); - let network_2 = build_network(|router| router.add_rpc_service(server)); - let (mut event_loop_2, _handle_2) = builder.build(network_2.clone()); + let (network_2, key_2) = build_network_and_key(|router| router.add_rpc_service(server)); + let (mut event_loop_2, _handle_2) = builder.build(network_2.clone(), key_2); let (mut subscriber_1, _) = network_1.subscribe()?; let (mut subscriber_2, _) = network_2.subscribe()?; @@ -129,10 +140,13 @@ async fn make_connection_to_seed_peer_with_peer_id() -> Result<()> { #[tokio::test(flavor = "current_thread", start_paused = true)] async fn three_nodes_can_connect_via_discovery() -> Result<()> { // Setup the peer that will be the seed for the other two - let config = P2pConfig::default(); + let config = P2pConfig::default().set_discovery_config(DiscoveryConfig { + enable_node_info_signatures: Some(true), + ..DiscoveryConfig::default() + }); let (builder, server) = Builder::new(create_test_channel().1).config(config).build(); - let network_1 = build_network(|router| router.add_rpc_service(server)); - let (event_loop_1, _handle_1) = builder.build(network_1.clone()); + let (network_1, key_1) = build_network_and_key(|router| router.add_rpc_service(server)); + let (event_loop_1, _handle_1) = builder.build(network_1.clone(), key_1); let mut config = P2pConfig::default(); config.seed_peers.push(SeedPeer { @@ -142,15 +156,15 @@ async fn three_nodes_can_connect_via_discovery() -> Result<()> { let (builder, server) = Builder::new(create_test_channel().1) .config(config.clone()) .build(); - let network_2 = build_network(|router| router.add_rpc_service(server)); - let (mut event_loop_2, _handle_2) = builder.build(network_2.clone()); + let (network_2, key_2) = build_network_and_key(|router| router.add_rpc_service(server)); + let (mut event_loop_2, _handle_2) = builder.build(network_2.clone(), key_2); // Set an external_address address for node 2 so that it can share its address event_loop_2.config.external_address = Some(format!("/dns/localhost/udp/{}", network_2.local_addr().port()).parse()?); let (builder, server) = Builder::new(create_test_channel().1).config(config).build(); - let network_3 = build_network(|router| router.add_rpc_service(server)); - let (event_loop_3, _handle_3) = builder.build(network_3.clone()); + let (network_3, key_3) = build_network_and_key(|router| router.add_rpc_service(server)); + let (event_loop_3, _handle_3) = builder.build(network_3.clone(), key_3); let (mut subscriber_1, _) = network_1.subscribe()?; let (mut subscriber_2, _) = network_2.subscribe()?; @@ -191,18 +205,21 @@ async fn three_nodes_can_connect_via_discovery() -> Result<()> { } #[tokio::test(flavor = "current_thread", start_paused = true)] -async fn peers_are_added_from_reocnfig_channel() -> Result<()> { +async fn peers_are_added_from_reconfig_channel() -> Result<()> { let (tx_1, rx_1) = create_test_channel(); - let config = P2pConfig::default(); + let config = P2pConfig::default().set_discovery_config(DiscoveryConfig { + enable_node_info_signatures: Some(true), + ..DiscoveryConfig::default() + }); let (builder, server) = Builder::new(rx_1).config(config.clone()).build(); - let network_1 = build_network(|router| router.add_rpc_service(server)); - let (event_loop_1, _handle_1) = builder.build(network_1.clone()); + let (network_1, key_1) = build_network_and_key(|router| router.add_rpc_service(server)); + let (event_loop_1, _handle_1) = builder.build(network_1.clone(), key_1); let (builder, server) = Builder::new(create_test_channel().1) .config(config.clone()) .build(); - let network_2 = build_network(|router| router.add_rpc_service(server)); - let (event_loop_2, _handle_2) = builder.build(network_2.clone()); + let (network_2, key_2) = build_network_and_key(|router| router.add_rpc_service(server)); + let (event_loop_2, _handle_2) = builder.build(network_2.clone(), key_2); let (mut subscriber_1, _) = network_1.subscribe()?; let (mut subscriber_2, _) = network_2.subscribe()?; @@ -282,6 +299,7 @@ async fn test_access_types() { let default_discovery_config = DiscoveryConfig { target_concurrent_connections: Some(100), interval_period_ms: Some(1000), + enable_node_info_signatures: Some(true), ..Default::default() }; let default_p2p_config = P2pConfig { @@ -292,11 +310,12 @@ async fn test_access_types() { target_concurrent_connections: Some(100), interval_period_ms: Some(1000), access_type: Some(AccessType::Private), + enable_node_info_signatures: Some(true), ..Default::default() }; // None 1, public - let (builder_1, network_1) = set_up_network(default_p2p_config.clone()); + let (builder_1, network_1, key_1) = set_up_network(default_p2p_config.clone()); let mut config = default_p2p_config.clone(); config.seed_peers.push(SeedPeer { @@ -307,16 +326,16 @@ async fn test_access_types() { }); // Node 2, public, seed: Node 1, allowlist: Node 7, Node 8 - let (mut builder_2, network_2) = set_up_network(config.clone()); + let (mut builder_2, network_2, key_2) = set_up_network(config.clone()); // Node 3, private, seed: Node 1 - let (mut builder_3, network_3) = set_up_network(config.clone()); + let (mut builder_3, network_3, key_3) = set_up_network(config.clone()); // Node 4, private, allowlist: Node 3, 5, and 6 - let (mut builder_4, network_4) = set_up_network(P2pConfig::default()); + let (mut builder_4, network_4, key_4) = set_up_network(P2pConfig::default()); // Node 5, private, allowlisted: Node 3 and Node 4 - let (builder_5, network_5) = { + let (builder_5, network_5, key_5) = { let mut private_discovery_config = default_private_discovery_config.clone(); private_discovery_config.allowlisted_peers = vec![ // Intitially 5 does not know how to contact 3 or 4. @@ -327,7 +346,7 @@ async fn test_access_types() { }; // Node 6, private, allowlisted: Node 4 - let (builder_6, network_6) = { + let (builder_6, network_6, key_6) = { let mut private_discovery_config = default_private_discovery_config.clone(); private_discovery_config.allowlisted_peers = vec![local_allowlisted_peer( network_4.peer_id(), @@ -354,15 +373,15 @@ async fn test_access_types() { builder_4.config.discovery = Some(private_discovery_config); // Node 7, private, allowlisted: Node 2, Node 8 - let (mut builder_7, network_7) = set_up_network( + let (mut builder_7, network_7, key_7) = set_up_network( P2pConfig::default().set_discovery_config(default_private_discovery_config.clone()), ); // Node 9, public - let (builder_9, network_9) = set_up_network(default_p2p_config.clone()); + let (builder_9, network_9, key_9) = set_up_network(default_p2p_config.clone()); // Node 8, private, allowlisted: Node 7, Node 9 - let (builder_8, network_8) = { + let (builder_8, network_8, key_8) = { let mut private_discovery_config = default_private_discovery_config.clone(); private_discovery_config.allowlisted_peers = vec![ local_allowlisted_peer(network_7.peer_id(), Some(network_7.local_addr().port())), @@ -392,7 +411,7 @@ async fn test_access_types() { builder_7.config.discovery = Some(private_discovery_config); // Node 10, private, seed: 9 - let (builder_10, network_10) = { + let (builder_10, network_10, key_10) = { let mut p2p_config = default_p2p_config.clone(); p2p_config.seed_peers.push(SeedPeer { peer_id: Some(network_9.peer_id()), @@ -405,7 +424,7 @@ async fn test_access_types() { }; // Node 11, private, seed: 1, allow: 7, 8 - let (builder_11, network_11) = { + let (builder_11, network_11, key_11) = { let mut p2p_config = default_p2p_config.clone(); p2p_config.seed_peers.push(SeedPeer { peer_id: Some(network_1.peer_id()), @@ -422,17 +441,19 @@ async fn test_access_types() { set_up_network(p2p_config) }; - let (event_loop_1, _handle_1, state_1) = start_network(builder_1, network_1.clone()); - let (event_loop_2, _handle_2, state_2) = start_network(builder_2, network_2.clone()); - let (event_loop_3, _handle_3, state_3) = start_network(builder_3, network_3.clone()); - let (event_loop_4, _handle_4, state_4) = start_network(builder_4, network_4.clone()); - let (event_loop_5, _handle_5, state_5) = start_network(builder_5, network_5.clone()); - let (event_loop_6, _handle_6, state_6) = start_network(builder_6, network_6.clone()); - let (event_loop_7, _handle_7, state_7) = start_network(builder_7, network_7.clone()); - let (event_loop_8, _handle_8, state_8) = start_network(builder_8, network_8.clone()); - let (event_loop_9, _handle_9, state_9) = start_network(builder_9, network_9.clone()); - let (event_loop_10, _handle_10, state_10) = start_network(builder_10, network_10.clone()); - let (event_loop_11, _handle_11, state_11) = start_network(builder_11, network_11.clone()); + let (event_loop_1, _handle_1, state_1) = start_network(builder_1, network_1.clone(), key_1); + let (event_loop_2, _handle_2, state_2) = start_network(builder_2, network_2.clone(), key_2); + let (event_loop_3, _handle_3, state_3) = start_network(builder_3, network_3.clone(), key_3); + let (event_loop_4, _handle_4, state_4) = start_network(builder_4, network_4.clone(), key_4); + let (event_loop_5, _handle_5, state_5) = start_network(builder_5, network_5.clone(), key_5); + let (event_loop_6, _handle_6, state_6) = start_network(builder_6, network_6.clone(), key_6); + let (event_loop_7, _handle_7, state_7) = start_network(builder_7, network_7.clone(), key_7); + let (event_loop_8, _handle_8, state_8) = start_network(builder_8, network_8.clone(), key_8); + let (event_loop_9, _handle_9, state_9) = start_network(builder_9, network_9.clone(), key_9); + let (event_loop_10, _handle_10, state_10) = + start_network(builder_10, network_10.clone(), key_10); + let (event_loop_11, _handle_11, state_11) = + start_network(builder_11, network_11.clone(), key_11); // Start all the event loops tokio::spawn(event_loop_1.start()); @@ -684,21 +705,22 @@ fn local_allowlisted_peer(peer_id: PeerId, port: Option) -> AllowlistedPeer } } -fn set_up_network(p2p_config: P2pConfig) -> (UnstartedDiscovery, Network) { +fn set_up_network(p2p_config: P2pConfig) -> (UnstartedDiscovery, Network, NetworkKeyPair) { let anemo_config = p2p_config.anemo_config.clone().unwrap_or_default(); let (builder, server) = Builder::new(create_test_channel().1) .config(p2p_config) .build(); - let network = + let (network, keypair) = build_network_with_anemo_config(|router| router.add_rpc_service(server), anemo_config); - (builder, network) + (builder, network, keypair) } fn start_network( builder: UnstartedDiscovery, network: Network, + keypair: NetworkKeyPair, ) -> (DiscoveryEventLoop, Handle, Arc>) { - let (mut event_loop, handle) = builder.build(network.clone()); + let (mut event_loop, handle) = builder.build(network.clone(), keypair); event_loop.config.external_address = Some( format!("/dns/localhost/udp/{}", network.local_addr().port()) .parse() diff --git a/crates/sui-network/src/state_sync/mod.rs b/crates/sui-network/src/state_sync/mod.rs index 8a890cce110f8..5243bc5888dbe 100644 --- a/crates/sui-network/src/state_sync/mod.rs +++ b/crates/sui-network/src/state_sync/mod.rs @@ -296,13 +296,18 @@ impl PeerBalancer { .unwrap() .peers_on_same_chain() // Filter out any peers who we aren't connected with. - .filter_map(|(peer_id, info)| network.peer(*peer_id).map(|peer| (peer, *info))) + .filter_map(|(peer_id, info)| { + network + .peer(*peer_id) + .map(|peer| (peer.connection_rtt(), peer, *info)) + }) .collect(); - peers.sort_by(|(peer_a, _), (peer_b, _)| { - peer_a.connection_rtt().cmp(&peer_b.connection_rtt()) - }); + peers.sort_by(|(rtt_a, _, _), (rtt_b, _, _)| rtt_a.cmp(rtt_b)); Self { - peers: peers.into(), + peers: peers + .into_iter() + .map(|(_, peer, info)| (peer, info)) + .collect(), requested_checkpoint: None, request_type, } diff --git a/crates/sui-network/src/utils.rs b/crates/sui-network/src/utils.rs index 5a6df36ac40dc..e8f04939bc3dc 100644 --- a/crates/sui-network/src/utils.rs +++ b/crates/sui-network/src/utils.rs @@ -3,6 +3,13 @@ #[cfg(test)] pub fn build_network(f: impl FnOnce(anemo::Router) -> anemo::Router) -> anemo::Network { + build_network_impl(f, None).0 +} + +#[cfg(test)] +pub fn build_network_and_key( + f: impl FnOnce(anemo::Router) -> anemo::Router, +) -> (anemo::Network, sui_types::crypto::NetworkKeyPair) { build_network_impl(f, None) } @@ -10,7 +17,7 @@ pub fn build_network(f: impl FnOnce(anemo::Router) -> anemo::Router) -> anemo::N pub fn build_network_with_anemo_config( f: impl FnOnce(anemo::Router) -> anemo::Router, anemo_config: anemo::Config, -) -> anemo::Network { +) -> (anemo::Network, sui_types::crypto::NetworkKeyPair) { build_network_impl(f, Some(anemo_config)) } @@ -18,10 +25,13 @@ pub fn build_network_with_anemo_config( fn build_network_impl( f: impl FnOnce(anemo::Router) -> anemo::Router, anemo_config: Option, -) -> anemo::Network { +) -> (anemo::Network, sui_types::crypto::NetworkKeyPair) { + use fastcrypto::traits::KeyPair; + + let keypair = sui_types::crypto::NetworkKeyPair::generate(&mut rand::thread_rng()); let router = f(anemo::Router::new()); let network = anemo::Network::bind("localhost:0") - .private_key(random_key()) + .private_key(keypair.copy().private().0.to_bytes()) .config(anemo_config.unwrap_or_default()) .server_name("test") .start(router) @@ -32,13 +42,5 @@ fn build_network_impl( network.local_addr(), network.peer_id(), ); - network -} - -#[cfg(test)] -fn random_key() -> [u8; 32] { - let mut rng = rand::thread_rng(); - let mut bytes = [0u8; 32]; - rand::RngCore::fill_bytes(&mut rng, &mut bytes[..]); - bytes + (network, keypair) } diff --git a/crates/sui-node/Cargo.toml b/crates/sui-node/Cargo.toml index a9342adbb5a7c..636761b27341b 100644 --- a/crates/sui-node/Cargo.toml +++ b/crates/sui-node/Cargo.toml @@ -18,6 +18,7 @@ anyhow.workspace = true base64.workspace = true bcs.workspace = true clap.workspace = true +consensus-core.workspace = true prometheus.workspace = true tokio = { workspace = true, features = ["full"] } tracing.workspace = true diff --git a/crates/sui-node/src/lib.rs b/crates/sui-node/src/lib.rs index 80b91bb52c3e6..e0fc567bffda6 100644 --- a/crates/sui-node/src/lib.rs +++ b/crates/sui-node/src/lib.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use anemo::Network; +use anemo::PeerId; use anemo_tower::callback::CallbackLayer; use anemo_tower::trace::DefaultMakeSpan; use anemo_tower::trace::DefaultOnFailure; @@ -57,8 +58,6 @@ pub use handle::SuiNodeHandle; use mysten_metrics::{spawn_monitored_task, RegistryService}; use mysten_network::server::ServerBuilder; use mysten_service::server_timing::server_timing_middleware; -use narwhal_network::metrics::MetricsMakeCallbackHandler; -use narwhal_network::metrics::{NetworkConnectionMetrics, NetworkMetrics}; use sui_archival::reader::ArchiveReaderBalancer; use sui_archival::writer::ArchiveWriter; use sui_config::node::{DBCheckpointConfig, RunWithRange}; @@ -90,6 +89,7 @@ use sui_core::epoch::committee_store::CommitteeStore; use sui_core::epoch::consensus_store_pruner::ConsensusStorePruner; use sui_core::epoch::epoch_metrics::EpochMetrics; use sui_core::epoch::reconfiguration::ReconfigurationInitiator; +use sui_core::jsonrpc_index::IndexStore; use sui_core::module_cache_metrics::ResolverMetrics; use sui_core::overload_monitor::overload_monitor; use sui_core::rest_index::RestIndexStore; @@ -122,7 +122,7 @@ use sui_storage::{ key_value_store::{FallbackTransactionKVStore, TransactionKeyValueStore}, key_value_store_metrics::KeyValueStoreMetrics, }; -use sui_storage::{FileCompression, IndexStore, StorageFormat}; +use sui_storage::{FileCompression, StorageFormat}; use sui_types::base_types::{AuthorityName, EpochId}; use sui_types::committee::Committee; use sui_types::crypto::KeypairTraits; @@ -157,11 +157,19 @@ pub struct ValidatorComponents { checkpoint_metrics: Arc, sui_tx_validator_metrics: Arc, } +pub struct P2pComponents { + p2p_network: Network, + known_peers: HashMap, + discovery_handle: discovery::Handle, + state_sync_handle: state_sync::Handle, + randomness_handle: randomness::Handle, +} #[cfg(msim)] mod simulator { - use super::*; use std::sync::atomic::AtomicBool; + + use super::*; pub(super) struct SimState { pub sim_node: sui_simulator::runtime::NodeHandle, pub sim_safe_mode_expected: AtomicBool, @@ -209,14 +217,14 @@ mod simulator { } } -#[cfg(msim)] -use simulator::*; - #[cfg(msim)] pub use simulator::set_jwk_injector; -use sui_core::consensus_handler::ConsensusHandlerInitializer; -use sui_core::safe_client::SafeClientMetricsBase; -use sui_core::validator_tx_finalizer::ValidatorTxFinalizer; +#[cfg(msim)] +use simulator::*; +use sui_core::{ + consensus_handler::ConsensusHandlerInitializer, safe_client::SafeClientMetricsBase, + validator_tx_finalizer::ValidatorTxFinalizer, +}; use sui_types::execution_config_utils::to_binary_config; pub struct SuiNode { @@ -230,6 +238,7 @@ pub struct SuiNode { metrics: Arc, _discovery: discovery::Handle, + _connection_monitor_handle: consensus_core::ConnectionMonitorHandle, state_sync_handle: state_sync::Handle, randomness_handle: randomness::Handle, checkpoint_store: Arc, @@ -572,6 +581,7 @@ impl SuiNode { .protocol_config() .max_move_identifier_len_as_option(), config.remove_deprecated_tables, + &store, ))) } else { None @@ -611,16 +621,21 @@ impl SuiNode { .unwrap_or_default() .mailbox_capacity(), ); - let (p2p_network, discovery_handle, state_sync_handle, randomness_handle) = - Self::create_p2p_network( - &config, - state_sync_store.clone(), - chain_identifier, - trusted_peer_change_rx, - archive_readers.clone(), - randomness_tx, - &prometheus_registry, - )?; + let P2pComponents { + p2p_network, + known_peers, + discovery_handle, + state_sync_handle, + randomness_handle, + } = Self::create_p2p_network( + &config, + state_sync_store.clone(), + chain_identifier, + trusted_peer_change_rx, + archive_readers.clone(), + randomness_tx, + &prometheus_registry, + )?; // We must explicitly send this instead of relying on the initial value to trigger // watch value change, so that state-sync is able to process it. @@ -767,21 +782,21 @@ impl SuiNode { .epoch_start_state() .get_authority_names_to_peer_ids(); - let network_connection_metrics = - NetworkConnectionMetrics::new("sui", ®istry_service.default_registry()); + let network_connection_metrics = consensus_core::QuinnConnectionMetrics::new( + "sui", + ®istry_service.default_registry(), + ); let authority_names_to_peer_ids = ArcSwap::from_pointee(authority_names_to_peer_ids); - let (_connection_monitor_handle, connection_statuses) = - narwhal_network::connectivity::ConnectionMonitor::spawn( - p2p_network.downgrade(), - network_connection_metrics, - HashMap::new(), - None, - ); + let connection_monitor_handle = consensus_core::AnemoConnectionMonitor::spawn( + p2p_network.downgrade(), + Arc::new(network_connection_metrics), + known_peers, + ); let connection_monitor_status = ConnectionMonitorStatus { - connection_statuses, + connection_statuses: connection_monitor_handle.connection_statuses(), authority_names_to_peer_ids, }; @@ -824,6 +839,7 @@ impl SuiNode { metrics: sui_node_metrics, _discovery: discovery_handle, + _connection_monitor_handle: connection_monitor_handle, state_sync_handle, randomness_handle, checkpoint_store, @@ -1023,12 +1039,7 @@ impl SuiNode { archive_readers: ArchiveReaderBalancer, randomness_tx: mpsc::Sender<(EpochId, RandomnessRound, Vec)>, prometheus_registry: &Registry, - ) -> Result<( - Network, - discovery::Handle, - state_sync::Handle, - randomness::Handle, - )> { + ) -> Result { let (state_sync, state_sync_server) = state_sync::Builder::new() .config(config.p2p_config.state_sync.clone().unwrap_or_default()) .store(state_sync_store) @@ -1040,6 +1051,18 @@ impl SuiNode { .config(config.p2p_config.clone()) .build(); + let discovery_config = config.p2p_config.discovery.clone().unwrap_or_default(); + let known_peers: HashMap = discovery_config + .allowlisted_peers + .clone() + .into_iter() + .map(|ap| (ap.peer_id, "allowlisted_peer".to_string())) + .chain(config.p2p_config.seed_peers.iter().filter_map(|peer| { + peer.peer_id + .map(|peer_id| (peer_id, "seed_peer".to_string())) + })) + .collect(); + let (randomness, randomness_router) = randomness::Builder::new(config.protocol_public_key(), randomness_tx) .config(config.p2p_config.randomness.clone().unwrap_or_default()) @@ -1053,9 +1076,9 @@ impl SuiNode { let routes = routes.merge(randomness_router); let inbound_network_metrics = - NetworkMetrics::new("sui", "inbound", prometheus_registry); + consensus_core::NetworkRouteMetrics::new("sui", "inbound", prometheus_registry); let outbound_network_metrics = - NetworkMetrics::new("sui", "outbound", prometheus_registry); + consensus_core::NetworkRouteMetrics::new("sui", "outbound", prometheus_registry); let service = ServiceBuilder::new() .layer( @@ -1063,10 +1086,12 @@ impl SuiNode { .make_span_with(DefaultMakeSpan::new().level(tracing::Level::INFO)) .on_failure(DefaultOnFailure::new().level(tracing::Level::WARN)), ) - .layer(CallbackLayer::new(MetricsMakeCallbackHandler::new( - Arc::new(inbound_network_metrics), - config.p2p_config.excessive_message_size(), - ))) + .layer(CallbackLayer::new( + consensus_core::MetricsMakeCallbackHandler::new( + Arc::new(inbound_network_metrics), + config.p2p_config.excessive_message_size(), + ), + )) .service(routes); let outbound_layer = ServiceBuilder::new() @@ -1075,10 +1100,12 @@ impl SuiNode { .make_span_with(DefaultMakeSpan::new().level(tracing::Level::INFO)) .on_failure(DefaultOnFailure::new().level(tracing::Level::WARN)), ) - .layer(CallbackLayer::new(MetricsMakeCallbackHandler::new( - Arc::new(outbound_network_metrics), - config.p2p_config.excessive_message_size(), - ))) + .layer(CallbackLayer::new( + consensus_core::MetricsMakeCallbackHandler::new( + Arc::new(outbound_network_metrics), + config.p2p_config.excessive_message_size(), + ), + )) .into_inner(); let mut anemo_config = config.p2p_config.anemo_config.clone().unwrap_or_default(); @@ -1141,16 +1168,18 @@ impl SuiNode { network }; - let discovery_handle = discovery.start(p2p_network.clone()); + let discovery_handle = + discovery.start(p2p_network.clone(), config.network_key_pair().copy()); let state_sync_handle = state_sync.start(p2p_network.clone()); let randomness_handle = randomness.start(p2p_network.clone()); - Ok(( + Ok(P2pComponents { p2p_network, + known_peers, discovery_handle, state_sync_handle, randomness_handle, - )) + }) } async fn construct_validator_components( @@ -1692,7 +1721,7 @@ impl SuiNode { consensus_store_pruner.prune(next_epoch).await; if self.state.is_validator(&new_epoch_store) { - // Only restart Narwhal if this node is still a validator in the new epoch. + // Only restart consensus if this node is still a validator in the new epoch. Some( Self::start_epoch_specific_validator_components( &self.config, @@ -2042,6 +2071,10 @@ pub async fn build_http_server( software_version, ); + if let Some(config) = config.rest.clone() { + rest_service.with_config(config); + } + rest_service.with_metrics(RestMetrics::new(prometheus_registry)); if let Some(transaction_orchestrator) = transaction_orchestrator { diff --git a/crates/sui-node/src/metrics.rs b/crates/sui-node/src/metrics.rs index f2d0f522a749a..f9bae0a8c8c00 100644 --- a/crates/sui-node/src/metrics.rs +++ b/crates/sui-node/src/metrics.rs @@ -6,7 +6,6 @@ use prometheus::{ register_histogram_vec_with_registry, register_int_counter_vec_with_registry, register_int_gauge_vec_with_registry, HistogramVec, IntCounterVec, IntGaugeVec, Registry, }; -use tokio::time::sleep; use std::time::Duration; use sui_network::tonic::Code; @@ -45,15 +44,22 @@ pub fn start_metrics_push_task(config: &sui_config::NodeConfig, registry: Regist let mut interval = tokio::time::interval(interval); interval.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip); + let mut errors = 0; loop { interval.tick().await; - // Retry pushing metrics if there is an error. - while let Err(error) = push_metrics(&client, &url, ®istry).await { - tracing::warn!("unable to push metrics: {error}; new client will be created"); - sleep(Duration::from_secs(1)).await; + if let Err(error) = push_metrics(&client, &url, ®istry).await { + errors += 1; + if errors >= 10 { + // If we hit 10 failures in a row, start logging errors. + tracing::error!("unable to push metrics: {error}; new client will be created"); + } else { + tracing::warn!("unable to push metrics: {error}; new client will be created"); + } // aggressively recreate our client connection if we hit an error client = MetricsPushClient::new(config_copy.network_key_pair().copy()); + } else { + errors = 0; } } }); diff --git a/crates/sui-open-rpc/spec/openrpc.json b/crates/sui-open-rpc/spec/openrpc.json index 9a6367c5f92a0..251b8df3073d9 100644 --- a/crates/sui-open-rpc/spec/openrpc.json +++ b/crates/sui-open-rpc/spec/openrpc.json @@ -12,7 +12,7 @@ "name": "Apache-2.0", "url": "https://raw.githubusercontent.com/MystenLabs/sui/main/LICENSE" }, - "version": "1.34.0" + "version": "1.36.1" }, "methods": [ { @@ -1293,7 +1293,7 @@ "name": "Result", "value": { "minSupportedProtocolVersion": "1", - "maxSupportedProtocolVersion": "60", + "maxSupportedProtocolVersion": "65", "protocolVersion": "6", "featureFlags": { "accept_zklogin_in_multisig": false, @@ -1326,6 +1326,7 @@ "loaded_child_object_format_type": false, "loaded_child_objects_fixed": true, "missing_type_is_compatibility_error": true, + "mysticeti_fastpath": false, "mysticeti_leader_scoring_and_schedule": false, "mysticeti_use_committed_subdag_digest": false, "narwhal_certificate_v2": false, @@ -1341,6 +1342,7 @@ "recompute_has_public_transfer_in_execution": false, "record_consensus_determined_version_assignments_in_prologue": false, "reject_mutable_random_on_entry_functions": false, + "relocate_event_module": false, "reshare_at_same_initial_version": false, "resolve_abort_locations_to_package_id": false, "rethrow_serialization_type_layout_errors": false, @@ -1421,6 +1423,7 @@ "config_read_setting_impl_cost_base": null, "config_read_setting_impl_cost_per_byte": null, "consensus_bad_nodes_stake_threshold": null, + "consensus_gc_depth": null, "consensus_max_num_transactions_in_block": null, "consensus_max_transaction_size_bytes": null, "consensus_max_transactions_in_block_bytes": null, @@ -1589,6 +1592,7 @@ "u64": "2" }, "execution_version": null, + "gas_budget_based_txn_cost_cap_factor": null, "gas_model_version": { "u64": "5" }, @@ -6104,40 +6108,57 @@ "EventFilter": { "oneOf": [ { - "description": "Query by sender address.", + "description": "Return all events.", "type": "object", "required": [ - "Sender" + "All" ], "properties": { - "Sender": { - "$ref": "#/components/schemas/SuiAddress" + "All": { + "type": "array", + "maxItems": 0 } }, "additionalProperties": false }, { - "description": "Return events emitted by the given transaction.", + "description": "Return events that match any of the given filters. Only supported on event subscriptions.", "type": "object", "required": [ - "Transaction" + "Any" ], "properties": { - "Transaction": { - "$ref": "#/components/schemas/TransactionDigest" + "Any": { + "type": "array", + "items": { + "$ref": "#/components/schemas/EventFilter" + } } }, "additionalProperties": false }, { - "description": "Return events emitted in a specified Package.", + "description": "Query by sender address.", "type": "object", "required": [ - "Package" + "Sender" ], "properties": { - "Package": { - "$ref": "#/components/schemas/ObjectID" + "Sender": { + "$ref": "#/components/schemas/SuiAddress" + } + }, + "additionalProperties": false + }, + { + "description": "Return events emitted by the given transaction.", + "type": "object", + "required": [ + "Transaction" + ], + "properties": { + "Transaction": { + "$ref": "#/components/schemas/TransactionDigest" } }, "additionalProperties": false @@ -6217,28 +6238,6 @@ }, "additionalProperties": false }, - { - "type": "object", - "required": [ - "MoveEventField" - ], - "properties": { - "MoveEventField": { - "type": "object", - "required": [ - "path", - "value" - ], - "properties": { - "path": { - "type": "string" - }, - "value": true - } - } - }, - "additionalProperties": false - }, { "description": "Return events emitted in [start_time, end_time] interval", "type": "object", @@ -6273,85 +6272,11 @@ } }, "additionalProperties": false - }, - { - "type": "object", - "required": [ - "All" - ], - "properties": { - "All": { - "type": "array", - "items": { - "$ref": "#/components/schemas/EventFilter" - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "Any" - ], - "properties": { - "Any": { - "type": "array", - "items": { - "$ref": "#/components/schemas/EventFilter" - } - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "And" - ], - "properties": { - "And": { - "type": "array", - "items": [ - { - "$ref": "#/components/schemas/EventFilter" - }, - { - "$ref": "#/components/schemas/EventFilter" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "Or" - ], - "properties": { - "Or": { - "type": "array", - "items": [ - { - "$ref": "#/components/schemas/EventFilter" - }, - { - "$ref": "#/components/schemas/EventFilter" - } - ], - "maxItems": 2, - "minItems": 2 - } - }, - "additionalProperties": false } ] }, "EventID": { - "description": "Unique ID of a Sui Event, the ID is a combination of tx seq number and event seq number, the ID is local to this particular fullnode and will be different from other fullnode.", + "description": "Unique ID of a Sui Event, the ID is a combination of transaction digest and event seq number.", "type": "object", "required": [ "eventSeq", @@ -10860,6 +10785,19 @@ }, "additionalProperties": false }, + { + "description": "Query for transactions that touch this object.", + "type": "object", + "required": [ + "AffectedObject" + ], + "properties": { + "AffectedObject": { + "$ref": "#/components/schemas/ObjectID" + } + }, + "additionalProperties": false + }, { "description": "Query by sender address.", "type": "object", diff --git a/crates/sui-package-resolver/src/lib.rs b/crates/sui-package-resolver/src/lib.rs index 828e6ea96d8e4..3c21640cad7a5 100644 --- a/crates/sui-package-resolver/src/lib.rs +++ b/crates/sui-package-resolver/src/lib.rs @@ -348,6 +348,33 @@ impl Resolver { } impl Resolver { + /// The canonical form of a type refers to each type in terms of its defining package ID. This + /// function takes a non-canonical type and updates all its package IDs to the appropriate + /// defining ID. + /// + /// For every `package::module::datatype` in the input `tag`, `package` must be an object + /// on-chain, containing a move package that includes `module`, and that module must define the + /// `datatype`. In practice this means the input type `tag` can refer to types at or after + /// their defining IDs. + pub async fn canonical_type(&self, mut tag: TypeTag) -> Result { + let mut context = ResolutionContext::new(self.limits.as_ref()); + + // (1). Fetch all the information from this store that is necessary to relocate package IDs + // in the type. + context + .add_type_tag( + &mut tag, + &self.package_store, + /* visit_fields */ false, + /* visit_phantoms */ true, + ) + .await?; + + // (2). Use that information to relocate package IDs in the type. + context.canonicalize_type(&mut tag)?; + Ok(tag) + } + /// Return the type layout corresponding to the given type tag. The layout always refers to /// structs in terms of their defining ID (i.e. their package ID always points to the first /// package that introduced them). @@ -1349,6 +1376,36 @@ impl<'l> ResolutionContext<'l> { Ok(()) } + /// Translate runtime IDs in a type `tag` into defining IDs using only the information + /// contained in this context. Requires that the necessary information was added to the context + /// through calls to `add_type_tag`. + fn canonicalize_type(&self, tag: &mut TypeTag) -> Result<()> { + use TypeTag as T; + + match tag { + T::Signer => return Err(Error::UnexpectedSigner), + T::Address | T::Bool | T::U8 | T::U16 | T::U32 | T::U64 | T::U128 | T::U256 => { + /* nop */ + } + + T::Vector(tag) => self.canonicalize_type(tag.as_mut())?, + + T::Struct(s) => { + for tag in &mut s.type_params { + self.canonicalize_type(tag)?; + } + + // SAFETY: `add_type_tag` ensures `datatyps` has an element with this key. + let key = DatatypeRef::from(s.as_ref()); + let def = &self.datatypes[&key]; + + s.address = def.defining_id; + } + } + + Ok(()) + } + /// Translate a type `tag` into its layout using only the information contained in this context. /// Requires that the necessary information was added to the context through calls to /// `add_type_tag` and `add_signature` before being called. @@ -1404,7 +1461,7 @@ impl<'l> ResolutionContext<'l> { .type_params .iter() // Reduce the max depth because we know these type parameters will be nested - // wthin this struct. + // within this struct. .map(|tag| self.resolve_type_layout(tag, max_depth - 1)) .collect::>>()?; @@ -1737,9 +1794,90 @@ mod tests { format!("struct:\n{struct_layout:#}\n\nenum:\n{enum_layout:#}",) } + #[tokio::test] + async fn test_simple_canonical_type() { + let (_, cache) = package_cache([(1, build_package("a0"), a0_types())]); + let package_resolver = Resolver::new(cache); + + let input = type_("0xa0::m::T0"); + let expect = input.clone(); + let actual = package_resolver.canonical_type(input).await.unwrap(); + assert_eq!(expect, actual); + } + + #[tokio::test] + async fn test_upgraded_canonical_type() { + let (_, cache) = package_cache([ + (1, build_package("a0"), a0_types()), + (2, build_package("a1"), a1_types()), + ]); + + let package_resolver = Resolver::new(cache); + + let input = type_("0xa1::m::T3"); + let expect = input.clone(); + let actual = package_resolver.canonical_type(input).await.unwrap(); + assert_eq!(expect, actual); + } + + #[tokio::test] + async fn test_latest_canonical_type() { + let (_, cache) = package_cache([ + (1, build_package("a0"), a0_types()), + (2, build_package("a1"), a1_types()), + ]); + + let package_resolver = Resolver::new(cache); + + let input = type_("0xa1::m::T0"); + let expect = type_("0xa0::m::T0"); + let actual = package_resolver.canonical_type(input).await.unwrap(); + assert_eq!(expect, actual); + } + + #[tokio::test] + async fn test_type_param_canonical_type() { + let (_, cache) = package_cache([ + (1, build_package("a0"), a0_types()), + (2, build_package("a1"), a1_types()), + ]); + + let package_resolver = Resolver::new(cache); + + let input = type_("0xa1::m::T1<0xa1::m::T0, 0xa1::m::T3>"); + let expect = type_("0xa0::m::T1<0xa0::m::T0, 0xa1::m::T3>"); + let actual = package_resolver.canonical_type(input).await.unwrap(); + assert_eq!(expect, actual); + } + + #[tokio::test] + async fn test_canonical_err_package_too_old() { + let (_, cache) = package_cache([ + (1, build_package("a0"), a0_types()), + (2, build_package("a1"), a1_types()), + ]); + + let package_resolver = Resolver::new(cache); + + let input = type_("0xa0::m::T3"); + let err = package_resolver.canonical_type(input).await.unwrap_err(); + assert!(matches!(err, Error::DatatypeNotFound(_, _, _))); + } + + #[tokio::test] + async fn test_canonical_err_signer() { + let (_, cache) = package_cache([(1, build_package("a0"), a0_types())]); + + let package_resolver = Resolver::new(cache); + + let input = type_("0xa0::m::T1<0xa0::m::T0, signer>"); + let err = package_resolver.canonical_type(input).await.unwrap_err(); + assert!(matches!(err, Error::UnexpectedSigner)); + } + /// Layout for a type that only refers to base types or other types in the same module. #[tokio::test] - async fn test_simple_type() { + async fn test_simple_type_layout() { let (_, cache) = package_cache([(1, build_package("a0"), a0_types())]); let package_resolver = Resolver::new(cache); let struct_layout = package_resolver @@ -1755,7 +1893,7 @@ mod tests { /// A type that refers to types from other modules in the same package. #[tokio::test] - async fn test_cross_module() { + async fn test_cross_module_layout() { let (_, cache) = package_cache([(1, build_package("a0"), a0_types())]); let resolver = Resolver::new(cache); let struct_layout = resolver.type_layout(type_("0xa0::n::T0")).await.unwrap(); @@ -1765,7 +1903,7 @@ mod tests { /// A type that refers to types a different package. #[tokio::test] - async fn test_cross_package() { + async fn test_cross_package_layout() { let (_, cache) = package_cache([ (1, build_package("a0"), a0_types()), (1, build_package("b0"), b0_types()), @@ -1780,7 +1918,7 @@ mod tests { /// A type from an upgraded package, mixing structs defined in the original package and the /// upgraded package. #[tokio::test] - async fn test_upgraded_package() { + async fn test_upgraded_package_layout() { let (_, cache) = package_cache([ (1, build_package("a0"), a0_types()), (2, build_package("a1"), a1_types()), @@ -1795,7 +1933,7 @@ mod tests { /// A generic type instantiation where the type parameters are resolved relative to linkage /// contexts from different versions of the same package. #[tokio::test] - async fn test_multiple_linkage_contexts() { + async fn test_multiple_linkage_contexts_layout() { let (_, cache) = package_cache([ (1, build_package("a0"), a0_types()), (2, build_package("a1"), a1_types()), @@ -1818,7 +1956,7 @@ mod tests { /// type can be referred to using the ID of any package that declares it, rather than only the /// package that first declared it (whose ID is its defining ID). #[tokio::test] - async fn test_upgraded_package_non_defining_id() { + async fn test_upgraded_package_non_defining_id_layout() { let (_, cache) = package_cache([ (1, build_package("a0"), a0_types()), (2, build_package("a1"), a1_types()), @@ -1840,7 +1978,7 @@ mod tests { /// dependency on A from v1 to v2. The type in C refers to types that were defined in both B, A /// v1, and A v2. #[tokio::test] - async fn test_relinking() { + async fn test_relinking_layout() { let (_, cache) = package_cache([ (1, build_package("a0"), a0_types()), (2, build_package("a1"), a1_types()), @@ -1855,7 +1993,7 @@ mod tests { } #[tokio::test] - async fn test_value_nesting_boundary() { + async fn test_value_nesting_boundary_layout() { let (_, cache) = package_cache([(1, build_package("a0"), a0_types())]); let resolver = Resolver::new_with_limits( @@ -1881,7 +2019,7 @@ mod tests { } #[tokio::test] - async fn test_err_value_nesting_simple() { + async fn test_err_value_nesting_simple_layout() { let (_, cache) = package_cache([(1, build_package("a0"), a0_types())]); let resolver = Resolver::new_with_limits( @@ -1908,7 +2046,7 @@ mod tests { } #[tokio::test] - async fn test_err_value_nesting_big_type_param() { + async fn test_err_value_nesting_big_type_param_layout() { let (_, cache) = package_cache([(1, build_package("a0"), a0_types())]); let resolver = Resolver::new_with_limits( @@ -1936,7 +2074,7 @@ mod tests { } #[tokio::test] - async fn test_err_value_nesting_big_phantom_type_param() { + async fn test_err_value_nesting_big_phantom_type_param_layout() { let (_, cache) = package_cache([ (1, build_package("sui"), sui_types()), (1, build_package("d0"), d0_types()), @@ -1978,7 +2116,7 @@ mod tests { } #[tokio::test] - async fn test_err_value_nesting_type_param_application() { + async fn test_err_value_nesting_type_param_application_layout() { let (_, cache) = package_cache([ (1, build_package("sui"), sui_types()), (1, build_package("d0"), d0_types()), @@ -2093,7 +2231,7 @@ mod tests { } #[tokio::test] - async fn test_err_not_a_package() { + async fn test_layout_err_not_a_package() { let (_, cache) = package_cache([(1, build_package("a0"), a0_types())]); let resolver = Resolver::new(cache); let err = resolver @@ -2104,7 +2242,7 @@ mod tests { } #[tokio::test] - async fn test_err_no_module() { + async fn test_layout_err_no_module() { let (_, cache) = package_cache([(1, build_package("a0"), a0_types())]); let resolver = Resolver::new(cache); let err = resolver @@ -2115,7 +2253,7 @@ mod tests { } #[tokio::test] - async fn test_err_no_struct() { + async fn test_layout_err_no_struct() { let (_, cache) = package_cache([(1, build_package("a0"), a0_types())]); let resolver = Resolver::new(cache); @@ -2127,7 +2265,7 @@ mod tests { } #[tokio::test] - async fn test_err_type_arity() { + async fn test_layout_err_type_arity() { let (_, cache) = package_cache([(1, build_package("a0"), a0_types())]); let resolver = Resolver::new(cache); diff --git a/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__cross_module_enum.snap b/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__cross_module_enum.snap deleted file mode 100644 index bdfb0231e4f0b..0000000000000 --- a/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__cross_module_enum.snap +++ /dev/null @@ -1,23 +0,0 @@ ---- -source: crates/sui-package-resolver/src/lib.rs -expression: "format!(\"{layout:#}\")" ---- -enum 0xa0::n::E0 { - V0 { - t: enum 0xa0::m::E1 { - V { - a: address, - p: u16, - q: vector, - }, - }, - u: struct 0xa0::m::T2 { - x: u8, - }, - l: enum 0xa0::m::E2 { - V0 { - x: u8, - }, - }, - }, -} diff --git a/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__cross_module.snap b/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__cross_module_layout.snap similarity index 100% rename from crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__cross_module.snap rename to crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__cross_module_layout.snap diff --git a/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__cross_package_enum.snap b/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__cross_package_enum.snap deleted file mode 100644 index a64e236c8470d..0000000000000 --- a/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__cross_package_enum.snap +++ /dev/null @@ -1,110 +0,0 @@ ---- -source: crates/sui-package-resolver/src/lib.rs -expression: "format!(\"{layout:#}\")" ---- -enum 0xb0::m::E0 { - V0 { - m: struct 0xa0::m::T2 { - x: u8, - }, - n: struct 0xa0::n::T0 { - t: struct 0xa0::m::T1 { - a: address, - p: u16, - q: vector, - }, - u: struct 0xa0::m::T2 { - x: u8, - }, - }, - em: enum 0xa0::m::E2 { - V0 { - x: u8, - }, - }, - en: enum 0xa0::n::E0 { - V0 { - t: enum 0xa0::m::E1 { - V { - a: address, - p: u16, - q: vector, - }, - }, - u: struct 0xa0::m::T2 { - x: u8, - }, - l: enum 0xa0::m::E2 { - V0 { - x: u8, - }, - }, - }, - }, - }, - V1 { - em: enum 0xa0::m::E2 { - V0 { - x: u8, - }, - }, - en: enum 0xa0::n::E0 { - V0 { - t: enum 0xa0::m::E1 { - V { - a: address, - p: u16, - q: vector, - }, - }, - u: struct 0xa0::m::T2 { - x: u8, - }, - l: enum 0xa0::m::E2 { - V0 { - x: u8, - }, - }, - }, - }, - }, - V2 { - m: struct 0xa0::m::T2 { - x: u8, - }, - n: struct 0xa0::n::T0 { - t: struct 0xa0::m::T1 { - a: address, - p: u16, - q: vector, - }, - u: struct 0xa0::m::T2 { - x: u8, - }, - }, - em: enum 0xa0::m::E2 { - V0 { - x: u8, - }, - }, - en: enum 0xa0::n::E0 { - V0 { - t: enum 0xa0::m::E1 { - V { - a: address, - p: u16, - q: vector, - }, - }, - u: struct 0xa0::m::T2 { - x: u8, - }, - l: enum 0xa0::m::E2 { - V0 { - x: u8, - }, - }, - }, - }, - }, -} diff --git a/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__cross_package.snap b/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__cross_package_layout.snap similarity index 100% rename from crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__cross_package.snap rename to crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__cross_package_layout.snap diff --git a/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__multiple_linkage_contexts.snap b/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__multiple_linkage_contexts_layout.snap similarity index 100% rename from crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__multiple_linkage_contexts.snap rename to crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__multiple_linkage_contexts_layout.snap diff --git a/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__relinking.snap b/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__relinking_layout.snap similarity index 100% rename from crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__relinking.snap rename to crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__relinking_layout.snap diff --git a/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__simple_type.snap b/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__simple_type_layout.snap similarity index 89% rename from crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__simple_type.snap rename to crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__simple_type_layout.snap index 0c4a04da2661b..fb0d5509afc12 100644 --- a/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__simple_type.snap +++ b/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__simple_type_layout.snap @@ -1,6 +1,6 @@ --- source: crates/sui-package-resolver/src/lib.rs -expression: "format!(\"struct:\\n{struct_layout:#}\\n\\nenum:\\n{enum_layout:#}\")" +expression: "fmt(struct_layout, enum_layout)" --- struct: struct 0xa0::m::T0 { diff --git a/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__upgraded_package_enum.snap b/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__upgraded_package_enum.snap deleted file mode 100644 index 8fe1d2ffb0707..0000000000000 --- a/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__upgraded_package_enum.snap +++ /dev/null @@ -1,34 +0,0 @@ ---- -source: crates/sui-package-resolver/src/lib.rs -expression: "format!(\"{layout:#}\")" ---- -enum 0xa1::n::E1 { - V0 { - t: struct 0xa0::m::T1<0xa1::m::T3, u32> { - a: address, - p: struct 0xa1::m::T3 { - y: u16, - }, - q: vector, - }, - u: struct 0xa1::m::T4 { - z: u32, - }, - et: enum 0xa0::m::E1<0xa1::m::E3, u32> { - V { - a: address, - p: enum 0xa1::m::E3 { - V0 { - y: u16, - }, - }, - q: vector, - }, - }, - eu: enum 0xa1::m::E4 { - V0 { - z: u32, - }, - }, - }, -} diff --git a/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__upgraded_package.snap b/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__upgraded_package_layout.snap similarity index 100% rename from crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__upgraded_package.snap rename to crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__upgraded_package_layout.snap diff --git a/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__upgraded_package_non_defining_id.snap b/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__upgraded_package_non_defining_id_layout.snap similarity index 100% rename from crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__upgraded_package_non_defining_id.snap rename to crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__upgraded_package_non_defining_id_layout.snap diff --git a/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__value_nesting_boundary.snap b/crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__value_nesting_boundary_layout.snap similarity index 100% rename from crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__value_nesting_boundary.snap rename to crates/sui-package-resolver/src/snapshots/sui_package_resolver__tests__value_nesting_boundary_layout.snap diff --git a/crates/sui-protocol-config/src/lib.rs b/crates/sui-protocol-config/src/lib.rs index bd70932697fe0..835463599f7b8 100644 --- a/crates/sui-protocol-config/src/lib.rs +++ b/crates/sui-protocol-config/src/lib.rs @@ -18,7 +18,7 @@ use tracing::{info, warn}; /// The minimum and maximum protocol versions supported by this build. const MIN_PROTOCOL_VERSION: u64 = 1; -const MAX_PROTOCOL_VERSION: u64 = 60; +const MAX_PROTOCOL_VERSION: u64 = 65; // Record history of protocol version allocations here: // @@ -180,6 +180,12 @@ const MAX_PROTOCOL_VERSION: u64 = 60; // Version 59: Enable round prober in consensus. // Version 60: Validation of public inputs for Groth16 verification. // Enable configuration of maximum number of type nodes in a type layout. +// Version 61: Switch to distributed vote scoring in consensus in testnet +// Further reduce minimum number of random beacon shares. +// Add feature flag for Mysticeti fastpath. +// Version 62: Makes the event's sending module package upgrade-aware. +// Version 63: Enable gas based congestion control in consensus commit. +// Version 64: Switch to distributed vote scoring in consensus in mainnet #[derive(Copy, Clone, Debug, Hash, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct ProtocolVersion(u64); @@ -534,6 +540,14 @@ struct FeatureFlags { // Validate identifier inputs separately #[serde(skip_serializing_if = "is_false")] validate_identifier_inputs: bool, + + // Enables Mysticeti fastpath. + #[serde(skip_serializing_if = "is_false")] + mysticeti_fastpath: bool, + + // Makes the event's sending module version-aware. + #[serde(skip_serializing_if = "is_false")] + relocate_event_module: bool, } fn is_false(b: &bool) -> bool { @@ -565,8 +579,9 @@ impl ConsensusTransactionOrdering { pub enum PerObjectCongestionControlMode { #[default] None, // No congestion control. - TotalGasBudget, // Use txn gas budget as execution cost. - TotalTxCount, // Use total txn count as execution cost. + TotalGasBudget, // Use txn gas budget as execution cost. + TotalTxCount, // Use total txn count as execution cost. + TotalGasBudgetWithCap, // Use txn gas budget as execution cost with a cap. } impl PerObjectCongestionControlMode { @@ -1249,6 +1264,15 @@ pub struct ProtocolConfig { /// This config plays the same role as `max_accumulated_txn_cost_per_object_in_narwhal_commit` /// but for mysticeti commits due to that mysticeti has higher commit rate. max_accumulated_txn_cost_per_object_in_mysticeti_commit: Option, + + /// Configures the garbage collection depth for consensus. When is unset or `0` then the garbage collection + /// is disabled. + consensus_gc_depth: Option, + + /// Used to calculate the max transaction cost when using TotalGasBudgetWithCap as shard + /// object congestion control strategy. Basically the max transaction cost is calculated as + /// (num of input object + num of commands) * this factor. + gas_budget_based_txn_cost_cap_factor: Option, } // feature flags @@ -1523,10 +1547,6 @@ impl ProtocolConfig { self.feature_flags.consensus_network } - pub fn mysticeti_leader_scoring_and_schedule(&self) -> bool { - self.feature_flags.mysticeti_leader_scoring_and_schedule - } - pub fn reshare_at_same_initial_version(&self) -> bool { self.feature_flags.reshare_at_same_initial_version } @@ -1596,6 +1616,18 @@ impl ProtocolConfig { pub fn validate_identifier_inputs(&self) -> bool { self.feature_flags.validate_identifier_inputs } + + pub fn gc_depth(&self) -> u32 { + self.consensus_gc_depth.unwrap_or(0) + } + + pub fn mysticeti_fastpath(&self) -> bool { + self.feature_flags.mysticeti_fastpath + } + + pub fn relocate_event_module(&self) -> bool { + self.feature_flags.relocate_event_module + } } #[cfg(not(msim))] @@ -2109,6 +2141,10 @@ impl ProtocolConfig { bridge_should_try_to_finalize_committee: None, max_accumulated_txn_cost_per_object_in_mysticeti_commit: None, + + consensus_gc_depth: None, + + gas_budget_based_txn_cost_cap_factor: None, // When adding a new constant, set it to None in the earliest version, like this: // new_constant: None, }; @@ -2773,6 +2809,41 @@ impl ProtocolConfig { cfg.max_type_to_layout_nodes = Some(512); cfg.feature_flags.validate_identifier_inputs = true; } + 61 => { + if chain != Chain::Mainnet { + // Enable distributed vote scoring for testnet + cfg.feature_flags + .consensus_distributed_vote_scoring_strategy = true; + } + // Further reduce minimum number of random beacon shares. + cfg.random_beacon_reduction_lower_bound = Some(700); + + if chain != Chain::Mainnet && chain != Chain::Testnet { + // Enable Mysticeti fastpath for devnet + cfg.feature_flags.mysticeti_fastpath = true; + } + } + 62 => { + cfg.feature_flags.relocate_event_module = true; + } + 63 => { + cfg.feature_flags.per_object_congestion_control_mode = + PerObjectCongestionControlMode::TotalGasBudgetWithCap; + cfg.gas_budget_based_txn_cost_cap_factor = Some(400_000); + cfg.max_accumulated_txn_cost_per_object_in_mysticeti_commit = Some(18_500_000); + cfg.max_accumulated_txn_cost_per_object_in_narwhal_commit = Some(240_000_000); + } + 64 => { + cfg.feature_flags.per_object_congestion_control_mode = + PerObjectCongestionControlMode::TotalTxCount; + cfg.max_accumulated_txn_cost_per_object_in_narwhal_commit = Some(40); + cfg.max_accumulated_txn_cost_per_object_in_mysticeti_commit = Some(3); + } + 65 => { + // Enable distributed vote scoring for mainnet + cfg.feature_flags + .consensus_distributed_vote_scoring_strategy = true; + } // Use this template when making changes: // // // modify an existing constant. @@ -2909,14 +2980,11 @@ impl ProtocolConfig { pub fn set_zklogin_max_epoch_upper_bound_delta_for_testing(&mut self, val: Option) { self.feature_flags.zklogin_max_epoch_upper_bound_delta = val } + pub fn set_disable_bridge_for_testing(&mut self) { self.feature_flags.bridge = false } - pub fn set_mysticeti_leader_scoring_and_schedule_for_testing(&mut self, val: bool) { - self.feature_flags.mysticeti_leader_scoring_and_schedule = val; - } - pub fn set_mysticeti_num_leaders_per_round_for_testing(&mut self, val: Option) { self.feature_flags.mysticeti_num_leaders_per_round = val; } @@ -2937,6 +3005,10 @@ impl ProtocolConfig { pub fn set_consensus_round_prober_for_testing(&mut self, val: bool) { self.feature_flags.consensus_round_prober = val; } + + pub fn set_gc_depth_for_testing(&mut self, val: u32) { + self.consensus_gc_depth = Some(val); + } } type OverrideFn = dyn Fn(ProtocolVersion, ProtocolConfig) -> ProtocolConfig + Send; @@ -3143,11 +3215,10 @@ mod test { .feature_flags .lookup_attr("some random string".to_owned()) .is_none()); - assert!(prot + assert!(!prot .feature_flags .attr_map() - .get("some random string") - .is_none()); + .contains_key("some random string")); // Was false in v1 assert!( diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_61.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_61.snap new file mode 100644 index 0000000000000..f187877ef56cb --- /dev/null +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_61.snap @@ -0,0 +1,327 @@ +--- +source: crates/sui-protocol-config/src/lib.rs +expression: "ProtocolConfig::get_for_version(cur, *chain_id)" +--- +version: 61 +feature_flags: + package_upgrades: true + commit_root_state_digest: true + advance_epoch_start_time_in_safe_mode: true + loaded_child_objects_fixed: true + missing_type_is_compatibility_error: true + scoring_decision_with_validity_cutoff: true + consensus_order_end_of_epoch_last: true + disallow_adding_abilities_on_upgrade: true + disable_invariant_violation_check_in_swap_loc: true + advance_to_highest_supported_protocol_version: true + ban_entry_init: true + package_digest_hash_module: true + disallow_change_struct_type_params_on_upgrade: true + no_extraneous_module_bytes: true + narwhal_versioned_metadata: true + zklogin_auth: true + consensus_transaction_ordering: ByGasPrice + simplified_unwrap_then_delete: true + upgraded_multisig_supported: true + txn_base_cost_as_multiplier: true + shared_object_deletion: true + narwhal_new_leader_election_schedule: true + loaded_child_object_format: true + enable_jwk_consensus_updates: true + end_of_epoch_transaction_supported: true + simple_conservation_checks: true + loaded_child_object_format_type: true + receive_objects: true + random_beacon: true + bridge: true + enable_effects_v2: true + narwhal_certificate_v2: true + verify_legacy_zklogin_address: true + recompute_has_public_transfer_in_execution: true + accept_zklogin_in_multisig: true + include_consensus_digest_in_prologue: true + hardened_otw_check: true + allow_receiving_object_id: true + enable_coin_deny_list: true + enable_group_ops_native_functions: true + reject_mutable_random_on_entry_functions: true + per_object_congestion_control_mode: TotalTxCount + consensus_choice: Mysticeti + consensus_network: Tonic + zklogin_max_epoch_upper_bound_delta: 30 + mysticeti_leader_scoring_and_schedule: true + reshare_at_same_initial_version: true + resolve_abort_locations_to_package_id: true + mysticeti_use_committed_subdag_digest: true + record_consensus_determined_version_assignments_in_prologue: true + fresh_vm_on_framework_upgrade: true + prepend_prologue_tx_in_consensus_commit_in_checkpoints: true + mysticeti_num_leaders_per_round: 1 + soft_bundle: true + enable_coin_deny_list_v2: true + rethrow_serialization_type_layout_errors: true + consensus_round_prober: true + validate_identifier_inputs: true +max_tx_size_bytes: 131072 +max_input_objects: 2048 +max_size_written_objects: 5000000 +max_size_written_objects_system_tx: 50000000 +max_serialized_tx_effects_size_bytes: 524288 +max_serialized_tx_effects_size_bytes_system_tx: 8388608 +max_gas_payment_objects: 256 +max_modules_in_publish: 64 +max_package_dependencies: 32 +max_arguments: 512 +max_type_arguments: 16 +max_type_argument_depth: 16 +max_pure_argument_size: 16384 +max_programmable_tx_commands: 1024 +move_binary_format_version: 7 +min_move_binary_format_version: 6 +binary_module_handles: 100 +binary_struct_handles: 300 +binary_function_handles: 1500 +binary_function_instantiations: 750 +binary_signatures: 1000 +binary_constant_pool: 4000 +binary_identifiers: 10000 +binary_address_identifiers: 100 +binary_struct_defs: 200 +binary_struct_def_instantiations: 100 +binary_function_defs: 1000 +binary_field_handles: 500 +binary_field_instantiations: 250 +binary_friend_decls: 100 +max_move_object_size: 256000 +max_move_package_size: 102400 +max_publish_or_upgrade_per_ptb: 5 +max_tx_gas: 50000000000 +max_gas_price: 100000 +max_gas_computation_bucket: 5000000 +gas_rounding_step: 1000 +max_loop_depth: 5 +max_generic_instantiation_length: 32 +max_function_parameters: 128 +max_basic_blocks: 1024 +max_value_stack_size: 1024 +max_type_nodes: 256 +max_push_size: 10000 +max_struct_definitions: 200 +max_function_definitions: 1000 +max_fields_in_struct: 32 +max_dependency_depth: 100 +max_num_event_emit: 1024 +max_num_new_move_object_ids: 2048 +max_num_new_move_object_ids_system_tx: 32768 +max_num_deleted_move_object_ids: 2048 +max_num_deleted_move_object_ids_system_tx: 32768 +max_num_transferred_move_object_ids: 2048 +max_num_transferred_move_object_ids_system_tx: 32768 +max_event_emit_size: 256000 +max_event_emit_size_total: 65536000 +max_move_vector_len: 262144 +max_move_identifier_len: 128 +max_move_value_depth: 128 +max_back_edges_per_function: 10000 +max_back_edges_per_module: 10000 +max_verifier_meter_ticks_per_function: 16000000 +max_meter_ticks_per_module: 16000000 +max_meter_ticks_per_package: 16000000 +object_runtime_max_num_cached_objects: 1000 +object_runtime_max_num_cached_objects_system_tx: 16000 +object_runtime_max_num_store_entries: 1000 +object_runtime_max_num_store_entries_system_tx: 16000 +base_tx_cost_fixed: 1000 +package_publish_cost_fixed: 1000 +base_tx_cost_per_byte: 0 +package_publish_cost_per_byte: 80 +obj_access_cost_read_per_byte: 15 +obj_access_cost_mutate_per_byte: 40 +obj_access_cost_delete_per_byte: 40 +obj_access_cost_verify_per_byte: 200 +max_type_to_layout_nodes: 512 +gas_model_version: 8 +obj_data_cost_refundable: 100 +obj_metadata_cost_non_refundable: 50 +storage_rebate_rate: 9900 +storage_fund_reinvest_rate: 500 +reward_slashing_rate: 10000 +storage_gas_price: 76 +max_transactions_per_checkpoint: 10000 +max_checkpoint_size_bytes: 31457280 +buffer_stake_for_protocol_upgrade_bps: 5000 +address_from_bytes_cost_base: 52 +address_to_u256_cost_base: 52 +address_from_u256_cost_base: 52 +config_read_setting_impl_cost_base: 100 +config_read_setting_impl_cost_per_byte: 40 +dynamic_field_hash_type_and_key_cost_base: 100 +dynamic_field_hash_type_and_key_type_cost_per_byte: 2 +dynamic_field_hash_type_and_key_value_cost_per_byte: 2 +dynamic_field_hash_type_and_key_type_tag_cost_per_byte: 2 +dynamic_field_add_child_object_cost_base: 100 +dynamic_field_add_child_object_type_cost_per_byte: 10 +dynamic_field_add_child_object_value_cost_per_byte: 10 +dynamic_field_add_child_object_struct_tag_cost_per_byte: 10 +dynamic_field_borrow_child_object_cost_base: 100 +dynamic_field_borrow_child_object_child_ref_cost_per_byte: 10 +dynamic_field_borrow_child_object_type_cost_per_byte: 10 +dynamic_field_remove_child_object_cost_base: 100 +dynamic_field_remove_child_object_child_cost_per_byte: 2 +dynamic_field_remove_child_object_type_cost_per_byte: 2 +dynamic_field_has_child_object_cost_base: 100 +dynamic_field_has_child_object_with_ty_cost_base: 100 +dynamic_field_has_child_object_with_ty_type_cost_per_byte: 2 +dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: 2 +event_emit_cost_base: 52 +event_emit_value_size_derivation_cost_per_byte: 2 +event_emit_tag_size_derivation_cost_per_byte: 5 +event_emit_output_cost_per_byte: 10 +object_borrow_uid_cost_base: 52 +object_delete_impl_cost_base: 52 +object_record_new_uid_cost_base: 52 +transfer_transfer_internal_cost_base: 52 +transfer_freeze_object_cost_base: 52 +transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 +tx_context_derive_id_cost_base: 52 +types_is_one_time_witness_cost_base: 52 +types_is_one_time_witness_type_tag_cost_per_byte: 2 +types_is_one_time_witness_type_cost_per_byte: 2 +validator_validate_metadata_cost_base: 52 +validator_validate_metadata_data_cost_per_byte: 2 +crypto_invalid_arguments_cost: 100 +bls12381_bls12381_min_sig_verify_cost_base: 52 +bls12381_bls12381_min_sig_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_sig_verify_msg_cost_per_block: 2 +bls12381_bls12381_min_pk_verify_cost_base: 52 +bls12381_bls12381_min_pk_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_pk_verify_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_keccak256_cost_base: 52 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_sha256_cost_base: 52 +ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_k1_decompress_pubkey_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_k1_secp256k1_verify_sha256_cost_base: 52 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_keccak256_cost_base: 52 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_sha256_cost_base: 52 +ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_keccak256_cost_base: 52 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_sha256_cost_base: 52 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: 2 +ecvrf_ecvrf_verify_cost_base: 52 +ecvrf_ecvrf_verify_alpha_string_cost_per_byte: 2 +ecvrf_ecvrf_verify_alpha_string_cost_per_block: 2 +ed25519_ed25519_verify_cost_base: 52 +ed25519_ed25519_verify_msg_cost_per_byte: 2 +ed25519_ed25519_verify_msg_cost_per_block: 2 +groth16_prepare_verifying_key_bls12381_cost_base: 52 +groth16_prepare_verifying_key_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_public_input_cost_per_byte: 2 +hash_blake2b256_cost_base: 52 +hash_blake2b256_data_cost_per_byte: 2 +hash_blake2b256_data_cost_per_block: 2 +hash_keccak256_cost_base: 52 +hash_keccak256_data_cost_per_byte: 2 +hash_keccak256_data_cost_per_block: 2 +group_ops_bls12381_decode_scalar_cost: 52 +group_ops_bls12381_decode_g1_cost: 52 +group_ops_bls12381_decode_g2_cost: 52 +group_ops_bls12381_decode_gt_cost: 52 +group_ops_bls12381_scalar_add_cost: 52 +group_ops_bls12381_g1_add_cost: 52 +group_ops_bls12381_g2_add_cost: 52 +group_ops_bls12381_gt_add_cost: 52 +group_ops_bls12381_scalar_sub_cost: 52 +group_ops_bls12381_g1_sub_cost: 52 +group_ops_bls12381_g2_sub_cost: 52 +group_ops_bls12381_gt_sub_cost: 52 +group_ops_bls12381_scalar_mul_cost: 52 +group_ops_bls12381_g1_mul_cost: 52 +group_ops_bls12381_g2_mul_cost: 52 +group_ops_bls12381_gt_mul_cost: 52 +group_ops_bls12381_scalar_div_cost: 52 +group_ops_bls12381_g1_div_cost: 52 +group_ops_bls12381_g2_div_cost: 52 +group_ops_bls12381_gt_div_cost: 52 +group_ops_bls12381_g1_hash_to_base_cost: 52 +group_ops_bls12381_g2_hash_to_base_cost: 52 +group_ops_bls12381_g1_hash_to_cost_per_byte: 2 +group_ops_bls12381_g2_hash_to_cost_per_byte: 2 +group_ops_bls12381_g1_msm_base_cost: 52 +group_ops_bls12381_g2_msm_base_cost: 52 +group_ops_bls12381_g1_msm_base_cost_per_input: 52 +group_ops_bls12381_g2_msm_base_cost_per_input: 52 +group_ops_bls12381_msm_max_len: 32 +group_ops_bls12381_pairing_cost: 52 +hmac_hmac_sha3_256_cost_base: 52 +hmac_hmac_sha3_256_input_cost_per_byte: 2 +hmac_hmac_sha3_256_input_cost_per_block: 2 +check_zklogin_id_cost_base: 200 +check_zklogin_issuer_cost_base: 200 +bcs_per_byte_serialized_cost: 2 +bcs_legacy_min_output_size_cost: 1 +bcs_failure_cost: 52 +hash_sha2_256_base_cost: 52 +hash_sha2_256_per_byte_cost: 2 +hash_sha2_256_legacy_min_input_len_cost: 1 +hash_sha3_256_base_cost: 52 +hash_sha3_256_per_byte_cost: 2 +hash_sha3_256_legacy_min_input_len_cost: 1 +type_name_get_base_cost: 52 +type_name_get_per_byte_cost: 2 +string_check_utf8_base_cost: 52 +string_check_utf8_per_byte_cost: 2 +string_is_char_boundary_base_cost: 52 +string_sub_string_base_cost: 52 +string_sub_string_per_byte_cost: 2 +string_index_of_base_cost: 52 +string_index_of_per_byte_pattern_cost: 2 +string_index_of_per_byte_searched_cost: 2 +vector_empty_base_cost: 52 +vector_length_base_cost: 52 +vector_push_back_base_cost: 52 +vector_push_back_legacy_per_abstract_memory_unit_cost: 2 +vector_borrow_base_cost: 52 +vector_pop_back_base_cost: 52 +vector_destroy_empty_base_cost: 52 +vector_swap_base_cost: 52 +debug_print_base_cost: 52 +debug_print_stack_trace_base_cost: 52 +execution_version: 3 +consensus_bad_nodes_stake_threshold: 20 +max_jwk_votes_per_validator_per_epoch: 240 +max_age_of_jwk_in_epochs: 1 +random_beacon_reduction_allowed_delta: 800 +random_beacon_reduction_lower_bound: 700 +random_beacon_dkg_timeout_round: 3000 +random_beacon_min_round_interval_ms: 500 +random_beacon_dkg_version: 1 +consensus_max_transaction_size_bytes: 262144 +consensus_max_transactions_in_block_bytes: 524288 +consensus_max_num_transactions_in_block: 512 +max_accumulated_txn_cost_per_object_in_narwhal_commit: 100 +max_deferral_rounds_for_congestion_control: 10 +min_checkpoint_interval_ms: 200 +checkpoint_summary_version_specific_data: 1 +max_soft_bundle_size: 5 +bridge_should_try_to_finalize_committee: true +max_accumulated_txn_cost_per_object_in_mysticeti_commit: 10 + diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_62.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_62.snap new file mode 100644 index 0000000000000..1c36c50bc287d --- /dev/null +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_62.snap @@ -0,0 +1,328 @@ +--- +source: crates/sui-protocol-config/src/lib.rs +expression: "ProtocolConfig::get_for_version(cur, *chain_id)" +--- +version: 62 +feature_flags: + package_upgrades: true + commit_root_state_digest: true + advance_epoch_start_time_in_safe_mode: true + loaded_child_objects_fixed: true + missing_type_is_compatibility_error: true + scoring_decision_with_validity_cutoff: true + consensus_order_end_of_epoch_last: true + disallow_adding_abilities_on_upgrade: true + disable_invariant_violation_check_in_swap_loc: true + advance_to_highest_supported_protocol_version: true + ban_entry_init: true + package_digest_hash_module: true + disallow_change_struct_type_params_on_upgrade: true + no_extraneous_module_bytes: true + narwhal_versioned_metadata: true + zklogin_auth: true + consensus_transaction_ordering: ByGasPrice + simplified_unwrap_then_delete: true + upgraded_multisig_supported: true + txn_base_cost_as_multiplier: true + shared_object_deletion: true + narwhal_new_leader_election_schedule: true + loaded_child_object_format: true + enable_jwk_consensus_updates: true + end_of_epoch_transaction_supported: true + simple_conservation_checks: true + loaded_child_object_format_type: true + receive_objects: true + random_beacon: true + bridge: true + enable_effects_v2: true + narwhal_certificate_v2: true + verify_legacy_zklogin_address: true + recompute_has_public_transfer_in_execution: true + accept_zklogin_in_multisig: true + include_consensus_digest_in_prologue: true + hardened_otw_check: true + allow_receiving_object_id: true + enable_coin_deny_list: true + enable_group_ops_native_functions: true + reject_mutable_random_on_entry_functions: true + per_object_congestion_control_mode: TotalTxCount + consensus_choice: Mysticeti + consensus_network: Tonic + zklogin_max_epoch_upper_bound_delta: 30 + mysticeti_leader_scoring_and_schedule: true + reshare_at_same_initial_version: true + resolve_abort_locations_to_package_id: true + mysticeti_use_committed_subdag_digest: true + record_consensus_determined_version_assignments_in_prologue: true + fresh_vm_on_framework_upgrade: true + prepend_prologue_tx_in_consensus_commit_in_checkpoints: true + mysticeti_num_leaders_per_round: 1 + soft_bundle: true + enable_coin_deny_list_v2: true + rethrow_serialization_type_layout_errors: true + consensus_round_prober: true + validate_identifier_inputs: true + relocate_event_module: true +max_tx_size_bytes: 131072 +max_input_objects: 2048 +max_size_written_objects: 5000000 +max_size_written_objects_system_tx: 50000000 +max_serialized_tx_effects_size_bytes: 524288 +max_serialized_tx_effects_size_bytes_system_tx: 8388608 +max_gas_payment_objects: 256 +max_modules_in_publish: 64 +max_package_dependencies: 32 +max_arguments: 512 +max_type_arguments: 16 +max_type_argument_depth: 16 +max_pure_argument_size: 16384 +max_programmable_tx_commands: 1024 +move_binary_format_version: 7 +min_move_binary_format_version: 6 +binary_module_handles: 100 +binary_struct_handles: 300 +binary_function_handles: 1500 +binary_function_instantiations: 750 +binary_signatures: 1000 +binary_constant_pool: 4000 +binary_identifiers: 10000 +binary_address_identifiers: 100 +binary_struct_defs: 200 +binary_struct_def_instantiations: 100 +binary_function_defs: 1000 +binary_field_handles: 500 +binary_field_instantiations: 250 +binary_friend_decls: 100 +max_move_object_size: 256000 +max_move_package_size: 102400 +max_publish_or_upgrade_per_ptb: 5 +max_tx_gas: 50000000000 +max_gas_price: 100000 +max_gas_computation_bucket: 5000000 +gas_rounding_step: 1000 +max_loop_depth: 5 +max_generic_instantiation_length: 32 +max_function_parameters: 128 +max_basic_blocks: 1024 +max_value_stack_size: 1024 +max_type_nodes: 256 +max_push_size: 10000 +max_struct_definitions: 200 +max_function_definitions: 1000 +max_fields_in_struct: 32 +max_dependency_depth: 100 +max_num_event_emit: 1024 +max_num_new_move_object_ids: 2048 +max_num_new_move_object_ids_system_tx: 32768 +max_num_deleted_move_object_ids: 2048 +max_num_deleted_move_object_ids_system_tx: 32768 +max_num_transferred_move_object_ids: 2048 +max_num_transferred_move_object_ids_system_tx: 32768 +max_event_emit_size: 256000 +max_event_emit_size_total: 65536000 +max_move_vector_len: 262144 +max_move_identifier_len: 128 +max_move_value_depth: 128 +max_back_edges_per_function: 10000 +max_back_edges_per_module: 10000 +max_verifier_meter_ticks_per_function: 16000000 +max_meter_ticks_per_module: 16000000 +max_meter_ticks_per_package: 16000000 +object_runtime_max_num_cached_objects: 1000 +object_runtime_max_num_cached_objects_system_tx: 16000 +object_runtime_max_num_store_entries: 1000 +object_runtime_max_num_store_entries_system_tx: 16000 +base_tx_cost_fixed: 1000 +package_publish_cost_fixed: 1000 +base_tx_cost_per_byte: 0 +package_publish_cost_per_byte: 80 +obj_access_cost_read_per_byte: 15 +obj_access_cost_mutate_per_byte: 40 +obj_access_cost_delete_per_byte: 40 +obj_access_cost_verify_per_byte: 200 +max_type_to_layout_nodes: 512 +gas_model_version: 8 +obj_data_cost_refundable: 100 +obj_metadata_cost_non_refundable: 50 +storage_rebate_rate: 9900 +storage_fund_reinvest_rate: 500 +reward_slashing_rate: 10000 +storage_gas_price: 76 +max_transactions_per_checkpoint: 10000 +max_checkpoint_size_bytes: 31457280 +buffer_stake_for_protocol_upgrade_bps: 5000 +address_from_bytes_cost_base: 52 +address_to_u256_cost_base: 52 +address_from_u256_cost_base: 52 +config_read_setting_impl_cost_base: 100 +config_read_setting_impl_cost_per_byte: 40 +dynamic_field_hash_type_and_key_cost_base: 100 +dynamic_field_hash_type_and_key_type_cost_per_byte: 2 +dynamic_field_hash_type_and_key_value_cost_per_byte: 2 +dynamic_field_hash_type_and_key_type_tag_cost_per_byte: 2 +dynamic_field_add_child_object_cost_base: 100 +dynamic_field_add_child_object_type_cost_per_byte: 10 +dynamic_field_add_child_object_value_cost_per_byte: 10 +dynamic_field_add_child_object_struct_tag_cost_per_byte: 10 +dynamic_field_borrow_child_object_cost_base: 100 +dynamic_field_borrow_child_object_child_ref_cost_per_byte: 10 +dynamic_field_borrow_child_object_type_cost_per_byte: 10 +dynamic_field_remove_child_object_cost_base: 100 +dynamic_field_remove_child_object_child_cost_per_byte: 2 +dynamic_field_remove_child_object_type_cost_per_byte: 2 +dynamic_field_has_child_object_cost_base: 100 +dynamic_field_has_child_object_with_ty_cost_base: 100 +dynamic_field_has_child_object_with_ty_type_cost_per_byte: 2 +dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: 2 +event_emit_cost_base: 52 +event_emit_value_size_derivation_cost_per_byte: 2 +event_emit_tag_size_derivation_cost_per_byte: 5 +event_emit_output_cost_per_byte: 10 +object_borrow_uid_cost_base: 52 +object_delete_impl_cost_base: 52 +object_record_new_uid_cost_base: 52 +transfer_transfer_internal_cost_base: 52 +transfer_freeze_object_cost_base: 52 +transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 +tx_context_derive_id_cost_base: 52 +types_is_one_time_witness_cost_base: 52 +types_is_one_time_witness_type_tag_cost_per_byte: 2 +types_is_one_time_witness_type_cost_per_byte: 2 +validator_validate_metadata_cost_base: 52 +validator_validate_metadata_data_cost_per_byte: 2 +crypto_invalid_arguments_cost: 100 +bls12381_bls12381_min_sig_verify_cost_base: 52 +bls12381_bls12381_min_sig_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_sig_verify_msg_cost_per_block: 2 +bls12381_bls12381_min_pk_verify_cost_base: 52 +bls12381_bls12381_min_pk_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_pk_verify_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_keccak256_cost_base: 52 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_sha256_cost_base: 52 +ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_k1_decompress_pubkey_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_k1_secp256k1_verify_sha256_cost_base: 52 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_keccak256_cost_base: 52 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_sha256_cost_base: 52 +ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_keccak256_cost_base: 52 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_sha256_cost_base: 52 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: 2 +ecvrf_ecvrf_verify_cost_base: 52 +ecvrf_ecvrf_verify_alpha_string_cost_per_byte: 2 +ecvrf_ecvrf_verify_alpha_string_cost_per_block: 2 +ed25519_ed25519_verify_cost_base: 52 +ed25519_ed25519_verify_msg_cost_per_byte: 2 +ed25519_ed25519_verify_msg_cost_per_block: 2 +groth16_prepare_verifying_key_bls12381_cost_base: 52 +groth16_prepare_verifying_key_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_public_input_cost_per_byte: 2 +hash_blake2b256_cost_base: 52 +hash_blake2b256_data_cost_per_byte: 2 +hash_blake2b256_data_cost_per_block: 2 +hash_keccak256_cost_base: 52 +hash_keccak256_data_cost_per_byte: 2 +hash_keccak256_data_cost_per_block: 2 +group_ops_bls12381_decode_scalar_cost: 52 +group_ops_bls12381_decode_g1_cost: 52 +group_ops_bls12381_decode_g2_cost: 52 +group_ops_bls12381_decode_gt_cost: 52 +group_ops_bls12381_scalar_add_cost: 52 +group_ops_bls12381_g1_add_cost: 52 +group_ops_bls12381_g2_add_cost: 52 +group_ops_bls12381_gt_add_cost: 52 +group_ops_bls12381_scalar_sub_cost: 52 +group_ops_bls12381_g1_sub_cost: 52 +group_ops_bls12381_g2_sub_cost: 52 +group_ops_bls12381_gt_sub_cost: 52 +group_ops_bls12381_scalar_mul_cost: 52 +group_ops_bls12381_g1_mul_cost: 52 +group_ops_bls12381_g2_mul_cost: 52 +group_ops_bls12381_gt_mul_cost: 52 +group_ops_bls12381_scalar_div_cost: 52 +group_ops_bls12381_g1_div_cost: 52 +group_ops_bls12381_g2_div_cost: 52 +group_ops_bls12381_gt_div_cost: 52 +group_ops_bls12381_g1_hash_to_base_cost: 52 +group_ops_bls12381_g2_hash_to_base_cost: 52 +group_ops_bls12381_g1_hash_to_cost_per_byte: 2 +group_ops_bls12381_g2_hash_to_cost_per_byte: 2 +group_ops_bls12381_g1_msm_base_cost: 52 +group_ops_bls12381_g2_msm_base_cost: 52 +group_ops_bls12381_g1_msm_base_cost_per_input: 52 +group_ops_bls12381_g2_msm_base_cost_per_input: 52 +group_ops_bls12381_msm_max_len: 32 +group_ops_bls12381_pairing_cost: 52 +hmac_hmac_sha3_256_cost_base: 52 +hmac_hmac_sha3_256_input_cost_per_byte: 2 +hmac_hmac_sha3_256_input_cost_per_block: 2 +check_zklogin_id_cost_base: 200 +check_zklogin_issuer_cost_base: 200 +bcs_per_byte_serialized_cost: 2 +bcs_legacy_min_output_size_cost: 1 +bcs_failure_cost: 52 +hash_sha2_256_base_cost: 52 +hash_sha2_256_per_byte_cost: 2 +hash_sha2_256_legacy_min_input_len_cost: 1 +hash_sha3_256_base_cost: 52 +hash_sha3_256_per_byte_cost: 2 +hash_sha3_256_legacy_min_input_len_cost: 1 +type_name_get_base_cost: 52 +type_name_get_per_byte_cost: 2 +string_check_utf8_base_cost: 52 +string_check_utf8_per_byte_cost: 2 +string_is_char_boundary_base_cost: 52 +string_sub_string_base_cost: 52 +string_sub_string_per_byte_cost: 2 +string_index_of_base_cost: 52 +string_index_of_per_byte_pattern_cost: 2 +string_index_of_per_byte_searched_cost: 2 +vector_empty_base_cost: 52 +vector_length_base_cost: 52 +vector_push_back_base_cost: 52 +vector_push_back_legacy_per_abstract_memory_unit_cost: 2 +vector_borrow_base_cost: 52 +vector_pop_back_base_cost: 52 +vector_destroy_empty_base_cost: 52 +vector_swap_base_cost: 52 +debug_print_base_cost: 52 +debug_print_stack_trace_base_cost: 52 +execution_version: 3 +consensus_bad_nodes_stake_threshold: 20 +max_jwk_votes_per_validator_per_epoch: 240 +max_age_of_jwk_in_epochs: 1 +random_beacon_reduction_allowed_delta: 800 +random_beacon_reduction_lower_bound: 700 +random_beacon_dkg_timeout_round: 3000 +random_beacon_min_round_interval_ms: 500 +random_beacon_dkg_version: 1 +consensus_max_transaction_size_bytes: 262144 +consensus_max_transactions_in_block_bytes: 524288 +consensus_max_num_transactions_in_block: 512 +max_accumulated_txn_cost_per_object_in_narwhal_commit: 100 +max_deferral_rounds_for_congestion_control: 10 +min_checkpoint_interval_ms: 200 +checkpoint_summary_version_specific_data: 1 +max_soft_bundle_size: 5 +bridge_should_try_to_finalize_committee: true +max_accumulated_txn_cost_per_object_in_mysticeti_commit: 10 + diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_63.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_63.snap new file mode 100644 index 0000000000000..14410e41af35a --- /dev/null +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_63.snap @@ -0,0 +1,328 @@ +--- +source: crates/sui-protocol-config/src/lib.rs +expression: "ProtocolConfig::get_for_version(cur, *chain_id)" +--- +version: 63 +feature_flags: + package_upgrades: true + commit_root_state_digest: true + advance_epoch_start_time_in_safe_mode: true + loaded_child_objects_fixed: true + missing_type_is_compatibility_error: true + scoring_decision_with_validity_cutoff: true + consensus_order_end_of_epoch_last: true + disallow_adding_abilities_on_upgrade: true + disable_invariant_violation_check_in_swap_loc: true + advance_to_highest_supported_protocol_version: true + ban_entry_init: true + package_digest_hash_module: true + disallow_change_struct_type_params_on_upgrade: true + no_extraneous_module_bytes: true + narwhal_versioned_metadata: true + zklogin_auth: true + consensus_transaction_ordering: ByGasPrice + simplified_unwrap_then_delete: true + upgraded_multisig_supported: true + txn_base_cost_as_multiplier: true + shared_object_deletion: true + narwhal_new_leader_election_schedule: true + loaded_child_object_format: true + enable_jwk_consensus_updates: true + end_of_epoch_transaction_supported: true + simple_conservation_checks: true + loaded_child_object_format_type: true + receive_objects: true + random_beacon: true + bridge: true + enable_effects_v2: true + narwhal_certificate_v2: true + verify_legacy_zklogin_address: true + recompute_has_public_transfer_in_execution: true + accept_zklogin_in_multisig: true + include_consensus_digest_in_prologue: true + hardened_otw_check: true + allow_receiving_object_id: true + enable_coin_deny_list: true + enable_group_ops_native_functions: true + reject_mutable_random_on_entry_functions: true + per_object_congestion_control_mode: TotalGasBudgetWithCap + consensus_choice: Mysticeti + consensus_network: Tonic + zklogin_max_epoch_upper_bound_delta: 30 + mysticeti_leader_scoring_and_schedule: true + reshare_at_same_initial_version: true + resolve_abort_locations_to_package_id: true + mysticeti_use_committed_subdag_digest: true + record_consensus_determined_version_assignments_in_prologue: true + fresh_vm_on_framework_upgrade: true + prepend_prologue_tx_in_consensus_commit_in_checkpoints: true + mysticeti_num_leaders_per_round: 1 + soft_bundle: true + enable_coin_deny_list_v2: true + rethrow_serialization_type_layout_errors: true + consensus_round_prober: true + validate_identifier_inputs: true + relocate_event_module: true +max_tx_size_bytes: 131072 +max_input_objects: 2048 +max_size_written_objects: 5000000 +max_size_written_objects_system_tx: 50000000 +max_serialized_tx_effects_size_bytes: 524288 +max_serialized_tx_effects_size_bytes_system_tx: 8388608 +max_gas_payment_objects: 256 +max_modules_in_publish: 64 +max_package_dependencies: 32 +max_arguments: 512 +max_type_arguments: 16 +max_type_argument_depth: 16 +max_pure_argument_size: 16384 +max_programmable_tx_commands: 1024 +move_binary_format_version: 7 +min_move_binary_format_version: 6 +binary_module_handles: 100 +binary_struct_handles: 300 +binary_function_handles: 1500 +binary_function_instantiations: 750 +binary_signatures: 1000 +binary_constant_pool: 4000 +binary_identifiers: 10000 +binary_address_identifiers: 100 +binary_struct_defs: 200 +binary_struct_def_instantiations: 100 +binary_function_defs: 1000 +binary_field_handles: 500 +binary_field_instantiations: 250 +binary_friend_decls: 100 +max_move_object_size: 256000 +max_move_package_size: 102400 +max_publish_or_upgrade_per_ptb: 5 +max_tx_gas: 50000000000 +max_gas_price: 100000 +max_gas_computation_bucket: 5000000 +gas_rounding_step: 1000 +max_loop_depth: 5 +max_generic_instantiation_length: 32 +max_function_parameters: 128 +max_basic_blocks: 1024 +max_value_stack_size: 1024 +max_type_nodes: 256 +max_push_size: 10000 +max_struct_definitions: 200 +max_function_definitions: 1000 +max_fields_in_struct: 32 +max_dependency_depth: 100 +max_num_event_emit: 1024 +max_num_new_move_object_ids: 2048 +max_num_new_move_object_ids_system_tx: 32768 +max_num_deleted_move_object_ids: 2048 +max_num_deleted_move_object_ids_system_tx: 32768 +max_num_transferred_move_object_ids: 2048 +max_num_transferred_move_object_ids_system_tx: 32768 +max_event_emit_size: 256000 +max_event_emit_size_total: 65536000 +max_move_vector_len: 262144 +max_move_identifier_len: 128 +max_move_value_depth: 128 +max_back_edges_per_function: 10000 +max_back_edges_per_module: 10000 +max_verifier_meter_ticks_per_function: 16000000 +max_meter_ticks_per_module: 16000000 +max_meter_ticks_per_package: 16000000 +object_runtime_max_num_cached_objects: 1000 +object_runtime_max_num_cached_objects_system_tx: 16000 +object_runtime_max_num_store_entries: 1000 +object_runtime_max_num_store_entries_system_tx: 16000 +base_tx_cost_fixed: 1000 +package_publish_cost_fixed: 1000 +base_tx_cost_per_byte: 0 +package_publish_cost_per_byte: 80 +obj_access_cost_read_per_byte: 15 +obj_access_cost_mutate_per_byte: 40 +obj_access_cost_delete_per_byte: 40 +obj_access_cost_verify_per_byte: 200 +max_type_to_layout_nodes: 512 +gas_model_version: 8 +obj_data_cost_refundable: 100 +obj_metadata_cost_non_refundable: 50 +storage_rebate_rate: 9900 +storage_fund_reinvest_rate: 500 +reward_slashing_rate: 10000 +storage_gas_price: 76 +max_transactions_per_checkpoint: 10000 +max_checkpoint_size_bytes: 31457280 +buffer_stake_for_protocol_upgrade_bps: 5000 +address_from_bytes_cost_base: 52 +address_to_u256_cost_base: 52 +address_from_u256_cost_base: 52 +config_read_setting_impl_cost_base: 100 +config_read_setting_impl_cost_per_byte: 40 +dynamic_field_hash_type_and_key_cost_base: 100 +dynamic_field_hash_type_and_key_type_cost_per_byte: 2 +dynamic_field_hash_type_and_key_value_cost_per_byte: 2 +dynamic_field_hash_type_and_key_type_tag_cost_per_byte: 2 +dynamic_field_add_child_object_cost_base: 100 +dynamic_field_add_child_object_type_cost_per_byte: 10 +dynamic_field_add_child_object_value_cost_per_byte: 10 +dynamic_field_add_child_object_struct_tag_cost_per_byte: 10 +dynamic_field_borrow_child_object_cost_base: 100 +dynamic_field_borrow_child_object_child_ref_cost_per_byte: 10 +dynamic_field_borrow_child_object_type_cost_per_byte: 10 +dynamic_field_remove_child_object_cost_base: 100 +dynamic_field_remove_child_object_child_cost_per_byte: 2 +dynamic_field_remove_child_object_type_cost_per_byte: 2 +dynamic_field_has_child_object_cost_base: 100 +dynamic_field_has_child_object_with_ty_cost_base: 100 +dynamic_field_has_child_object_with_ty_type_cost_per_byte: 2 +dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: 2 +event_emit_cost_base: 52 +event_emit_value_size_derivation_cost_per_byte: 2 +event_emit_tag_size_derivation_cost_per_byte: 5 +event_emit_output_cost_per_byte: 10 +object_borrow_uid_cost_base: 52 +object_delete_impl_cost_base: 52 +object_record_new_uid_cost_base: 52 +transfer_transfer_internal_cost_base: 52 +transfer_freeze_object_cost_base: 52 +transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 +tx_context_derive_id_cost_base: 52 +types_is_one_time_witness_cost_base: 52 +types_is_one_time_witness_type_tag_cost_per_byte: 2 +types_is_one_time_witness_type_cost_per_byte: 2 +validator_validate_metadata_cost_base: 52 +validator_validate_metadata_data_cost_per_byte: 2 +crypto_invalid_arguments_cost: 100 +bls12381_bls12381_min_sig_verify_cost_base: 52 +bls12381_bls12381_min_sig_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_sig_verify_msg_cost_per_block: 2 +bls12381_bls12381_min_pk_verify_cost_base: 52 +bls12381_bls12381_min_pk_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_pk_verify_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_keccak256_cost_base: 52 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_sha256_cost_base: 52 +ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_k1_decompress_pubkey_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_k1_secp256k1_verify_sha256_cost_base: 52 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_keccak256_cost_base: 52 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_sha256_cost_base: 52 +ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_keccak256_cost_base: 52 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_sha256_cost_base: 52 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: 2 +ecvrf_ecvrf_verify_cost_base: 52 +ecvrf_ecvrf_verify_alpha_string_cost_per_byte: 2 +ecvrf_ecvrf_verify_alpha_string_cost_per_block: 2 +ed25519_ed25519_verify_cost_base: 52 +ed25519_ed25519_verify_msg_cost_per_byte: 2 +ed25519_ed25519_verify_msg_cost_per_block: 2 +groth16_prepare_verifying_key_bls12381_cost_base: 52 +groth16_prepare_verifying_key_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_public_input_cost_per_byte: 2 +hash_blake2b256_cost_base: 52 +hash_blake2b256_data_cost_per_byte: 2 +hash_blake2b256_data_cost_per_block: 2 +hash_keccak256_cost_base: 52 +hash_keccak256_data_cost_per_byte: 2 +hash_keccak256_data_cost_per_block: 2 +group_ops_bls12381_decode_scalar_cost: 52 +group_ops_bls12381_decode_g1_cost: 52 +group_ops_bls12381_decode_g2_cost: 52 +group_ops_bls12381_decode_gt_cost: 52 +group_ops_bls12381_scalar_add_cost: 52 +group_ops_bls12381_g1_add_cost: 52 +group_ops_bls12381_g2_add_cost: 52 +group_ops_bls12381_gt_add_cost: 52 +group_ops_bls12381_scalar_sub_cost: 52 +group_ops_bls12381_g1_sub_cost: 52 +group_ops_bls12381_g2_sub_cost: 52 +group_ops_bls12381_gt_sub_cost: 52 +group_ops_bls12381_scalar_mul_cost: 52 +group_ops_bls12381_g1_mul_cost: 52 +group_ops_bls12381_g2_mul_cost: 52 +group_ops_bls12381_gt_mul_cost: 52 +group_ops_bls12381_scalar_div_cost: 52 +group_ops_bls12381_g1_div_cost: 52 +group_ops_bls12381_g2_div_cost: 52 +group_ops_bls12381_gt_div_cost: 52 +group_ops_bls12381_g1_hash_to_base_cost: 52 +group_ops_bls12381_g2_hash_to_base_cost: 52 +group_ops_bls12381_g1_hash_to_cost_per_byte: 2 +group_ops_bls12381_g2_hash_to_cost_per_byte: 2 +group_ops_bls12381_g1_msm_base_cost: 52 +group_ops_bls12381_g2_msm_base_cost: 52 +group_ops_bls12381_g1_msm_base_cost_per_input: 52 +group_ops_bls12381_g2_msm_base_cost_per_input: 52 +group_ops_bls12381_msm_max_len: 32 +group_ops_bls12381_pairing_cost: 52 +hmac_hmac_sha3_256_cost_base: 52 +hmac_hmac_sha3_256_input_cost_per_byte: 2 +hmac_hmac_sha3_256_input_cost_per_block: 2 +check_zklogin_id_cost_base: 200 +check_zklogin_issuer_cost_base: 200 +bcs_per_byte_serialized_cost: 2 +bcs_legacy_min_output_size_cost: 1 +bcs_failure_cost: 52 +hash_sha2_256_base_cost: 52 +hash_sha2_256_per_byte_cost: 2 +hash_sha2_256_legacy_min_input_len_cost: 1 +hash_sha3_256_base_cost: 52 +hash_sha3_256_per_byte_cost: 2 +hash_sha3_256_legacy_min_input_len_cost: 1 +type_name_get_base_cost: 52 +type_name_get_per_byte_cost: 2 +string_check_utf8_base_cost: 52 +string_check_utf8_per_byte_cost: 2 +string_is_char_boundary_base_cost: 52 +string_sub_string_base_cost: 52 +string_sub_string_per_byte_cost: 2 +string_index_of_base_cost: 52 +string_index_of_per_byte_pattern_cost: 2 +string_index_of_per_byte_searched_cost: 2 +vector_empty_base_cost: 52 +vector_length_base_cost: 52 +vector_push_back_base_cost: 52 +vector_push_back_legacy_per_abstract_memory_unit_cost: 2 +vector_borrow_base_cost: 52 +vector_pop_back_base_cost: 52 +vector_destroy_empty_base_cost: 52 +vector_swap_base_cost: 52 +debug_print_base_cost: 52 +debug_print_stack_trace_base_cost: 52 +execution_version: 3 +consensus_bad_nodes_stake_threshold: 20 +max_jwk_votes_per_validator_per_epoch: 240 +max_age_of_jwk_in_epochs: 1 +random_beacon_reduction_allowed_delta: 800 +random_beacon_reduction_lower_bound: 700 +random_beacon_dkg_timeout_round: 3000 +random_beacon_min_round_interval_ms: 500 +random_beacon_dkg_version: 1 +consensus_max_transaction_size_bytes: 262144 +consensus_max_transactions_in_block_bytes: 524288 +consensus_max_num_transactions_in_block: 512 +max_accumulated_txn_cost_per_object_in_narwhal_commit: 240000000 +max_deferral_rounds_for_congestion_control: 10 +min_checkpoint_interval_ms: 200 +checkpoint_summary_version_specific_data: 1 +max_soft_bundle_size: 5 +bridge_should_try_to_finalize_committee: true +max_accumulated_txn_cost_per_object_in_mysticeti_commit: 18500000 +gas_budget_based_txn_cost_cap_factor: 400000 diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_64.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_64.snap new file mode 100644 index 0000000000000..5353969544779 --- /dev/null +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_64.snap @@ -0,0 +1,329 @@ +--- +source: crates/sui-protocol-config/src/lib.rs +expression: "ProtocolConfig::get_for_version(cur, *chain_id)" +--- +version: 64 +feature_flags: + package_upgrades: true + commit_root_state_digest: true + advance_epoch_start_time_in_safe_mode: true + loaded_child_objects_fixed: true + missing_type_is_compatibility_error: true + scoring_decision_with_validity_cutoff: true + consensus_order_end_of_epoch_last: true + disallow_adding_abilities_on_upgrade: true + disable_invariant_violation_check_in_swap_loc: true + advance_to_highest_supported_protocol_version: true + ban_entry_init: true + package_digest_hash_module: true + disallow_change_struct_type_params_on_upgrade: true + no_extraneous_module_bytes: true + narwhal_versioned_metadata: true + zklogin_auth: true + consensus_transaction_ordering: ByGasPrice + simplified_unwrap_then_delete: true + upgraded_multisig_supported: true + txn_base_cost_as_multiplier: true + shared_object_deletion: true + narwhal_new_leader_election_schedule: true + loaded_child_object_format: true + enable_jwk_consensus_updates: true + end_of_epoch_transaction_supported: true + simple_conservation_checks: true + loaded_child_object_format_type: true + receive_objects: true + random_beacon: true + bridge: true + enable_effects_v2: true + narwhal_certificate_v2: true + verify_legacy_zklogin_address: true + recompute_has_public_transfer_in_execution: true + accept_zklogin_in_multisig: true + include_consensus_digest_in_prologue: true + hardened_otw_check: true + allow_receiving_object_id: true + enable_coin_deny_list: true + enable_group_ops_native_functions: true + reject_mutable_random_on_entry_functions: true + per_object_congestion_control_mode: TotalTxCount + consensus_choice: Mysticeti + consensus_network: Tonic + zklogin_max_epoch_upper_bound_delta: 30 + mysticeti_leader_scoring_and_schedule: true + reshare_at_same_initial_version: true + resolve_abort_locations_to_package_id: true + mysticeti_use_committed_subdag_digest: true + record_consensus_determined_version_assignments_in_prologue: true + fresh_vm_on_framework_upgrade: true + prepend_prologue_tx_in_consensus_commit_in_checkpoints: true + mysticeti_num_leaders_per_round: 1 + soft_bundle: true + enable_coin_deny_list_v2: true + rethrow_serialization_type_layout_errors: true + consensus_round_prober: true + validate_identifier_inputs: true + relocate_event_module: true +max_tx_size_bytes: 131072 +max_input_objects: 2048 +max_size_written_objects: 5000000 +max_size_written_objects_system_tx: 50000000 +max_serialized_tx_effects_size_bytes: 524288 +max_serialized_tx_effects_size_bytes_system_tx: 8388608 +max_gas_payment_objects: 256 +max_modules_in_publish: 64 +max_package_dependencies: 32 +max_arguments: 512 +max_type_arguments: 16 +max_type_argument_depth: 16 +max_pure_argument_size: 16384 +max_programmable_tx_commands: 1024 +move_binary_format_version: 7 +min_move_binary_format_version: 6 +binary_module_handles: 100 +binary_struct_handles: 300 +binary_function_handles: 1500 +binary_function_instantiations: 750 +binary_signatures: 1000 +binary_constant_pool: 4000 +binary_identifiers: 10000 +binary_address_identifiers: 100 +binary_struct_defs: 200 +binary_struct_def_instantiations: 100 +binary_function_defs: 1000 +binary_field_handles: 500 +binary_field_instantiations: 250 +binary_friend_decls: 100 +max_move_object_size: 256000 +max_move_package_size: 102400 +max_publish_or_upgrade_per_ptb: 5 +max_tx_gas: 50000000000 +max_gas_price: 100000 +max_gas_computation_bucket: 5000000 +gas_rounding_step: 1000 +max_loop_depth: 5 +max_generic_instantiation_length: 32 +max_function_parameters: 128 +max_basic_blocks: 1024 +max_value_stack_size: 1024 +max_type_nodes: 256 +max_push_size: 10000 +max_struct_definitions: 200 +max_function_definitions: 1000 +max_fields_in_struct: 32 +max_dependency_depth: 100 +max_num_event_emit: 1024 +max_num_new_move_object_ids: 2048 +max_num_new_move_object_ids_system_tx: 32768 +max_num_deleted_move_object_ids: 2048 +max_num_deleted_move_object_ids_system_tx: 32768 +max_num_transferred_move_object_ids: 2048 +max_num_transferred_move_object_ids_system_tx: 32768 +max_event_emit_size: 256000 +max_event_emit_size_total: 65536000 +max_move_vector_len: 262144 +max_move_identifier_len: 128 +max_move_value_depth: 128 +max_back_edges_per_function: 10000 +max_back_edges_per_module: 10000 +max_verifier_meter_ticks_per_function: 16000000 +max_meter_ticks_per_module: 16000000 +max_meter_ticks_per_package: 16000000 +object_runtime_max_num_cached_objects: 1000 +object_runtime_max_num_cached_objects_system_tx: 16000 +object_runtime_max_num_store_entries: 1000 +object_runtime_max_num_store_entries_system_tx: 16000 +base_tx_cost_fixed: 1000 +package_publish_cost_fixed: 1000 +base_tx_cost_per_byte: 0 +package_publish_cost_per_byte: 80 +obj_access_cost_read_per_byte: 15 +obj_access_cost_mutate_per_byte: 40 +obj_access_cost_delete_per_byte: 40 +obj_access_cost_verify_per_byte: 200 +max_type_to_layout_nodes: 512 +gas_model_version: 8 +obj_data_cost_refundable: 100 +obj_metadata_cost_non_refundable: 50 +storage_rebate_rate: 9900 +storage_fund_reinvest_rate: 500 +reward_slashing_rate: 10000 +storage_gas_price: 76 +max_transactions_per_checkpoint: 10000 +max_checkpoint_size_bytes: 31457280 +buffer_stake_for_protocol_upgrade_bps: 5000 +address_from_bytes_cost_base: 52 +address_to_u256_cost_base: 52 +address_from_u256_cost_base: 52 +config_read_setting_impl_cost_base: 100 +config_read_setting_impl_cost_per_byte: 40 +dynamic_field_hash_type_and_key_cost_base: 100 +dynamic_field_hash_type_and_key_type_cost_per_byte: 2 +dynamic_field_hash_type_and_key_value_cost_per_byte: 2 +dynamic_field_hash_type_and_key_type_tag_cost_per_byte: 2 +dynamic_field_add_child_object_cost_base: 100 +dynamic_field_add_child_object_type_cost_per_byte: 10 +dynamic_field_add_child_object_value_cost_per_byte: 10 +dynamic_field_add_child_object_struct_tag_cost_per_byte: 10 +dynamic_field_borrow_child_object_cost_base: 100 +dynamic_field_borrow_child_object_child_ref_cost_per_byte: 10 +dynamic_field_borrow_child_object_type_cost_per_byte: 10 +dynamic_field_remove_child_object_cost_base: 100 +dynamic_field_remove_child_object_child_cost_per_byte: 2 +dynamic_field_remove_child_object_type_cost_per_byte: 2 +dynamic_field_has_child_object_cost_base: 100 +dynamic_field_has_child_object_with_ty_cost_base: 100 +dynamic_field_has_child_object_with_ty_type_cost_per_byte: 2 +dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: 2 +event_emit_cost_base: 52 +event_emit_value_size_derivation_cost_per_byte: 2 +event_emit_tag_size_derivation_cost_per_byte: 5 +event_emit_output_cost_per_byte: 10 +object_borrow_uid_cost_base: 52 +object_delete_impl_cost_base: 52 +object_record_new_uid_cost_base: 52 +transfer_transfer_internal_cost_base: 52 +transfer_freeze_object_cost_base: 52 +transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 +tx_context_derive_id_cost_base: 52 +types_is_one_time_witness_cost_base: 52 +types_is_one_time_witness_type_tag_cost_per_byte: 2 +types_is_one_time_witness_type_cost_per_byte: 2 +validator_validate_metadata_cost_base: 52 +validator_validate_metadata_data_cost_per_byte: 2 +crypto_invalid_arguments_cost: 100 +bls12381_bls12381_min_sig_verify_cost_base: 52 +bls12381_bls12381_min_sig_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_sig_verify_msg_cost_per_block: 2 +bls12381_bls12381_min_pk_verify_cost_base: 52 +bls12381_bls12381_min_pk_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_pk_verify_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_keccak256_cost_base: 52 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_sha256_cost_base: 52 +ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_k1_decompress_pubkey_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_k1_secp256k1_verify_sha256_cost_base: 52 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_keccak256_cost_base: 52 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_sha256_cost_base: 52 +ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_keccak256_cost_base: 52 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_sha256_cost_base: 52 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: 2 +ecvrf_ecvrf_verify_cost_base: 52 +ecvrf_ecvrf_verify_alpha_string_cost_per_byte: 2 +ecvrf_ecvrf_verify_alpha_string_cost_per_block: 2 +ed25519_ed25519_verify_cost_base: 52 +ed25519_ed25519_verify_msg_cost_per_byte: 2 +ed25519_ed25519_verify_msg_cost_per_block: 2 +groth16_prepare_verifying_key_bls12381_cost_base: 52 +groth16_prepare_verifying_key_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_public_input_cost_per_byte: 2 +hash_blake2b256_cost_base: 52 +hash_blake2b256_data_cost_per_byte: 2 +hash_blake2b256_data_cost_per_block: 2 +hash_keccak256_cost_base: 52 +hash_keccak256_data_cost_per_byte: 2 +hash_keccak256_data_cost_per_block: 2 +group_ops_bls12381_decode_scalar_cost: 52 +group_ops_bls12381_decode_g1_cost: 52 +group_ops_bls12381_decode_g2_cost: 52 +group_ops_bls12381_decode_gt_cost: 52 +group_ops_bls12381_scalar_add_cost: 52 +group_ops_bls12381_g1_add_cost: 52 +group_ops_bls12381_g2_add_cost: 52 +group_ops_bls12381_gt_add_cost: 52 +group_ops_bls12381_scalar_sub_cost: 52 +group_ops_bls12381_g1_sub_cost: 52 +group_ops_bls12381_g2_sub_cost: 52 +group_ops_bls12381_gt_sub_cost: 52 +group_ops_bls12381_scalar_mul_cost: 52 +group_ops_bls12381_g1_mul_cost: 52 +group_ops_bls12381_g2_mul_cost: 52 +group_ops_bls12381_gt_mul_cost: 52 +group_ops_bls12381_scalar_div_cost: 52 +group_ops_bls12381_g1_div_cost: 52 +group_ops_bls12381_g2_div_cost: 52 +group_ops_bls12381_gt_div_cost: 52 +group_ops_bls12381_g1_hash_to_base_cost: 52 +group_ops_bls12381_g2_hash_to_base_cost: 52 +group_ops_bls12381_g1_hash_to_cost_per_byte: 2 +group_ops_bls12381_g2_hash_to_cost_per_byte: 2 +group_ops_bls12381_g1_msm_base_cost: 52 +group_ops_bls12381_g2_msm_base_cost: 52 +group_ops_bls12381_g1_msm_base_cost_per_input: 52 +group_ops_bls12381_g2_msm_base_cost_per_input: 52 +group_ops_bls12381_msm_max_len: 32 +group_ops_bls12381_pairing_cost: 52 +hmac_hmac_sha3_256_cost_base: 52 +hmac_hmac_sha3_256_input_cost_per_byte: 2 +hmac_hmac_sha3_256_input_cost_per_block: 2 +check_zklogin_id_cost_base: 200 +check_zklogin_issuer_cost_base: 200 +bcs_per_byte_serialized_cost: 2 +bcs_legacy_min_output_size_cost: 1 +bcs_failure_cost: 52 +hash_sha2_256_base_cost: 52 +hash_sha2_256_per_byte_cost: 2 +hash_sha2_256_legacy_min_input_len_cost: 1 +hash_sha3_256_base_cost: 52 +hash_sha3_256_per_byte_cost: 2 +hash_sha3_256_legacy_min_input_len_cost: 1 +type_name_get_base_cost: 52 +type_name_get_per_byte_cost: 2 +string_check_utf8_base_cost: 52 +string_check_utf8_per_byte_cost: 2 +string_is_char_boundary_base_cost: 52 +string_sub_string_base_cost: 52 +string_sub_string_per_byte_cost: 2 +string_index_of_base_cost: 52 +string_index_of_per_byte_pattern_cost: 2 +string_index_of_per_byte_searched_cost: 2 +vector_empty_base_cost: 52 +vector_length_base_cost: 52 +vector_push_back_base_cost: 52 +vector_push_back_legacy_per_abstract_memory_unit_cost: 2 +vector_borrow_base_cost: 52 +vector_pop_back_base_cost: 52 +vector_destroy_empty_base_cost: 52 +vector_swap_base_cost: 52 +debug_print_base_cost: 52 +debug_print_stack_trace_base_cost: 52 +execution_version: 3 +consensus_bad_nodes_stake_threshold: 20 +max_jwk_votes_per_validator_per_epoch: 240 +max_age_of_jwk_in_epochs: 1 +random_beacon_reduction_allowed_delta: 800 +random_beacon_reduction_lower_bound: 700 +random_beacon_dkg_timeout_round: 3000 +random_beacon_min_round_interval_ms: 500 +random_beacon_dkg_version: 1 +consensus_max_transaction_size_bytes: 262144 +consensus_max_transactions_in_block_bytes: 524288 +consensus_max_num_transactions_in_block: 512 +max_accumulated_txn_cost_per_object_in_narwhal_commit: 40 +max_deferral_rounds_for_congestion_control: 10 +min_checkpoint_interval_ms: 200 +checkpoint_summary_version_specific_data: 1 +max_soft_bundle_size: 5 +bridge_should_try_to_finalize_committee: true +max_accumulated_txn_cost_per_object_in_mysticeti_commit: 3 +gas_budget_based_txn_cost_cap_factor: 400000 + diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_65.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_65.snap new file mode 100644 index 0000000000000..2a201ab2b494f --- /dev/null +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Mainnet_version_65.snap @@ -0,0 +1,330 @@ +--- +source: crates/sui-protocol-config/src/lib.rs +expression: "ProtocolConfig::get_for_version(cur, *chain_id)" +--- +version: 65 +feature_flags: + package_upgrades: true + commit_root_state_digest: true + advance_epoch_start_time_in_safe_mode: true + loaded_child_objects_fixed: true + missing_type_is_compatibility_error: true + scoring_decision_with_validity_cutoff: true + consensus_order_end_of_epoch_last: true + disallow_adding_abilities_on_upgrade: true + disable_invariant_violation_check_in_swap_loc: true + advance_to_highest_supported_protocol_version: true + ban_entry_init: true + package_digest_hash_module: true + disallow_change_struct_type_params_on_upgrade: true + no_extraneous_module_bytes: true + narwhal_versioned_metadata: true + zklogin_auth: true + consensus_transaction_ordering: ByGasPrice + simplified_unwrap_then_delete: true + upgraded_multisig_supported: true + txn_base_cost_as_multiplier: true + shared_object_deletion: true + narwhal_new_leader_election_schedule: true + loaded_child_object_format: true + enable_jwk_consensus_updates: true + end_of_epoch_transaction_supported: true + simple_conservation_checks: true + loaded_child_object_format_type: true + receive_objects: true + random_beacon: true + bridge: true + enable_effects_v2: true + narwhal_certificate_v2: true + verify_legacy_zklogin_address: true + recompute_has_public_transfer_in_execution: true + accept_zklogin_in_multisig: true + include_consensus_digest_in_prologue: true + hardened_otw_check: true + allow_receiving_object_id: true + enable_coin_deny_list: true + enable_group_ops_native_functions: true + reject_mutable_random_on_entry_functions: true + per_object_congestion_control_mode: TotalTxCount + consensus_choice: Mysticeti + consensus_network: Tonic + zklogin_max_epoch_upper_bound_delta: 30 + mysticeti_leader_scoring_and_schedule: true + reshare_at_same_initial_version: true + resolve_abort_locations_to_package_id: true + mysticeti_use_committed_subdag_digest: true + record_consensus_determined_version_assignments_in_prologue: true + fresh_vm_on_framework_upgrade: true + prepend_prologue_tx_in_consensus_commit_in_checkpoints: true + mysticeti_num_leaders_per_round: 1 + soft_bundle: true + enable_coin_deny_list_v2: true + rethrow_serialization_type_layout_errors: true + consensus_distributed_vote_scoring_strategy: true + consensus_round_prober: true + validate_identifier_inputs: true + relocate_event_module: true +max_tx_size_bytes: 131072 +max_input_objects: 2048 +max_size_written_objects: 5000000 +max_size_written_objects_system_tx: 50000000 +max_serialized_tx_effects_size_bytes: 524288 +max_serialized_tx_effects_size_bytes_system_tx: 8388608 +max_gas_payment_objects: 256 +max_modules_in_publish: 64 +max_package_dependencies: 32 +max_arguments: 512 +max_type_arguments: 16 +max_type_argument_depth: 16 +max_pure_argument_size: 16384 +max_programmable_tx_commands: 1024 +move_binary_format_version: 7 +min_move_binary_format_version: 6 +binary_module_handles: 100 +binary_struct_handles: 300 +binary_function_handles: 1500 +binary_function_instantiations: 750 +binary_signatures: 1000 +binary_constant_pool: 4000 +binary_identifiers: 10000 +binary_address_identifiers: 100 +binary_struct_defs: 200 +binary_struct_def_instantiations: 100 +binary_function_defs: 1000 +binary_field_handles: 500 +binary_field_instantiations: 250 +binary_friend_decls: 100 +max_move_object_size: 256000 +max_move_package_size: 102400 +max_publish_or_upgrade_per_ptb: 5 +max_tx_gas: 50000000000 +max_gas_price: 100000 +max_gas_computation_bucket: 5000000 +gas_rounding_step: 1000 +max_loop_depth: 5 +max_generic_instantiation_length: 32 +max_function_parameters: 128 +max_basic_blocks: 1024 +max_value_stack_size: 1024 +max_type_nodes: 256 +max_push_size: 10000 +max_struct_definitions: 200 +max_function_definitions: 1000 +max_fields_in_struct: 32 +max_dependency_depth: 100 +max_num_event_emit: 1024 +max_num_new_move_object_ids: 2048 +max_num_new_move_object_ids_system_tx: 32768 +max_num_deleted_move_object_ids: 2048 +max_num_deleted_move_object_ids_system_tx: 32768 +max_num_transferred_move_object_ids: 2048 +max_num_transferred_move_object_ids_system_tx: 32768 +max_event_emit_size: 256000 +max_event_emit_size_total: 65536000 +max_move_vector_len: 262144 +max_move_identifier_len: 128 +max_move_value_depth: 128 +max_back_edges_per_function: 10000 +max_back_edges_per_module: 10000 +max_verifier_meter_ticks_per_function: 16000000 +max_meter_ticks_per_module: 16000000 +max_meter_ticks_per_package: 16000000 +object_runtime_max_num_cached_objects: 1000 +object_runtime_max_num_cached_objects_system_tx: 16000 +object_runtime_max_num_store_entries: 1000 +object_runtime_max_num_store_entries_system_tx: 16000 +base_tx_cost_fixed: 1000 +package_publish_cost_fixed: 1000 +base_tx_cost_per_byte: 0 +package_publish_cost_per_byte: 80 +obj_access_cost_read_per_byte: 15 +obj_access_cost_mutate_per_byte: 40 +obj_access_cost_delete_per_byte: 40 +obj_access_cost_verify_per_byte: 200 +max_type_to_layout_nodes: 512 +gas_model_version: 8 +obj_data_cost_refundable: 100 +obj_metadata_cost_non_refundable: 50 +storage_rebate_rate: 9900 +storage_fund_reinvest_rate: 500 +reward_slashing_rate: 10000 +storage_gas_price: 76 +max_transactions_per_checkpoint: 10000 +max_checkpoint_size_bytes: 31457280 +buffer_stake_for_protocol_upgrade_bps: 5000 +address_from_bytes_cost_base: 52 +address_to_u256_cost_base: 52 +address_from_u256_cost_base: 52 +config_read_setting_impl_cost_base: 100 +config_read_setting_impl_cost_per_byte: 40 +dynamic_field_hash_type_and_key_cost_base: 100 +dynamic_field_hash_type_and_key_type_cost_per_byte: 2 +dynamic_field_hash_type_and_key_value_cost_per_byte: 2 +dynamic_field_hash_type_and_key_type_tag_cost_per_byte: 2 +dynamic_field_add_child_object_cost_base: 100 +dynamic_field_add_child_object_type_cost_per_byte: 10 +dynamic_field_add_child_object_value_cost_per_byte: 10 +dynamic_field_add_child_object_struct_tag_cost_per_byte: 10 +dynamic_field_borrow_child_object_cost_base: 100 +dynamic_field_borrow_child_object_child_ref_cost_per_byte: 10 +dynamic_field_borrow_child_object_type_cost_per_byte: 10 +dynamic_field_remove_child_object_cost_base: 100 +dynamic_field_remove_child_object_child_cost_per_byte: 2 +dynamic_field_remove_child_object_type_cost_per_byte: 2 +dynamic_field_has_child_object_cost_base: 100 +dynamic_field_has_child_object_with_ty_cost_base: 100 +dynamic_field_has_child_object_with_ty_type_cost_per_byte: 2 +dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: 2 +event_emit_cost_base: 52 +event_emit_value_size_derivation_cost_per_byte: 2 +event_emit_tag_size_derivation_cost_per_byte: 5 +event_emit_output_cost_per_byte: 10 +object_borrow_uid_cost_base: 52 +object_delete_impl_cost_base: 52 +object_record_new_uid_cost_base: 52 +transfer_transfer_internal_cost_base: 52 +transfer_freeze_object_cost_base: 52 +transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 +tx_context_derive_id_cost_base: 52 +types_is_one_time_witness_cost_base: 52 +types_is_one_time_witness_type_tag_cost_per_byte: 2 +types_is_one_time_witness_type_cost_per_byte: 2 +validator_validate_metadata_cost_base: 52 +validator_validate_metadata_data_cost_per_byte: 2 +crypto_invalid_arguments_cost: 100 +bls12381_bls12381_min_sig_verify_cost_base: 52 +bls12381_bls12381_min_sig_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_sig_verify_msg_cost_per_block: 2 +bls12381_bls12381_min_pk_verify_cost_base: 52 +bls12381_bls12381_min_pk_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_pk_verify_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_keccak256_cost_base: 52 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_sha256_cost_base: 52 +ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_k1_decompress_pubkey_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_k1_secp256k1_verify_sha256_cost_base: 52 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_keccak256_cost_base: 52 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_sha256_cost_base: 52 +ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_keccak256_cost_base: 52 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_sha256_cost_base: 52 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: 2 +ecvrf_ecvrf_verify_cost_base: 52 +ecvrf_ecvrf_verify_alpha_string_cost_per_byte: 2 +ecvrf_ecvrf_verify_alpha_string_cost_per_block: 2 +ed25519_ed25519_verify_cost_base: 52 +ed25519_ed25519_verify_msg_cost_per_byte: 2 +ed25519_ed25519_verify_msg_cost_per_block: 2 +groth16_prepare_verifying_key_bls12381_cost_base: 52 +groth16_prepare_verifying_key_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_public_input_cost_per_byte: 2 +hash_blake2b256_cost_base: 52 +hash_blake2b256_data_cost_per_byte: 2 +hash_blake2b256_data_cost_per_block: 2 +hash_keccak256_cost_base: 52 +hash_keccak256_data_cost_per_byte: 2 +hash_keccak256_data_cost_per_block: 2 +group_ops_bls12381_decode_scalar_cost: 52 +group_ops_bls12381_decode_g1_cost: 52 +group_ops_bls12381_decode_g2_cost: 52 +group_ops_bls12381_decode_gt_cost: 52 +group_ops_bls12381_scalar_add_cost: 52 +group_ops_bls12381_g1_add_cost: 52 +group_ops_bls12381_g2_add_cost: 52 +group_ops_bls12381_gt_add_cost: 52 +group_ops_bls12381_scalar_sub_cost: 52 +group_ops_bls12381_g1_sub_cost: 52 +group_ops_bls12381_g2_sub_cost: 52 +group_ops_bls12381_gt_sub_cost: 52 +group_ops_bls12381_scalar_mul_cost: 52 +group_ops_bls12381_g1_mul_cost: 52 +group_ops_bls12381_g2_mul_cost: 52 +group_ops_bls12381_gt_mul_cost: 52 +group_ops_bls12381_scalar_div_cost: 52 +group_ops_bls12381_g1_div_cost: 52 +group_ops_bls12381_g2_div_cost: 52 +group_ops_bls12381_gt_div_cost: 52 +group_ops_bls12381_g1_hash_to_base_cost: 52 +group_ops_bls12381_g2_hash_to_base_cost: 52 +group_ops_bls12381_g1_hash_to_cost_per_byte: 2 +group_ops_bls12381_g2_hash_to_cost_per_byte: 2 +group_ops_bls12381_g1_msm_base_cost: 52 +group_ops_bls12381_g2_msm_base_cost: 52 +group_ops_bls12381_g1_msm_base_cost_per_input: 52 +group_ops_bls12381_g2_msm_base_cost_per_input: 52 +group_ops_bls12381_msm_max_len: 32 +group_ops_bls12381_pairing_cost: 52 +hmac_hmac_sha3_256_cost_base: 52 +hmac_hmac_sha3_256_input_cost_per_byte: 2 +hmac_hmac_sha3_256_input_cost_per_block: 2 +check_zklogin_id_cost_base: 200 +check_zklogin_issuer_cost_base: 200 +bcs_per_byte_serialized_cost: 2 +bcs_legacy_min_output_size_cost: 1 +bcs_failure_cost: 52 +hash_sha2_256_base_cost: 52 +hash_sha2_256_per_byte_cost: 2 +hash_sha2_256_legacy_min_input_len_cost: 1 +hash_sha3_256_base_cost: 52 +hash_sha3_256_per_byte_cost: 2 +hash_sha3_256_legacy_min_input_len_cost: 1 +type_name_get_base_cost: 52 +type_name_get_per_byte_cost: 2 +string_check_utf8_base_cost: 52 +string_check_utf8_per_byte_cost: 2 +string_is_char_boundary_base_cost: 52 +string_sub_string_base_cost: 52 +string_sub_string_per_byte_cost: 2 +string_index_of_base_cost: 52 +string_index_of_per_byte_pattern_cost: 2 +string_index_of_per_byte_searched_cost: 2 +vector_empty_base_cost: 52 +vector_length_base_cost: 52 +vector_push_back_base_cost: 52 +vector_push_back_legacy_per_abstract_memory_unit_cost: 2 +vector_borrow_base_cost: 52 +vector_pop_back_base_cost: 52 +vector_destroy_empty_base_cost: 52 +vector_swap_base_cost: 52 +debug_print_base_cost: 52 +debug_print_stack_trace_base_cost: 52 +execution_version: 3 +consensus_bad_nodes_stake_threshold: 20 +max_jwk_votes_per_validator_per_epoch: 240 +max_age_of_jwk_in_epochs: 1 +random_beacon_reduction_allowed_delta: 800 +random_beacon_reduction_lower_bound: 700 +random_beacon_dkg_timeout_round: 3000 +random_beacon_min_round_interval_ms: 500 +random_beacon_dkg_version: 1 +consensus_max_transaction_size_bytes: 262144 +consensus_max_transactions_in_block_bytes: 524288 +consensus_max_num_transactions_in_block: 512 +max_accumulated_txn_cost_per_object_in_narwhal_commit: 40 +max_deferral_rounds_for_congestion_control: 10 +min_checkpoint_interval_ms: 200 +checkpoint_summary_version_specific_data: 1 +max_soft_bundle_size: 5 +bridge_should_try_to_finalize_committee: true +max_accumulated_txn_cost_per_object_in_mysticeti_commit: 3 +gas_budget_based_txn_cost_cap_factor: 400000 + diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_61.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_61.snap new file mode 100644 index 0000000000000..a91875c19e377 --- /dev/null +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_61.snap @@ -0,0 +1,328 @@ +--- +source: crates/sui-protocol-config/src/lib.rs +expression: "ProtocolConfig::get_for_version(cur, *chain_id)" +--- +version: 61 +feature_flags: + package_upgrades: true + commit_root_state_digest: true + advance_epoch_start_time_in_safe_mode: true + loaded_child_objects_fixed: true + missing_type_is_compatibility_error: true + scoring_decision_with_validity_cutoff: true + consensus_order_end_of_epoch_last: true + disallow_adding_abilities_on_upgrade: true + disable_invariant_violation_check_in_swap_loc: true + advance_to_highest_supported_protocol_version: true + ban_entry_init: true + package_digest_hash_module: true + disallow_change_struct_type_params_on_upgrade: true + no_extraneous_module_bytes: true + narwhal_versioned_metadata: true + zklogin_auth: true + consensus_transaction_ordering: ByGasPrice + simplified_unwrap_then_delete: true + upgraded_multisig_supported: true + txn_base_cost_as_multiplier: true + shared_object_deletion: true + narwhal_new_leader_election_schedule: true + loaded_child_object_format: true + enable_jwk_consensus_updates: true + end_of_epoch_transaction_supported: true + simple_conservation_checks: true + loaded_child_object_format_type: true + receive_objects: true + random_beacon: true + bridge: true + enable_effects_v2: true + narwhal_certificate_v2: true + verify_legacy_zklogin_address: true + recompute_has_public_transfer_in_execution: true + accept_zklogin_in_multisig: true + include_consensus_digest_in_prologue: true + hardened_otw_check: true + allow_receiving_object_id: true + enable_coin_deny_list: true + enable_group_ops_native_functions: true + reject_mutable_random_on_entry_functions: true + per_object_congestion_control_mode: TotalTxCount + consensus_choice: Mysticeti + consensus_network: Tonic + zklogin_max_epoch_upper_bound_delta: 30 + mysticeti_leader_scoring_and_schedule: true + reshare_at_same_initial_version: true + resolve_abort_locations_to_package_id: true + mysticeti_use_committed_subdag_digest: true + record_consensus_determined_version_assignments_in_prologue: true + fresh_vm_on_framework_upgrade: true + prepend_prologue_tx_in_consensus_commit_in_checkpoints: true + mysticeti_num_leaders_per_round: 1 + soft_bundle: true + enable_coin_deny_list_v2: true + rethrow_serialization_type_layout_errors: true + consensus_distributed_vote_scoring_strategy: true + consensus_round_prober: true + validate_identifier_inputs: true +max_tx_size_bytes: 131072 +max_input_objects: 2048 +max_size_written_objects: 5000000 +max_size_written_objects_system_tx: 50000000 +max_serialized_tx_effects_size_bytes: 524288 +max_serialized_tx_effects_size_bytes_system_tx: 8388608 +max_gas_payment_objects: 256 +max_modules_in_publish: 64 +max_package_dependencies: 32 +max_arguments: 512 +max_type_arguments: 16 +max_type_argument_depth: 16 +max_pure_argument_size: 16384 +max_programmable_tx_commands: 1024 +move_binary_format_version: 7 +min_move_binary_format_version: 6 +binary_module_handles: 100 +binary_struct_handles: 300 +binary_function_handles: 1500 +binary_function_instantiations: 750 +binary_signatures: 1000 +binary_constant_pool: 4000 +binary_identifiers: 10000 +binary_address_identifiers: 100 +binary_struct_defs: 200 +binary_struct_def_instantiations: 100 +binary_function_defs: 1000 +binary_field_handles: 500 +binary_field_instantiations: 250 +binary_friend_decls: 100 +max_move_object_size: 256000 +max_move_package_size: 102400 +max_publish_or_upgrade_per_ptb: 5 +max_tx_gas: 50000000000 +max_gas_price: 100000 +max_gas_computation_bucket: 5000000 +gas_rounding_step: 1000 +max_loop_depth: 5 +max_generic_instantiation_length: 32 +max_function_parameters: 128 +max_basic_blocks: 1024 +max_value_stack_size: 1024 +max_type_nodes: 256 +max_push_size: 10000 +max_struct_definitions: 200 +max_function_definitions: 1000 +max_fields_in_struct: 32 +max_dependency_depth: 100 +max_num_event_emit: 1024 +max_num_new_move_object_ids: 2048 +max_num_new_move_object_ids_system_tx: 32768 +max_num_deleted_move_object_ids: 2048 +max_num_deleted_move_object_ids_system_tx: 32768 +max_num_transferred_move_object_ids: 2048 +max_num_transferred_move_object_ids_system_tx: 32768 +max_event_emit_size: 256000 +max_event_emit_size_total: 65536000 +max_move_vector_len: 262144 +max_move_identifier_len: 128 +max_move_value_depth: 128 +max_back_edges_per_function: 10000 +max_back_edges_per_module: 10000 +max_verifier_meter_ticks_per_function: 16000000 +max_meter_ticks_per_module: 16000000 +max_meter_ticks_per_package: 16000000 +object_runtime_max_num_cached_objects: 1000 +object_runtime_max_num_cached_objects_system_tx: 16000 +object_runtime_max_num_store_entries: 1000 +object_runtime_max_num_store_entries_system_tx: 16000 +base_tx_cost_fixed: 1000 +package_publish_cost_fixed: 1000 +base_tx_cost_per_byte: 0 +package_publish_cost_per_byte: 80 +obj_access_cost_read_per_byte: 15 +obj_access_cost_mutate_per_byte: 40 +obj_access_cost_delete_per_byte: 40 +obj_access_cost_verify_per_byte: 200 +max_type_to_layout_nodes: 512 +gas_model_version: 8 +obj_data_cost_refundable: 100 +obj_metadata_cost_non_refundable: 50 +storage_rebate_rate: 9900 +storage_fund_reinvest_rate: 500 +reward_slashing_rate: 10000 +storage_gas_price: 76 +max_transactions_per_checkpoint: 10000 +max_checkpoint_size_bytes: 31457280 +buffer_stake_for_protocol_upgrade_bps: 5000 +address_from_bytes_cost_base: 52 +address_to_u256_cost_base: 52 +address_from_u256_cost_base: 52 +config_read_setting_impl_cost_base: 100 +config_read_setting_impl_cost_per_byte: 40 +dynamic_field_hash_type_and_key_cost_base: 100 +dynamic_field_hash_type_and_key_type_cost_per_byte: 2 +dynamic_field_hash_type_and_key_value_cost_per_byte: 2 +dynamic_field_hash_type_and_key_type_tag_cost_per_byte: 2 +dynamic_field_add_child_object_cost_base: 100 +dynamic_field_add_child_object_type_cost_per_byte: 10 +dynamic_field_add_child_object_value_cost_per_byte: 10 +dynamic_field_add_child_object_struct_tag_cost_per_byte: 10 +dynamic_field_borrow_child_object_cost_base: 100 +dynamic_field_borrow_child_object_child_ref_cost_per_byte: 10 +dynamic_field_borrow_child_object_type_cost_per_byte: 10 +dynamic_field_remove_child_object_cost_base: 100 +dynamic_field_remove_child_object_child_cost_per_byte: 2 +dynamic_field_remove_child_object_type_cost_per_byte: 2 +dynamic_field_has_child_object_cost_base: 100 +dynamic_field_has_child_object_with_ty_cost_base: 100 +dynamic_field_has_child_object_with_ty_type_cost_per_byte: 2 +dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: 2 +event_emit_cost_base: 52 +event_emit_value_size_derivation_cost_per_byte: 2 +event_emit_tag_size_derivation_cost_per_byte: 5 +event_emit_output_cost_per_byte: 10 +object_borrow_uid_cost_base: 52 +object_delete_impl_cost_base: 52 +object_record_new_uid_cost_base: 52 +transfer_transfer_internal_cost_base: 52 +transfer_freeze_object_cost_base: 52 +transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 +tx_context_derive_id_cost_base: 52 +types_is_one_time_witness_cost_base: 52 +types_is_one_time_witness_type_tag_cost_per_byte: 2 +types_is_one_time_witness_type_cost_per_byte: 2 +validator_validate_metadata_cost_base: 52 +validator_validate_metadata_data_cost_per_byte: 2 +crypto_invalid_arguments_cost: 100 +bls12381_bls12381_min_sig_verify_cost_base: 52 +bls12381_bls12381_min_sig_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_sig_verify_msg_cost_per_block: 2 +bls12381_bls12381_min_pk_verify_cost_base: 52 +bls12381_bls12381_min_pk_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_pk_verify_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_keccak256_cost_base: 52 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_sha256_cost_base: 52 +ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_k1_decompress_pubkey_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_k1_secp256k1_verify_sha256_cost_base: 52 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_keccak256_cost_base: 52 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_sha256_cost_base: 52 +ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_keccak256_cost_base: 52 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_sha256_cost_base: 52 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: 2 +ecvrf_ecvrf_verify_cost_base: 52 +ecvrf_ecvrf_verify_alpha_string_cost_per_byte: 2 +ecvrf_ecvrf_verify_alpha_string_cost_per_block: 2 +ed25519_ed25519_verify_cost_base: 52 +ed25519_ed25519_verify_msg_cost_per_byte: 2 +ed25519_ed25519_verify_msg_cost_per_block: 2 +groth16_prepare_verifying_key_bls12381_cost_base: 52 +groth16_prepare_verifying_key_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_public_input_cost_per_byte: 2 +hash_blake2b256_cost_base: 52 +hash_blake2b256_data_cost_per_byte: 2 +hash_blake2b256_data_cost_per_block: 2 +hash_keccak256_cost_base: 52 +hash_keccak256_data_cost_per_byte: 2 +hash_keccak256_data_cost_per_block: 2 +group_ops_bls12381_decode_scalar_cost: 52 +group_ops_bls12381_decode_g1_cost: 52 +group_ops_bls12381_decode_g2_cost: 52 +group_ops_bls12381_decode_gt_cost: 52 +group_ops_bls12381_scalar_add_cost: 52 +group_ops_bls12381_g1_add_cost: 52 +group_ops_bls12381_g2_add_cost: 52 +group_ops_bls12381_gt_add_cost: 52 +group_ops_bls12381_scalar_sub_cost: 52 +group_ops_bls12381_g1_sub_cost: 52 +group_ops_bls12381_g2_sub_cost: 52 +group_ops_bls12381_gt_sub_cost: 52 +group_ops_bls12381_scalar_mul_cost: 52 +group_ops_bls12381_g1_mul_cost: 52 +group_ops_bls12381_g2_mul_cost: 52 +group_ops_bls12381_gt_mul_cost: 52 +group_ops_bls12381_scalar_div_cost: 52 +group_ops_bls12381_g1_div_cost: 52 +group_ops_bls12381_g2_div_cost: 52 +group_ops_bls12381_gt_div_cost: 52 +group_ops_bls12381_g1_hash_to_base_cost: 52 +group_ops_bls12381_g2_hash_to_base_cost: 52 +group_ops_bls12381_g1_hash_to_cost_per_byte: 2 +group_ops_bls12381_g2_hash_to_cost_per_byte: 2 +group_ops_bls12381_g1_msm_base_cost: 52 +group_ops_bls12381_g2_msm_base_cost: 52 +group_ops_bls12381_g1_msm_base_cost_per_input: 52 +group_ops_bls12381_g2_msm_base_cost_per_input: 52 +group_ops_bls12381_msm_max_len: 32 +group_ops_bls12381_pairing_cost: 52 +hmac_hmac_sha3_256_cost_base: 52 +hmac_hmac_sha3_256_input_cost_per_byte: 2 +hmac_hmac_sha3_256_input_cost_per_block: 2 +check_zklogin_id_cost_base: 200 +check_zklogin_issuer_cost_base: 200 +bcs_per_byte_serialized_cost: 2 +bcs_legacy_min_output_size_cost: 1 +bcs_failure_cost: 52 +hash_sha2_256_base_cost: 52 +hash_sha2_256_per_byte_cost: 2 +hash_sha2_256_legacy_min_input_len_cost: 1 +hash_sha3_256_base_cost: 52 +hash_sha3_256_per_byte_cost: 2 +hash_sha3_256_legacy_min_input_len_cost: 1 +type_name_get_base_cost: 52 +type_name_get_per_byte_cost: 2 +string_check_utf8_base_cost: 52 +string_check_utf8_per_byte_cost: 2 +string_is_char_boundary_base_cost: 52 +string_sub_string_base_cost: 52 +string_sub_string_per_byte_cost: 2 +string_index_of_base_cost: 52 +string_index_of_per_byte_pattern_cost: 2 +string_index_of_per_byte_searched_cost: 2 +vector_empty_base_cost: 52 +vector_length_base_cost: 52 +vector_push_back_base_cost: 52 +vector_push_back_legacy_per_abstract_memory_unit_cost: 2 +vector_borrow_base_cost: 52 +vector_pop_back_base_cost: 52 +vector_destroy_empty_base_cost: 52 +vector_swap_base_cost: 52 +debug_print_base_cost: 52 +debug_print_stack_trace_base_cost: 52 +execution_version: 3 +consensus_bad_nodes_stake_threshold: 20 +max_jwk_votes_per_validator_per_epoch: 240 +max_age_of_jwk_in_epochs: 1 +random_beacon_reduction_allowed_delta: 800 +random_beacon_reduction_lower_bound: 700 +random_beacon_dkg_timeout_round: 3000 +random_beacon_min_round_interval_ms: 500 +random_beacon_dkg_version: 1 +consensus_max_transaction_size_bytes: 262144 +consensus_max_transactions_in_block_bytes: 524288 +consensus_max_num_transactions_in_block: 512 +max_accumulated_txn_cost_per_object_in_narwhal_commit: 100 +max_deferral_rounds_for_congestion_control: 10 +min_checkpoint_interval_ms: 200 +checkpoint_summary_version_specific_data: 1 +max_soft_bundle_size: 5 +bridge_should_try_to_finalize_committee: true +max_accumulated_txn_cost_per_object_in_mysticeti_commit: 10 + diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_62.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_62.snap new file mode 100644 index 0000000000000..6b44c5932bae3 --- /dev/null +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_62.snap @@ -0,0 +1,329 @@ +--- +source: crates/sui-protocol-config/src/lib.rs +expression: "ProtocolConfig::get_for_version(cur, *chain_id)" +--- +version: 62 +feature_flags: + package_upgrades: true + commit_root_state_digest: true + advance_epoch_start_time_in_safe_mode: true + loaded_child_objects_fixed: true + missing_type_is_compatibility_error: true + scoring_decision_with_validity_cutoff: true + consensus_order_end_of_epoch_last: true + disallow_adding_abilities_on_upgrade: true + disable_invariant_violation_check_in_swap_loc: true + advance_to_highest_supported_protocol_version: true + ban_entry_init: true + package_digest_hash_module: true + disallow_change_struct_type_params_on_upgrade: true + no_extraneous_module_bytes: true + narwhal_versioned_metadata: true + zklogin_auth: true + consensus_transaction_ordering: ByGasPrice + simplified_unwrap_then_delete: true + upgraded_multisig_supported: true + txn_base_cost_as_multiplier: true + shared_object_deletion: true + narwhal_new_leader_election_schedule: true + loaded_child_object_format: true + enable_jwk_consensus_updates: true + end_of_epoch_transaction_supported: true + simple_conservation_checks: true + loaded_child_object_format_type: true + receive_objects: true + random_beacon: true + bridge: true + enable_effects_v2: true + narwhal_certificate_v2: true + verify_legacy_zklogin_address: true + recompute_has_public_transfer_in_execution: true + accept_zklogin_in_multisig: true + include_consensus_digest_in_prologue: true + hardened_otw_check: true + allow_receiving_object_id: true + enable_coin_deny_list: true + enable_group_ops_native_functions: true + reject_mutable_random_on_entry_functions: true + per_object_congestion_control_mode: TotalTxCount + consensus_choice: Mysticeti + consensus_network: Tonic + zklogin_max_epoch_upper_bound_delta: 30 + mysticeti_leader_scoring_and_schedule: true + reshare_at_same_initial_version: true + resolve_abort_locations_to_package_id: true + mysticeti_use_committed_subdag_digest: true + record_consensus_determined_version_assignments_in_prologue: true + fresh_vm_on_framework_upgrade: true + prepend_prologue_tx_in_consensus_commit_in_checkpoints: true + mysticeti_num_leaders_per_round: 1 + soft_bundle: true + enable_coin_deny_list_v2: true + rethrow_serialization_type_layout_errors: true + consensus_distributed_vote_scoring_strategy: true + consensus_round_prober: true + validate_identifier_inputs: true + relocate_event_module: true +max_tx_size_bytes: 131072 +max_input_objects: 2048 +max_size_written_objects: 5000000 +max_size_written_objects_system_tx: 50000000 +max_serialized_tx_effects_size_bytes: 524288 +max_serialized_tx_effects_size_bytes_system_tx: 8388608 +max_gas_payment_objects: 256 +max_modules_in_publish: 64 +max_package_dependencies: 32 +max_arguments: 512 +max_type_arguments: 16 +max_type_argument_depth: 16 +max_pure_argument_size: 16384 +max_programmable_tx_commands: 1024 +move_binary_format_version: 7 +min_move_binary_format_version: 6 +binary_module_handles: 100 +binary_struct_handles: 300 +binary_function_handles: 1500 +binary_function_instantiations: 750 +binary_signatures: 1000 +binary_constant_pool: 4000 +binary_identifiers: 10000 +binary_address_identifiers: 100 +binary_struct_defs: 200 +binary_struct_def_instantiations: 100 +binary_function_defs: 1000 +binary_field_handles: 500 +binary_field_instantiations: 250 +binary_friend_decls: 100 +max_move_object_size: 256000 +max_move_package_size: 102400 +max_publish_or_upgrade_per_ptb: 5 +max_tx_gas: 50000000000 +max_gas_price: 100000 +max_gas_computation_bucket: 5000000 +gas_rounding_step: 1000 +max_loop_depth: 5 +max_generic_instantiation_length: 32 +max_function_parameters: 128 +max_basic_blocks: 1024 +max_value_stack_size: 1024 +max_type_nodes: 256 +max_push_size: 10000 +max_struct_definitions: 200 +max_function_definitions: 1000 +max_fields_in_struct: 32 +max_dependency_depth: 100 +max_num_event_emit: 1024 +max_num_new_move_object_ids: 2048 +max_num_new_move_object_ids_system_tx: 32768 +max_num_deleted_move_object_ids: 2048 +max_num_deleted_move_object_ids_system_tx: 32768 +max_num_transferred_move_object_ids: 2048 +max_num_transferred_move_object_ids_system_tx: 32768 +max_event_emit_size: 256000 +max_event_emit_size_total: 65536000 +max_move_vector_len: 262144 +max_move_identifier_len: 128 +max_move_value_depth: 128 +max_back_edges_per_function: 10000 +max_back_edges_per_module: 10000 +max_verifier_meter_ticks_per_function: 16000000 +max_meter_ticks_per_module: 16000000 +max_meter_ticks_per_package: 16000000 +object_runtime_max_num_cached_objects: 1000 +object_runtime_max_num_cached_objects_system_tx: 16000 +object_runtime_max_num_store_entries: 1000 +object_runtime_max_num_store_entries_system_tx: 16000 +base_tx_cost_fixed: 1000 +package_publish_cost_fixed: 1000 +base_tx_cost_per_byte: 0 +package_publish_cost_per_byte: 80 +obj_access_cost_read_per_byte: 15 +obj_access_cost_mutate_per_byte: 40 +obj_access_cost_delete_per_byte: 40 +obj_access_cost_verify_per_byte: 200 +max_type_to_layout_nodes: 512 +gas_model_version: 8 +obj_data_cost_refundable: 100 +obj_metadata_cost_non_refundable: 50 +storage_rebate_rate: 9900 +storage_fund_reinvest_rate: 500 +reward_slashing_rate: 10000 +storage_gas_price: 76 +max_transactions_per_checkpoint: 10000 +max_checkpoint_size_bytes: 31457280 +buffer_stake_for_protocol_upgrade_bps: 5000 +address_from_bytes_cost_base: 52 +address_to_u256_cost_base: 52 +address_from_u256_cost_base: 52 +config_read_setting_impl_cost_base: 100 +config_read_setting_impl_cost_per_byte: 40 +dynamic_field_hash_type_and_key_cost_base: 100 +dynamic_field_hash_type_and_key_type_cost_per_byte: 2 +dynamic_field_hash_type_and_key_value_cost_per_byte: 2 +dynamic_field_hash_type_and_key_type_tag_cost_per_byte: 2 +dynamic_field_add_child_object_cost_base: 100 +dynamic_field_add_child_object_type_cost_per_byte: 10 +dynamic_field_add_child_object_value_cost_per_byte: 10 +dynamic_field_add_child_object_struct_tag_cost_per_byte: 10 +dynamic_field_borrow_child_object_cost_base: 100 +dynamic_field_borrow_child_object_child_ref_cost_per_byte: 10 +dynamic_field_borrow_child_object_type_cost_per_byte: 10 +dynamic_field_remove_child_object_cost_base: 100 +dynamic_field_remove_child_object_child_cost_per_byte: 2 +dynamic_field_remove_child_object_type_cost_per_byte: 2 +dynamic_field_has_child_object_cost_base: 100 +dynamic_field_has_child_object_with_ty_cost_base: 100 +dynamic_field_has_child_object_with_ty_type_cost_per_byte: 2 +dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: 2 +event_emit_cost_base: 52 +event_emit_value_size_derivation_cost_per_byte: 2 +event_emit_tag_size_derivation_cost_per_byte: 5 +event_emit_output_cost_per_byte: 10 +object_borrow_uid_cost_base: 52 +object_delete_impl_cost_base: 52 +object_record_new_uid_cost_base: 52 +transfer_transfer_internal_cost_base: 52 +transfer_freeze_object_cost_base: 52 +transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 +tx_context_derive_id_cost_base: 52 +types_is_one_time_witness_cost_base: 52 +types_is_one_time_witness_type_tag_cost_per_byte: 2 +types_is_one_time_witness_type_cost_per_byte: 2 +validator_validate_metadata_cost_base: 52 +validator_validate_metadata_data_cost_per_byte: 2 +crypto_invalid_arguments_cost: 100 +bls12381_bls12381_min_sig_verify_cost_base: 52 +bls12381_bls12381_min_sig_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_sig_verify_msg_cost_per_block: 2 +bls12381_bls12381_min_pk_verify_cost_base: 52 +bls12381_bls12381_min_pk_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_pk_verify_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_keccak256_cost_base: 52 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_sha256_cost_base: 52 +ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_k1_decompress_pubkey_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_k1_secp256k1_verify_sha256_cost_base: 52 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_keccak256_cost_base: 52 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_sha256_cost_base: 52 +ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_keccak256_cost_base: 52 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_sha256_cost_base: 52 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: 2 +ecvrf_ecvrf_verify_cost_base: 52 +ecvrf_ecvrf_verify_alpha_string_cost_per_byte: 2 +ecvrf_ecvrf_verify_alpha_string_cost_per_block: 2 +ed25519_ed25519_verify_cost_base: 52 +ed25519_ed25519_verify_msg_cost_per_byte: 2 +ed25519_ed25519_verify_msg_cost_per_block: 2 +groth16_prepare_verifying_key_bls12381_cost_base: 52 +groth16_prepare_verifying_key_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_public_input_cost_per_byte: 2 +hash_blake2b256_cost_base: 52 +hash_blake2b256_data_cost_per_byte: 2 +hash_blake2b256_data_cost_per_block: 2 +hash_keccak256_cost_base: 52 +hash_keccak256_data_cost_per_byte: 2 +hash_keccak256_data_cost_per_block: 2 +group_ops_bls12381_decode_scalar_cost: 52 +group_ops_bls12381_decode_g1_cost: 52 +group_ops_bls12381_decode_g2_cost: 52 +group_ops_bls12381_decode_gt_cost: 52 +group_ops_bls12381_scalar_add_cost: 52 +group_ops_bls12381_g1_add_cost: 52 +group_ops_bls12381_g2_add_cost: 52 +group_ops_bls12381_gt_add_cost: 52 +group_ops_bls12381_scalar_sub_cost: 52 +group_ops_bls12381_g1_sub_cost: 52 +group_ops_bls12381_g2_sub_cost: 52 +group_ops_bls12381_gt_sub_cost: 52 +group_ops_bls12381_scalar_mul_cost: 52 +group_ops_bls12381_g1_mul_cost: 52 +group_ops_bls12381_g2_mul_cost: 52 +group_ops_bls12381_gt_mul_cost: 52 +group_ops_bls12381_scalar_div_cost: 52 +group_ops_bls12381_g1_div_cost: 52 +group_ops_bls12381_g2_div_cost: 52 +group_ops_bls12381_gt_div_cost: 52 +group_ops_bls12381_g1_hash_to_base_cost: 52 +group_ops_bls12381_g2_hash_to_base_cost: 52 +group_ops_bls12381_g1_hash_to_cost_per_byte: 2 +group_ops_bls12381_g2_hash_to_cost_per_byte: 2 +group_ops_bls12381_g1_msm_base_cost: 52 +group_ops_bls12381_g2_msm_base_cost: 52 +group_ops_bls12381_g1_msm_base_cost_per_input: 52 +group_ops_bls12381_g2_msm_base_cost_per_input: 52 +group_ops_bls12381_msm_max_len: 32 +group_ops_bls12381_pairing_cost: 52 +hmac_hmac_sha3_256_cost_base: 52 +hmac_hmac_sha3_256_input_cost_per_byte: 2 +hmac_hmac_sha3_256_input_cost_per_block: 2 +check_zklogin_id_cost_base: 200 +check_zklogin_issuer_cost_base: 200 +bcs_per_byte_serialized_cost: 2 +bcs_legacy_min_output_size_cost: 1 +bcs_failure_cost: 52 +hash_sha2_256_base_cost: 52 +hash_sha2_256_per_byte_cost: 2 +hash_sha2_256_legacy_min_input_len_cost: 1 +hash_sha3_256_base_cost: 52 +hash_sha3_256_per_byte_cost: 2 +hash_sha3_256_legacy_min_input_len_cost: 1 +type_name_get_base_cost: 52 +type_name_get_per_byte_cost: 2 +string_check_utf8_base_cost: 52 +string_check_utf8_per_byte_cost: 2 +string_is_char_boundary_base_cost: 52 +string_sub_string_base_cost: 52 +string_sub_string_per_byte_cost: 2 +string_index_of_base_cost: 52 +string_index_of_per_byte_pattern_cost: 2 +string_index_of_per_byte_searched_cost: 2 +vector_empty_base_cost: 52 +vector_length_base_cost: 52 +vector_push_back_base_cost: 52 +vector_push_back_legacy_per_abstract_memory_unit_cost: 2 +vector_borrow_base_cost: 52 +vector_pop_back_base_cost: 52 +vector_destroy_empty_base_cost: 52 +vector_swap_base_cost: 52 +debug_print_base_cost: 52 +debug_print_stack_trace_base_cost: 52 +execution_version: 3 +consensus_bad_nodes_stake_threshold: 20 +max_jwk_votes_per_validator_per_epoch: 240 +max_age_of_jwk_in_epochs: 1 +random_beacon_reduction_allowed_delta: 800 +random_beacon_reduction_lower_bound: 700 +random_beacon_dkg_timeout_round: 3000 +random_beacon_min_round_interval_ms: 500 +random_beacon_dkg_version: 1 +consensus_max_transaction_size_bytes: 262144 +consensus_max_transactions_in_block_bytes: 524288 +consensus_max_num_transactions_in_block: 512 +max_accumulated_txn_cost_per_object_in_narwhal_commit: 100 +max_deferral_rounds_for_congestion_control: 10 +min_checkpoint_interval_ms: 200 +checkpoint_summary_version_specific_data: 1 +max_soft_bundle_size: 5 +bridge_should_try_to_finalize_committee: true +max_accumulated_txn_cost_per_object_in_mysticeti_commit: 10 + diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_63.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_63.snap new file mode 100644 index 0000000000000..785270a636a42 --- /dev/null +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_63.snap @@ -0,0 +1,329 @@ +--- +source: crates/sui-protocol-config/src/lib.rs +expression: "ProtocolConfig::get_for_version(cur, *chain_id)" +--- +version: 63 +feature_flags: + package_upgrades: true + commit_root_state_digest: true + advance_epoch_start_time_in_safe_mode: true + loaded_child_objects_fixed: true + missing_type_is_compatibility_error: true + scoring_decision_with_validity_cutoff: true + consensus_order_end_of_epoch_last: true + disallow_adding_abilities_on_upgrade: true + disable_invariant_violation_check_in_swap_loc: true + advance_to_highest_supported_protocol_version: true + ban_entry_init: true + package_digest_hash_module: true + disallow_change_struct_type_params_on_upgrade: true + no_extraneous_module_bytes: true + narwhal_versioned_metadata: true + zklogin_auth: true + consensus_transaction_ordering: ByGasPrice + simplified_unwrap_then_delete: true + upgraded_multisig_supported: true + txn_base_cost_as_multiplier: true + shared_object_deletion: true + narwhal_new_leader_election_schedule: true + loaded_child_object_format: true + enable_jwk_consensus_updates: true + end_of_epoch_transaction_supported: true + simple_conservation_checks: true + loaded_child_object_format_type: true + receive_objects: true + random_beacon: true + bridge: true + enable_effects_v2: true + narwhal_certificate_v2: true + verify_legacy_zklogin_address: true + recompute_has_public_transfer_in_execution: true + accept_zklogin_in_multisig: true + include_consensus_digest_in_prologue: true + hardened_otw_check: true + allow_receiving_object_id: true + enable_coin_deny_list: true + enable_group_ops_native_functions: true + reject_mutable_random_on_entry_functions: true + per_object_congestion_control_mode: TotalGasBudgetWithCap + consensus_choice: Mysticeti + consensus_network: Tonic + zklogin_max_epoch_upper_bound_delta: 30 + mysticeti_leader_scoring_and_schedule: true + reshare_at_same_initial_version: true + resolve_abort_locations_to_package_id: true + mysticeti_use_committed_subdag_digest: true + record_consensus_determined_version_assignments_in_prologue: true + fresh_vm_on_framework_upgrade: true + prepend_prologue_tx_in_consensus_commit_in_checkpoints: true + mysticeti_num_leaders_per_round: 1 + soft_bundle: true + enable_coin_deny_list_v2: true + rethrow_serialization_type_layout_errors: true + consensus_distributed_vote_scoring_strategy: true + consensus_round_prober: true + validate_identifier_inputs: true + relocate_event_module: true +max_tx_size_bytes: 131072 +max_input_objects: 2048 +max_size_written_objects: 5000000 +max_size_written_objects_system_tx: 50000000 +max_serialized_tx_effects_size_bytes: 524288 +max_serialized_tx_effects_size_bytes_system_tx: 8388608 +max_gas_payment_objects: 256 +max_modules_in_publish: 64 +max_package_dependencies: 32 +max_arguments: 512 +max_type_arguments: 16 +max_type_argument_depth: 16 +max_pure_argument_size: 16384 +max_programmable_tx_commands: 1024 +move_binary_format_version: 7 +min_move_binary_format_version: 6 +binary_module_handles: 100 +binary_struct_handles: 300 +binary_function_handles: 1500 +binary_function_instantiations: 750 +binary_signatures: 1000 +binary_constant_pool: 4000 +binary_identifiers: 10000 +binary_address_identifiers: 100 +binary_struct_defs: 200 +binary_struct_def_instantiations: 100 +binary_function_defs: 1000 +binary_field_handles: 500 +binary_field_instantiations: 250 +binary_friend_decls: 100 +max_move_object_size: 256000 +max_move_package_size: 102400 +max_publish_or_upgrade_per_ptb: 5 +max_tx_gas: 50000000000 +max_gas_price: 100000 +max_gas_computation_bucket: 5000000 +gas_rounding_step: 1000 +max_loop_depth: 5 +max_generic_instantiation_length: 32 +max_function_parameters: 128 +max_basic_blocks: 1024 +max_value_stack_size: 1024 +max_type_nodes: 256 +max_push_size: 10000 +max_struct_definitions: 200 +max_function_definitions: 1000 +max_fields_in_struct: 32 +max_dependency_depth: 100 +max_num_event_emit: 1024 +max_num_new_move_object_ids: 2048 +max_num_new_move_object_ids_system_tx: 32768 +max_num_deleted_move_object_ids: 2048 +max_num_deleted_move_object_ids_system_tx: 32768 +max_num_transferred_move_object_ids: 2048 +max_num_transferred_move_object_ids_system_tx: 32768 +max_event_emit_size: 256000 +max_event_emit_size_total: 65536000 +max_move_vector_len: 262144 +max_move_identifier_len: 128 +max_move_value_depth: 128 +max_back_edges_per_function: 10000 +max_back_edges_per_module: 10000 +max_verifier_meter_ticks_per_function: 16000000 +max_meter_ticks_per_module: 16000000 +max_meter_ticks_per_package: 16000000 +object_runtime_max_num_cached_objects: 1000 +object_runtime_max_num_cached_objects_system_tx: 16000 +object_runtime_max_num_store_entries: 1000 +object_runtime_max_num_store_entries_system_tx: 16000 +base_tx_cost_fixed: 1000 +package_publish_cost_fixed: 1000 +base_tx_cost_per_byte: 0 +package_publish_cost_per_byte: 80 +obj_access_cost_read_per_byte: 15 +obj_access_cost_mutate_per_byte: 40 +obj_access_cost_delete_per_byte: 40 +obj_access_cost_verify_per_byte: 200 +max_type_to_layout_nodes: 512 +gas_model_version: 8 +obj_data_cost_refundable: 100 +obj_metadata_cost_non_refundable: 50 +storage_rebate_rate: 9900 +storage_fund_reinvest_rate: 500 +reward_slashing_rate: 10000 +storage_gas_price: 76 +max_transactions_per_checkpoint: 10000 +max_checkpoint_size_bytes: 31457280 +buffer_stake_for_protocol_upgrade_bps: 5000 +address_from_bytes_cost_base: 52 +address_to_u256_cost_base: 52 +address_from_u256_cost_base: 52 +config_read_setting_impl_cost_base: 100 +config_read_setting_impl_cost_per_byte: 40 +dynamic_field_hash_type_and_key_cost_base: 100 +dynamic_field_hash_type_and_key_type_cost_per_byte: 2 +dynamic_field_hash_type_and_key_value_cost_per_byte: 2 +dynamic_field_hash_type_and_key_type_tag_cost_per_byte: 2 +dynamic_field_add_child_object_cost_base: 100 +dynamic_field_add_child_object_type_cost_per_byte: 10 +dynamic_field_add_child_object_value_cost_per_byte: 10 +dynamic_field_add_child_object_struct_tag_cost_per_byte: 10 +dynamic_field_borrow_child_object_cost_base: 100 +dynamic_field_borrow_child_object_child_ref_cost_per_byte: 10 +dynamic_field_borrow_child_object_type_cost_per_byte: 10 +dynamic_field_remove_child_object_cost_base: 100 +dynamic_field_remove_child_object_child_cost_per_byte: 2 +dynamic_field_remove_child_object_type_cost_per_byte: 2 +dynamic_field_has_child_object_cost_base: 100 +dynamic_field_has_child_object_with_ty_cost_base: 100 +dynamic_field_has_child_object_with_ty_type_cost_per_byte: 2 +dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: 2 +event_emit_cost_base: 52 +event_emit_value_size_derivation_cost_per_byte: 2 +event_emit_tag_size_derivation_cost_per_byte: 5 +event_emit_output_cost_per_byte: 10 +object_borrow_uid_cost_base: 52 +object_delete_impl_cost_base: 52 +object_record_new_uid_cost_base: 52 +transfer_transfer_internal_cost_base: 52 +transfer_freeze_object_cost_base: 52 +transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 +tx_context_derive_id_cost_base: 52 +types_is_one_time_witness_cost_base: 52 +types_is_one_time_witness_type_tag_cost_per_byte: 2 +types_is_one_time_witness_type_cost_per_byte: 2 +validator_validate_metadata_cost_base: 52 +validator_validate_metadata_data_cost_per_byte: 2 +crypto_invalid_arguments_cost: 100 +bls12381_bls12381_min_sig_verify_cost_base: 52 +bls12381_bls12381_min_sig_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_sig_verify_msg_cost_per_block: 2 +bls12381_bls12381_min_pk_verify_cost_base: 52 +bls12381_bls12381_min_pk_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_pk_verify_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_keccak256_cost_base: 52 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_sha256_cost_base: 52 +ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_k1_decompress_pubkey_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_k1_secp256k1_verify_sha256_cost_base: 52 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_keccak256_cost_base: 52 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_sha256_cost_base: 52 +ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_keccak256_cost_base: 52 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_sha256_cost_base: 52 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: 2 +ecvrf_ecvrf_verify_cost_base: 52 +ecvrf_ecvrf_verify_alpha_string_cost_per_byte: 2 +ecvrf_ecvrf_verify_alpha_string_cost_per_block: 2 +ed25519_ed25519_verify_cost_base: 52 +ed25519_ed25519_verify_msg_cost_per_byte: 2 +ed25519_ed25519_verify_msg_cost_per_block: 2 +groth16_prepare_verifying_key_bls12381_cost_base: 52 +groth16_prepare_verifying_key_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_public_input_cost_per_byte: 2 +hash_blake2b256_cost_base: 52 +hash_blake2b256_data_cost_per_byte: 2 +hash_blake2b256_data_cost_per_block: 2 +hash_keccak256_cost_base: 52 +hash_keccak256_data_cost_per_byte: 2 +hash_keccak256_data_cost_per_block: 2 +group_ops_bls12381_decode_scalar_cost: 52 +group_ops_bls12381_decode_g1_cost: 52 +group_ops_bls12381_decode_g2_cost: 52 +group_ops_bls12381_decode_gt_cost: 52 +group_ops_bls12381_scalar_add_cost: 52 +group_ops_bls12381_g1_add_cost: 52 +group_ops_bls12381_g2_add_cost: 52 +group_ops_bls12381_gt_add_cost: 52 +group_ops_bls12381_scalar_sub_cost: 52 +group_ops_bls12381_g1_sub_cost: 52 +group_ops_bls12381_g2_sub_cost: 52 +group_ops_bls12381_gt_sub_cost: 52 +group_ops_bls12381_scalar_mul_cost: 52 +group_ops_bls12381_g1_mul_cost: 52 +group_ops_bls12381_g2_mul_cost: 52 +group_ops_bls12381_gt_mul_cost: 52 +group_ops_bls12381_scalar_div_cost: 52 +group_ops_bls12381_g1_div_cost: 52 +group_ops_bls12381_g2_div_cost: 52 +group_ops_bls12381_gt_div_cost: 52 +group_ops_bls12381_g1_hash_to_base_cost: 52 +group_ops_bls12381_g2_hash_to_base_cost: 52 +group_ops_bls12381_g1_hash_to_cost_per_byte: 2 +group_ops_bls12381_g2_hash_to_cost_per_byte: 2 +group_ops_bls12381_g1_msm_base_cost: 52 +group_ops_bls12381_g2_msm_base_cost: 52 +group_ops_bls12381_g1_msm_base_cost_per_input: 52 +group_ops_bls12381_g2_msm_base_cost_per_input: 52 +group_ops_bls12381_msm_max_len: 32 +group_ops_bls12381_pairing_cost: 52 +hmac_hmac_sha3_256_cost_base: 52 +hmac_hmac_sha3_256_input_cost_per_byte: 2 +hmac_hmac_sha3_256_input_cost_per_block: 2 +check_zklogin_id_cost_base: 200 +check_zklogin_issuer_cost_base: 200 +bcs_per_byte_serialized_cost: 2 +bcs_legacy_min_output_size_cost: 1 +bcs_failure_cost: 52 +hash_sha2_256_base_cost: 52 +hash_sha2_256_per_byte_cost: 2 +hash_sha2_256_legacy_min_input_len_cost: 1 +hash_sha3_256_base_cost: 52 +hash_sha3_256_per_byte_cost: 2 +hash_sha3_256_legacy_min_input_len_cost: 1 +type_name_get_base_cost: 52 +type_name_get_per_byte_cost: 2 +string_check_utf8_base_cost: 52 +string_check_utf8_per_byte_cost: 2 +string_is_char_boundary_base_cost: 52 +string_sub_string_base_cost: 52 +string_sub_string_per_byte_cost: 2 +string_index_of_base_cost: 52 +string_index_of_per_byte_pattern_cost: 2 +string_index_of_per_byte_searched_cost: 2 +vector_empty_base_cost: 52 +vector_length_base_cost: 52 +vector_push_back_base_cost: 52 +vector_push_back_legacy_per_abstract_memory_unit_cost: 2 +vector_borrow_base_cost: 52 +vector_pop_back_base_cost: 52 +vector_destroy_empty_base_cost: 52 +vector_swap_base_cost: 52 +debug_print_base_cost: 52 +debug_print_stack_trace_base_cost: 52 +execution_version: 3 +consensus_bad_nodes_stake_threshold: 20 +max_jwk_votes_per_validator_per_epoch: 240 +max_age_of_jwk_in_epochs: 1 +random_beacon_reduction_allowed_delta: 800 +random_beacon_reduction_lower_bound: 700 +random_beacon_dkg_timeout_round: 3000 +random_beacon_min_round_interval_ms: 500 +random_beacon_dkg_version: 1 +consensus_max_transaction_size_bytes: 262144 +consensus_max_transactions_in_block_bytes: 524288 +consensus_max_num_transactions_in_block: 512 +max_accumulated_txn_cost_per_object_in_narwhal_commit: 240000000 +max_deferral_rounds_for_congestion_control: 10 +min_checkpoint_interval_ms: 200 +checkpoint_summary_version_specific_data: 1 +max_soft_bundle_size: 5 +bridge_should_try_to_finalize_committee: true +max_accumulated_txn_cost_per_object_in_mysticeti_commit: 18500000 +gas_budget_based_txn_cost_cap_factor: 400000 diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_64.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_64.snap new file mode 100644 index 0000000000000..4de54c46bde71 --- /dev/null +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_64.snap @@ -0,0 +1,330 @@ +--- +source: crates/sui-protocol-config/src/lib.rs +expression: "ProtocolConfig::get_for_version(cur, *chain_id)" +--- +version: 64 +feature_flags: + package_upgrades: true + commit_root_state_digest: true + advance_epoch_start_time_in_safe_mode: true + loaded_child_objects_fixed: true + missing_type_is_compatibility_error: true + scoring_decision_with_validity_cutoff: true + consensus_order_end_of_epoch_last: true + disallow_adding_abilities_on_upgrade: true + disable_invariant_violation_check_in_swap_loc: true + advance_to_highest_supported_protocol_version: true + ban_entry_init: true + package_digest_hash_module: true + disallow_change_struct_type_params_on_upgrade: true + no_extraneous_module_bytes: true + narwhal_versioned_metadata: true + zklogin_auth: true + consensus_transaction_ordering: ByGasPrice + simplified_unwrap_then_delete: true + upgraded_multisig_supported: true + txn_base_cost_as_multiplier: true + shared_object_deletion: true + narwhal_new_leader_election_schedule: true + loaded_child_object_format: true + enable_jwk_consensus_updates: true + end_of_epoch_transaction_supported: true + simple_conservation_checks: true + loaded_child_object_format_type: true + receive_objects: true + random_beacon: true + bridge: true + enable_effects_v2: true + narwhal_certificate_v2: true + verify_legacy_zklogin_address: true + recompute_has_public_transfer_in_execution: true + accept_zklogin_in_multisig: true + include_consensus_digest_in_prologue: true + hardened_otw_check: true + allow_receiving_object_id: true + enable_coin_deny_list: true + enable_group_ops_native_functions: true + reject_mutable_random_on_entry_functions: true + per_object_congestion_control_mode: TotalTxCount + consensus_choice: Mysticeti + consensus_network: Tonic + zklogin_max_epoch_upper_bound_delta: 30 + mysticeti_leader_scoring_and_schedule: true + reshare_at_same_initial_version: true + resolve_abort_locations_to_package_id: true + mysticeti_use_committed_subdag_digest: true + record_consensus_determined_version_assignments_in_prologue: true + fresh_vm_on_framework_upgrade: true + prepend_prologue_tx_in_consensus_commit_in_checkpoints: true + mysticeti_num_leaders_per_round: 1 + soft_bundle: true + enable_coin_deny_list_v2: true + rethrow_serialization_type_layout_errors: true + consensus_distributed_vote_scoring_strategy: true + consensus_round_prober: true + validate_identifier_inputs: true + relocate_event_module: true +max_tx_size_bytes: 131072 +max_input_objects: 2048 +max_size_written_objects: 5000000 +max_size_written_objects_system_tx: 50000000 +max_serialized_tx_effects_size_bytes: 524288 +max_serialized_tx_effects_size_bytes_system_tx: 8388608 +max_gas_payment_objects: 256 +max_modules_in_publish: 64 +max_package_dependencies: 32 +max_arguments: 512 +max_type_arguments: 16 +max_type_argument_depth: 16 +max_pure_argument_size: 16384 +max_programmable_tx_commands: 1024 +move_binary_format_version: 7 +min_move_binary_format_version: 6 +binary_module_handles: 100 +binary_struct_handles: 300 +binary_function_handles: 1500 +binary_function_instantiations: 750 +binary_signatures: 1000 +binary_constant_pool: 4000 +binary_identifiers: 10000 +binary_address_identifiers: 100 +binary_struct_defs: 200 +binary_struct_def_instantiations: 100 +binary_function_defs: 1000 +binary_field_handles: 500 +binary_field_instantiations: 250 +binary_friend_decls: 100 +max_move_object_size: 256000 +max_move_package_size: 102400 +max_publish_or_upgrade_per_ptb: 5 +max_tx_gas: 50000000000 +max_gas_price: 100000 +max_gas_computation_bucket: 5000000 +gas_rounding_step: 1000 +max_loop_depth: 5 +max_generic_instantiation_length: 32 +max_function_parameters: 128 +max_basic_blocks: 1024 +max_value_stack_size: 1024 +max_type_nodes: 256 +max_push_size: 10000 +max_struct_definitions: 200 +max_function_definitions: 1000 +max_fields_in_struct: 32 +max_dependency_depth: 100 +max_num_event_emit: 1024 +max_num_new_move_object_ids: 2048 +max_num_new_move_object_ids_system_tx: 32768 +max_num_deleted_move_object_ids: 2048 +max_num_deleted_move_object_ids_system_tx: 32768 +max_num_transferred_move_object_ids: 2048 +max_num_transferred_move_object_ids_system_tx: 32768 +max_event_emit_size: 256000 +max_event_emit_size_total: 65536000 +max_move_vector_len: 262144 +max_move_identifier_len: 128 +max_move_value_depth: 128 +max_back_edges_per_function: 10000 +max_back_edges_per_module: 10000 +max_verifier_meter_ticks_per_function: 16000000 +max_meter_ticks_per_module: 16000000 +max_meter_ticks_per_package: 16000000 +object_runtime_max_num_cached_objects: 1000 +object_runtime_max_num_cached_objects_system_tx: 16000 +object_runtime_max_num_store_entries: 1000 +object_runtime_max_num_store_entries_system_tx: 16000 +base_tx_cost_fixed: 1000 +package_publish_cost_fixed: 1000 +base_tx_cost_per_byte: 0 +package_publish_cost_per_byte: 80 +obj_access_cost_read_per_byte: 15 +obj_access_cost_mutate_per_byte: 40 +obj_access_cost_delete_per_byte: 40 +obj_access_cost_verify_per_byte: 200 +max_type_to_layout_nodes: 512 +gas_model_version: 8 +obj_data_cost_refundable: 100 +obj_metadata_cost_non_refundable: 50 +storage_rebate_rate: 9900 +storage_fund_reinvest_rate: 500 +reward_slashing_rate: 10000 +storage_gas_price: 76 +max_transactions_per_checkpoint: 10000 +max_checkpoint_size_bytes: 31457280 +buffer_stake_for_protocol_upgrade_bps: 5000 +address_from_bytes_cost_base: 52 +address_to_u256_cost_base: 52 +address_from_u256_cost_base: 52 +config_read_setting_impl_cost_base: 100 +config_read_setting_impl_cost_per_byte: 40 +dynamic_field_hash_type_and_key_cost_base: 100 +dynamic_field_hash_type_and_key_type_cost_per_byte: 2 +dynamic_field_hash_type_and_key_value_cost_per_byte: 2 +dynamic_field_hash_type_and_key_type_tag_cost_per_byte: 2 +dynamic_field_add_child_object_cost_base: 100 +dynamic_field_add_child_object_type_cost_per_byte: 10 +dynamic_field_add_child_object_value_cost_per_byte: 10 +dynamic_field_add_child_object_struct_tag_cost_per_byte: 10 +dynamic_field_borrow_child_object_cost_base: 100 +dynamic_field_borrow_child_object_child_ref_cost_per_byte: 10 +dynamic_field_borrow_child_object_type_cost_per_byte: 10 +dynamic_field_remove_child_object_cost_base: 100 +dynamic_field_remove_child_object_child_cost_per_byte: 2 +dynamic_field_remove_child_object_type_cost_per_byte: 2 +dynamic_field_has_child_object_cost_base: 100 +dynamic_field_has_child_object_with_ty_cost_base: 100 +dynamic_field_has_child_object_with_ty_type_cost_per_byte: 2 +dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: 2 +event_emit_cost_base: 52 +event_emit_value_size_derivation_cost_per_byte: 2 +event_emit_tag_size_derivation_cost_per_byte: 5 +event_emit_output_cost_per_byte: 10 +object_borrow_uid_cost_base: 52 +object_delete_impl_cost_base: 52 +object_record_new_uid_cost_base: 52 +transfer_transfer_internal_cost_base: 52 +transfer_freeze_object_cost_base: 52 +transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 +tx_context_derive_id_cost_base: 52 +types_is_one_time_witness_cost_base: 52 +types_is_one_time_witness_type_tag_cost_per_byte: 2 +types_is_one_time_witness_type_cost_per_byte: 2 +validator_validate_metadata_cost_base: 52 +validator_validate_metadata_data_cost_per_byte: 2 +crypto_invalid_arguments_cost: 100 +bls12381_bls12381_min_sig_verify_cost_base: 52 +bls12381_bls12381_min_sig_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_sig_verify_msg_cost_per_block: 2 +bls12381_bls12381_min_pk_verify_cost_base: 52 +bls12381_bls12381_min_pk_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_pk_verify_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_keccak256_cost_base: 52 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_sha256_cost_base: 52 +ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_k1_decompress_pubkey_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_k1_secp256k1_verify_sha256_cost_base: 52 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_keccak256_cost_base: 52 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_sha256_cost_base: 52 +ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_keccak256_cost_base: 52 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_sha256_cost_base: 52 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: 2 +ecvrf_ecvrf_verify_cost_base: 52 +ecvrf_ecvrf_verify_alpha_string_cost_per_byte: 2 +ecvrf_ecvrf_verify_alpha_string_cost_per_block: 2 +ed25519_ed25519_verify_cost_base: 52 +ed25519_ed25519_verify_msg_cost_per_byte: 2 +ed25519_ed25519_verify_msg_cost_per_block: 2 +groth16_prepare_verifying_key_bls12381_cost_base: 52 +groth16_prepare_verifying_key_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_public_input_cost_per_byte: 2 +hash_blake2b256_cost_base: 52 +hash_blake2b256_data_cost_per_byte: 2 +hash_blake2b256_data_cost_per_block: 2 +hash_keccak256_cost_base: 52 +hash_keccak256_data_cost_per_byte: 2 +hash_keccak256_data_cost_per_block: 2 +group_ops_bls12381_decode_scalar_cost: 52 +group_ops_bls12381_decode_g1_cost: 52 +group_ops_bls12381_decode_g2_cost: 52 +group_ops_bls12381_decode_gt_cost: 52 +group_ops_bls12381_scalar_add_cost: 52 +group_ops_bls12381_g1_add_cost: 52 +group_ops_bls12381_g2_add_cost: 52 +group_ops_bls12381_gt_add_cost: 52 +group_ops_bls12381_scalar_sub_cost: 52 +group_ops_bls12381_g1_sub_cost: 52 +group_ops_bls12381_g2_sub_cost: 52 +group_ops_bls12381_gt_sub_cost: 52 +group_ops_bls12381_scalar_mul_cost: 52 +group_ops_bls12381_g1_mul_cost: 52 +group_ops_bls12381_g2_mul_cost: 52 +group_ops_bls12381_gt_mul_cost: 52 +group_ops_bls12381_scalar_div_cost: 52 +group_ops_bls12381_g1_div_cost: 52 +group_ops_bls12381_g2_div_cost: 52 +group_ops_bls12381_gt_div_cost: 52 +group_ops_bls12381_g1_hash_to_base_cost: 52 +group_ops_bls12381_g2_hash_to_base_cost: 52 +group_ops_bls12381_g1_hash_to_cost_per_byte: 2 +group_ops_bls12381_g2_hash_to_cost_per_byte: 2 +group_ops_bls12381_g1_msm_base_cost: 52 +group_ops_bls12381_g2_msm_base_cost: 52 +group_ops_bls12381_g1_msm_base_cost_per_input: 52 +group_ops_bls12381_g2_msm_base_cost_per_input: 52 +group_ops_bls12381_msm_max_len: 32 +group_ops_bls12381_pairing_cost: 52 +hmac_hmac_sha3_256_cost_base: 52 +hmac_hmac_sha3_256_input_cost_per_byte: 2 +hmac_hmac_sha3_256_input_cost_per_block: 2 +check_zklogin_id_cost_base: 200 +check_zklogin_issuer_cost_base: 200 +bcs_per_byte_serialized_cost: 2 +bcs_legacy_min_output_size_cost: 1 +bcs_failure_cost: 52 +hash_sha2_256_base_cost: 52 +hash_sha2_256_per_byte_cost: 2 +hash_sha2_256_legacy_min_input_len_cost: 1 +hash_sha3_256_base_cost: 52 +hash_sha3_256_per_byte_cost: 2 +hash_sha3_256_legacy_min_input_len_cost: 1 +type_name_get_base_cost: 52 +type_name_get_per_byte_cost: 2 +string_check_utf8_base_cost: 52 +string_check_utf8_per_byte_cost: 2 +string_is_char_boundary_base_cost: 52 +string_sub_string_base_cost: 52 +string_sub_string_per_byte_cost: 2 +string_index_of_base_cost: 52 +string_index_of_per_byte_pattern_cost: 2 +string_index_of_per_byte_searched_cost: 2 +vector_empty_base_cost: 52 +vector_length_base_cost: 52 +vector_push_back_base_cost: 52 +vector_push_back_legacy_per_abstract_memory_unit_cost: 2 +vector_borrow_base_cost: 52 +vector_pop_back_base_cost: 52 +vector_destroy_empty_base_cost: 52 +vector_swap_base_cost: 52 +debug_print_base_cost: 52 +debug_print_stack_trace_base_cost: 52 +execution_version: 3 +consensus_bad_nodes_stake_threshold: 20 +max_jwk_votes_per_validator_per_epoch: 240 +max_age_of_jwk_in_epochs: 1 +random_beacon_reduction_allowed_delta: 800 +random_beacon_reduction_lower_bound: 700 +random_beacon_dkg_timeout_round: 3000 +random_beacon_min_round_interval_ms: 500 +random_beacon_dkg_version: 1 +consensus_max_transaction_size_bytes: 262144 +consensus_max_transactions_in_block_bytes: 524288 +consensus_max_num_transactions_in_block: 512 +max_accumulated_txn_cost_per_object_in_narwhal_commit: 40 +max_deferral_rounds_for_congestion_control: 10 +min_checkpoint_interval_ms: 200 +checkpoint_summary_version_specific_data: 1 +max_soft_bundle_size: 5 +bridge_should_try_to_finalize_committee: true +max_accumulated_txn_cost_per_object_in_mysticeti_commit: 3 +gas_budget_based_txn_cost_cap_factor: 400000 + diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_65.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_65.snap new file mode 100644 index 0000000000000..2a201ab2b494f --- /dev/null +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__Testnet_version_65.snap @@ -0,0 +1,330 @@ +--- +source: crates/sui-protocol-config/src/lib.rs +expression: "ProtocolConfig::get_for_version(cur, *chain_id)" +--- +version: 65 +feature_flags: + package_upgrades: true + commit_root_state_digest: true + advance_epoch_start_time_in_safe_mode: true + loaded_child_objects_fixed: true + missing_type_is_compatibility_error: true + scoring_decision_with_validity_cutoff: true + consensus_order_end_of_epoch_last: true + disallow_adding_abilities_on_upgrade: true + disable_invariant_violation_check_in_swap_loc: true + advance_to_highest_supported_protocol_version: true + ban_entry_init: true + package_digest_hash_module: true + disallow_change_struct_type_params_on_upgrade: true + no_extraneous_module_bytes: true + narwhal_versioned_metadata: true + zklogin_auth: true + consensus_transaction_ordering: ByGasPrice + simplified_unwrap_then_delete: true + upgraded_multisig_supported: true + txn_base_cost_as_multiplier: true + shared_object_deletion: true + narwhal_new_leader_election_schedule: true + loaded_child_object_format: true + enable_jwk_consensus_updates: true + end_of_epoch_transaction_supported: true + simple_conservation_checks: true + loaded_child_object_format_type: true + receive_objects: true + random_beacon: true + bridge: true + enable_effects_v2: true + narwhal_certificate_v2: true + verify_legacy_zklogin_address: true + recompute_has_public_transfer_in_execution: true + accept_zklogin_in_multisig: true + include_consensus_digest_in_prologue: true + hardened_otw_check: true + allow_receiving_object_id: true + enable_coin_deny_list: true + enable_group_ops_native_functions: true + reject_mutable_random_on_entry_functions: true + per_object_congestion_control_mode: TotalTxCount + consensus_choice: Mysticeti + consensus_network: Tonic + zklogin_max_epoch_upper_bound_delta: 30 + mysticeti_leader_scoring_and_schedule: true + reshare_at_same_initial_version: true + resolve_abort_locations_to_package_id: true + mysticeti_use_committed_subdag_digest: true + record_consensus_determined_version_assignments_in_prologue: true + fresh_vm_on_framework_upgrade: true + prepend_prologue_tx_in_consensus_commit_in_checkpoints: true + mysticeti_num_leaders_per_round: 1 + soft_bundle: true + enable_coin_deny_list_v2: true + rethrow_serialization_type_layout_errors: true + consensus_distributed_vote_scoring_strategy: true + consensus_round_prober: true + validate_identifier_inputs: true + relocate_event_module: true +max_tx_size_bytes: 131072 +max_input_objects: 2048 +max_size_written_objects: 5000000 +max_size_written_objects_system_tx: 50000000 +max_serialized_tx_effects_size_bytes: 524288 +max_serialized_tx_effects_size_bytes_system_tx: 8388608 +max_gas_payment_objects: 256 +max_modules_in_publish: 64 +max_package_dependencies: 32 +max_arguments: 512 +max_type_arguments: 16 +max_type_argument_depth: 16 +max_pure_argument_size: 16384 +max_programmable_tx_commands: 1024 +move_binary_format_version: 7 +min_move_binary_format_version: 6 +binary_module_handles: 100 +binary_struct_handles: 300 +binary_function_handles: 1500 +binary_function_instantiations: 750 +binary_signatures: 1000 +binary_constant_pool: 4000 +binary_identifiers: 10000 +binary_address_identifiers: 100 +binary_struct_defs: 200 +binary_struct_def_instantiations: 100 +binary_function_defs: 1000 +binary_field_handles: 500 +binary_field_instantiations: 250 +binary_friend_decls: 100 +max_move_object_size: 256000 +max_move_package_size: 102400 +max_publish_or_upgrade_per_ptb: 5 +max_tx_gas: 50000000000 +max_gas_price: 100000 +max_gas_computation_bucket: 5000000 +gas_rounding_step: 1000 +max_loop_depth: 5 +max_generic_instantiation_length: 32 +max_function_parameters: 128 +max_basic_blocks: 1024 +max_value_stack_size: 1024 +max_type_nodes: 256 +max_push_size: 10000 +max_struct_definitions: 200 +max_function_definitions: 1000 +max_fields_in_struct: 32 +max_dependency_depth: 100 +max_num_event_emit: 1024 +max_num_new_move_object_ids: 2048 +max_num_new_move_object_ids_system_tx: 32768 +max_num_deleted_move_object_ids: 2048 +max_num_deleted_move_object_ids_system_tx: 32768 +max_num_transferred_move_object_ids: 2048 +max_num_transferred_move_object_ids_system_tx: 32768 +max_event_emit_size: 256000 +max_event_emit_size_total: 65536000 +max_move_vector_len: 262144 +max_move_identifier_len: 128 +max_move_value_depth: 128 +max_back_edges_per_function: 10000 +max_back_edges_per_module: 10000 +max_verifier_meter_ticks_per_function: 16000000 +max_meter_ticks_per_module: 16000000 +max_meter_ticks_per_package: 16000000 +object_runtime_max_num_cached_objects: 1000 +object_runtime_max_num_cached_objects_system_tx: 16000 +object_runtime_max_num_store_entries: 1000 +object_runtime_max_num_store_entries_system_tx: 16000 +base_tx_cost_fixed: 1000 +package_publish_cost_fixed: 1000 +base_tx_cost_per_byte: 0 +package_publish_cost_per_byte: 80 +obj_access_cost_read_per_byte: 15 +obj_access_cost_mutate_per_byte: 40 +obj_access_cost_delete_per_byte: 40 +obj_access_cost_verify_per_byte: 200 +max_type_to_layout_nodes: 512 +gas_model_version: 8 +obj_data_cost_refundable: 100 +obj_metadata_cost_non_refundable: 50 +storage_rebate_rate: 9900 +storage_fund_reinvest_rate: 500 +reward_slashing_rate: 10000 +storage_gas_price: 76 +max_transactions_per_checkpoint: 10000 +max_checkpoint_size_bytes: 31457280 +buffer_stake_for_protocol_upgrade_bps: 5000 +address_from_bytes_cost_base: 52 +address_to_u256_cost_base: 52 +address_from_u256_cost_base: 52 +config_read_setting_impl_cost_base: 100 +config_read_setting_impl_cost_per_byte: 40 +dynamic_field_hash_type_and_key_cost_base: 100 +dynamic_field_hash_type_and_key_type_cost_per_byte: 2 +dynamic_field_hash_type_and_key_value_cost_per_byte: 2 +dynamic_field_hash_type_and_key_type_tag_cost_per_byte: 2 +dynamic_field_add_child_object_cost_base: 100 +dynamic_field_add_child_object_type_cost_per_byte: 10 +dynamic_field_add_child_object_value_cost_per_byte: 10 +dynamic_field_add_child_object_struct_tag_cost_per_byte: 10 +dynamic_field_borrow_child_object_cost_base: 100 +dynamic_field_borrow_child_object_child_ref_cost_per_byte: 10 +dynamic_field_borrow_child_object_type_cost_per_byte: 10 +dynamic_field_remove_child_object_cost_base: 100 +dynamic_field_remove_child_object_child_cost_per_byte: 2 +dynamic_field_remove_child_object_type_cost_per_byte: 2 +dynamic_field_has_child_object_cost_base: 100 +dynamic_field_has_child_object_with_ty_cost_base: 100 +dynamic_field_has_child_object_with_ty_type_cost_per_byte: 2 +dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: 2 +event_emit_cost_base: 52 +event_emit_value_size_derivation_cost_per_byte: 2 +event_emit_tag_size_derivation_cost_per_byte: 5 +event_emit_output_cost_per_byte: 10 +object_borrow_uid_cost_base: 52 +object_delete_impl_cost_base: 52 +object_record_new_uid_cost_base: 52 +transfer_transfer_internal_cost_base: 52 +transfer_freeze_object_cost_base: 52 +transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 +tx_context_derive_id_cost_base: 52 +types_is_one_time_witness_cost_base: 52 +types_is_one_time_witness_type_tag_cost_per_byte: 2 +types_is_one_time_witness_type_cost_per_byte: 2 +validator_validate_metadata_cost_base: 52 +validator_validate_metadata_data_cost_per_byte: 2 +crypto_invalid_arguments_cost: 100 +bls12381_bls12381_min_sig_verify_cost_base: 52 +bls12381_bls12381_min_sig_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_sig_verify_msg_cost_per_block: 2 +bls12381_bls12381_min_pk_verify_cost_base: 52 +bls12381_bls12381_min_pk_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_pk_verify_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_keccak256_cost_base: 52 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_sha256_cost_base: 52 +ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_k1_decompress_pubkey_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_k1_secp256k1_verify_sha256_cost_base: 52 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_keccak256_cost_base: 52 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_sha256_cost_base: 52 +ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_keccak256_cost_base: 52 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_sha256_cost_base: 52 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: 2 +ecvrf_ecvrf_verify_cost_base: 52 +ecvrf_ecvrf_verify_alpha_string_cost_per_byte: 2 +ecvrf_ecvrf_verify_alpha_string_cost_per_block: 2 +ed25519_ed25519_verify_cost_base: 52 +ed25519_ed25519_verify_msg_cost_per_byte: 2 +ed25519_ed25519_verify_msg_cost_per_block: 2 +groth16_prepare_verifying_key_bls12381_cost_base: 52 +groth16_prepare_verifying_key_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_public_input_cost_per_byte: 2 +hash_blake2b256_cost_base: 52 +hash_blake2b256_data_cost_per_byte: 2 +hash_blake2b256_data_cost_per_block: 2 +hash_keccak256_cost_base: 52 +hash_keccak256_data_cost_per_byte: 2 +hash_keccak256_data_cost_per_block: 2 +group_ops_bls12381_decode_scalar_cost: 52 +group_ops_bls12381_decode_g1_cost: 52 +group_ops_bls12381_decode_g2_cost: 52 +group_ops_bls12381_decode_gt_cost: 52 +group_ops_bls12381_scalar_add_cost: 52 +group_ops_bls12381_g1_add_cost: 52 +group_ops_bls12381_g2_add_cost: 52 +group_ops_bls12381_gt_add_cost: 52 +group_ops_bls12381_scalar_sub_cost: 52 +group_ops_bls12381_g1_sub_cost: 52 +group_ops_bls12381_g2_sub_cost: 52 +group_ops_bls12381_gt_sub_cost: 52 +group_ops_bls12381_scalar_mul_cost: 52 +group_ops_bls12381_g1_mul_cost: 52 +group_ops_bls12381_g2_mul_cost: 52 +group_ops_bls12381_gt_mul_cost: 52 +group_ops_bls12381_scalar_div_cost: 52 +group_ops_bls12381_g1_div_cost: 52 +group_ops_bls12381_g2_div_cost: 52 +group_ops_bls12381_gt_div_cost: 52 +group_ops_bls12381_g1_hash_to_base_cost: 52 +group_ops_bls12381_g2_hash_to_base_cost: 52 +group_ops_bls12381_g1_hash_to_cost_per_byte: 2 +group_ops_bls12381_g2_hash_to_cost_per_byte: 2 +group_ops_bls12381_g1_msm_base_cost: 52 +group_ops_bls12381_g2_msm_base_cost: 52 +group_ops_bls12381_g1_msm_base_cost_per_input: 52 +group_ops_bls12381_g2_msm_base_cost_per_input: 52 +group_ops_bls12381_msm_max_len: 32 +group_ops_bls12381_pairing_cost: 52 +hmac_hmac_sha3_256_cost_base: 52 +hmac_hmac_sha3_256_input_cost_per_byte: 2 +hmac_hmac_sha3_256_input_cost_per_block: 2 +check_zklogin_id_cost_base: 200 +check_zklogin_issuer_cost_base: 200 +bcs_per_byte_serialized_cost: 2 +bcs_legacy_min_output_size_cost: 1 +bcs_failure_cost: 52 +hash_sha2_256_base_cost: 52 +hash_sha2_256_per_byte_cost: 2 +hash_sha2_256_legacy_min_input_len_cost: 1 +hash_sha3_256_base_cost: 52 +hash_sha3_256_per_byte_cost: 2 +hash_sha3_256_legacy_min_input_len_cost: 1 +type_name_get_base_cost: 52 +type_name_get_per_byte_cost: 2 +string_check_utf8_base_cost: 52 +string_check_utf8_per_byte_cost: 2 +string_is_char_boundary_base_cost: 52 +string_sub_string_base_cost: 52 +string_sub_string_per_byte_cost: 2 +string_index_of_base_cost: 52 +string_index_of_per_byte_pattern_cost: 2 +string_index_of_per_byte_searched_cost: 2 +vector_empty_base_cost: 52 +vector_length_base_cost: 52 +vector_push_back_base_cost: 52 +vector_push_back_legacy_per_abstract_memory_unit_cost: 2 +vector_borrow_base_cost: 52 +vector_pop_back_base_cost: 52 +vector_destroy_empty_base_cost: 52 +vector_swap_base_cost: 52 +debug_print_base_cost: 52 +debug_print_stack_trace_base_cost: 52 +execution_version: 3 +consensus_bad_nodes_stake_threshold: 20 +max_jwk_votes_per_validator_per_epoch: 240 +max_age_of_jwk_in_epochs: 1 +random_beacon_reduction_allowed_delta: 800 +random_beacon_reduction_lower_bound: 700 +random_beacon_dkg_timeout_round: 3000 +random_beacon_min_round_interval_ms: 500 +random_beacon_dkg_version: 1 +consensus_max_transaction_size_bytes: 262144 +consensus_max_transactions_in_block_bytes: 524288 +consensus_max_num_transactions_in_block: 512 +max_accumulated_txn_cost_per_object_in_narwhal_commit: 40 +max_deferral_rounds_for_congestion_control: 10 +min_checkpoint_interval_ms: 200 +checkpoint_summary_version_specific_data: 1 +max_soft_bundle_size: 5 +bridge_should_try_to_finalize_committee: true +max_accumulated_txn_cost_per_object_in_mysticeti_commit: 3 +gas_budget_based_txn_cost_cap_factor: 400000 + diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_61.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_61.snap new file mode 100644 index 0000000000000..a386a9bcc1ab5 --- /dev/null +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_61.snap @@ -0,0 +1,338 @@ +--- +source: crates/sui-protocol-config/src/lib.rs +expression: "ProtocolConfig::get_for_version(cur, *chain_id)" +--- +version: 61 +feature_flags: + package_upgrades: true + commit_root_state_digest: true + advance_epoch_start_time_in_safe_mode: true + loaded_child_objects_fixed: true + missing_type_is_compatibility_error: true + scoring_decision_with_validity_cutoff: true + consensus_order_end_of_epoch_last: true + disallow_adding_abilities_on_upgrade: true + disable_invariant_violation_check_in_swap_loc: true + advance_to_highest_supported_protocol_version: true + ban_entry_init: true + package_digest_hash_module: true + disallow_change_struct_type_params_on_upgrade: true + no_extraneous_module_bytes: true + narwhal_versioned_metadata: true + zklogin_auth: true + consensus_transaction_ordering: ByGasPrice + simplified_unwrap_then_delete: true + upgraded_multisig_supported: true + txn_base_cost_as_multiplier: true + shared_object_deletion: true + narwhal_new_leader_election_schedule: true + loaded_child_object_format: true + enable_jwk_consensus_updates: true + end_of_epoch_transaction_supported: true + simple_conservation_checks: true + loaded_child_object_format_type: true + receive_objects: true + random_beacon: true + bridge: true + enable_effects_v2: true + narwhal_certificate_v2: true + verify_legacy_zklogin_address: true + recompute_has_public_transfer_in_execution: true + accept_zklogin_in_multisig: true + include_consensus_digest_in_prologue: true + hardened_otw_check: true + allow_receiving_object_id: true + enable_poseidon: true + enable_coin_deny_list: true + enable_group_ops_native_functions: true + enable_group_ops_native_function_msm: true + reject_mutable_random_on_entry_functions: true + per_object_congestion_control_mode: TotalTxCount + consensus_choice: Mysticeti + consensus_network: Tonic + zklogin_max_epoch_upper_bound_delta: 30 + mysticeti_leader_scoring_and_schedule: true + reshare_at_same_initial_version: true + resolve_abort_locations_to_package_id: true + mysticeti_use_committed_subdag_digest: true + enable_vdf: true + record_consensus_determined_version_assignments_in_prologue: true + fresh_vm_on_framework_upgrade: true + prepend_prologue_tx_in_consensus_commit_in_checkpoints: true + mysticeti_num_leaders_per_round: 1 + soft_bundle: true + enable_coin_deny_list_v2: true + passkey_auth: true + authority_capabilities_v2: true + rethrow_serialization_type_layout_errors: true + consensus_distributed_vote_scoring_strategy: true + consensus_round_prober: true + validate_identifier_inputs: true + mysticeti_fastpath: true +max_tx_size_bytes: 131072 +max_input_objects: 2048 +max_size_written_objects: 5000000 +max_size_written_objects_system_tx: 50000000 +max_serialized_tx_effects_size_bytes: 524288 +max_serialized_tx_effects_size_bytes_system_tx: 8388608 +max_gas_payment_objects: 256 +max_modules_in_publish: 64 +max_package_dependencies: 32 +max_arguments: 512 +max_type_arguments: 16 +max_type_argument_depth: 16 +max_pure_argument_size: 16384 +max_programmable_tx_commands: 1024 +move_binary_format_version: 7 +min_move_binary_format_version: 6 +binary_module_handles: 100 +binary_struct_handles: 300 +binary_function_handles: 1500 +binary_function_instantiations: 750 +binary_signatures: 1000 +binary_constant_pool: 4000 +binary_identifiers: 10000 +binary_address_identifiers: 100 +binary_struct_defs: 200 +binary_struct_def_instantiations: 100 +binary_function_defs: 1000 +binary_field_handles: 500 +binary_field_instantiations: 250 +binary_friend_decls: 100 +max_move_object_size: 256000 +max_move_package_size: 102400 +max_publish_or_upgrade_per_ptb: 5 +max_tx_gas: 50000000000 +max_gas_price: 100000 +max_gas_computation_bucket: 5000000 +gas_rounding_step: 1000 +max_loop_depth: 5 +max_generic_instantiation_length: 32 +max_function_parameters: 128 +max_basic_blocks: 1024 +max_value_stack_size: 1024 +max_type_nodes: 256 +max_push_size: 10000 +max_struct_definitions: 200 +max_function_definitions: 1000 +max_fields_in_struct: 32 +max_dependency_depth: 100 +max_num_event_emit: 1024 +max_num_new_move_object_ids: 2048 +max_num_new_move_object_ids_system_tx: 32768 +max_num_deleted_move_object_ids: 2048 +max_num_deleted_move_object_ids_system_tx: 32768 +max_num_transferred_move_object_ids: 2048 +max_num_transferred_move_object_ids_system_tx: 32768 +max_event_emit_size: 256000 +max_event_emit_size_total: 65536000 +max_move_vector_len: 262144 +max_move_identifier_len: 128 +max_move_value_depth: 128 +max_back_edges_per_function: 10000 +max_back_edges_per_module: 10000 +max_verifier_meter_ticks_per_function: 16000000 +max_meter_ticks_per_module: 16000000 +max_meter_ticks_per_package: 16000000 +object_runtime_max_num_cached_objects: 1000 +object_runtime_max_num_cached_objects_system_tx: 16000 +object_runtime_max_num_store_entries: 1000 +object_runtime_max_num_store_entries_system_tx: 16000 +base_tx_cost_fixed: 1000 +package_publish_cost_fixed: 1000 +base_tx_cost_per_byte: 0 +package_publish_cost_per_byte: 80 +obj_access_cost_read_per_byte: 15 +obj_access_cost_mutate_per_byte: 40 +obj_access_cost_delete_per_byte: 40 +obj_access_cost_verify_per_byte: 200 +max_type_to_layout_nodes: 512 +gas_model_version: 8 +obj_data_cost_refundable: 100 +obj_metadata_cost_non_refundable: 50 +storage_rebate_rate: 9900 +storage_fund_reinvest_rate: 500 +reward_slashing_rate: 10000 +storage_gas_price: 76 +max_transactions_per_checkpoint: 10000 +max_checkpoint_size_bytes: 31457280 +buffer_stake_for_protocol_upgrade_bps: 5000 +address_from_bytes_cost_base: 52 +address_to_u256_cost_base: 52 +address_from_u256_cost_base: 52 +config_read_setting_impl_cost_base: 100 +config_read_setting_impl_cost_per_byte: 40 +dynamic_field_hash_type_and_key_cost_base: 100 +dynamic_field_hash_type_and_key_type_cost_per_byte: 2 +dynamic_field_hash_type_and_key_value_cost_per_byte: 2 +dynamic_field_hash_type_and_key_type_tag_cost_per_byte: 2 +dynamic_field_add_child_object_cost_base: 100 +dynamic_field_add_child_object_type_cost_per_byte: 10 +dynamic_field_add_child_object_value_cost_per_byte: 10 +dynamic_field_add_child_object_struct_tag_cost_per_byte: 10 +dynamic_field_borrow_child_object_cost_base: 100 +dynamic_field_borrow_child_object_child_ref_cost_per_byte: 10 +dynamic_field_borrow_child_object_type_cost_per_byte: 10 +dynamic_field_remove_child_object_cost_base: 100 +dynamic_field_remove_child_object_child_cost_per_byte: 2 +dynamic_field_remove_child_object_type_cost_per_byte: 2 +dynamic_field_has_child_object_cost_base: 100 +dynamic_field_has_child_object_with_ty_cost_base: 100 +dynamic_field_has_child_object_with_ty_type_cost_per_byte: 2 +dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: 2 +event_emit_cost_base: 52 +event_emit_value_size_derivation_cost_per_byte: 2 +event_emit_tag_size_derivation_cost_per_byte: 5 +event_emit_output_cost_per_byte: 10 +object_borrow_uid_cost_base: 52 +object_delete_impl_cost_base: 52 +object_record_new_uid_cost_base: 52 +transfer_transfer_internal_cost_base: 52 +transfer_freeze_object_cost_base: 52 +transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 +tx_context_derive_id_cost_base: 52 +types_is_one_time_witness_cost_base: 52 +types_is_one_time_witness_type_tag_cost_per_byte: 2 +types_is_one_time_witness_type_cost_per_byte: 2 +validator_validate_metadata_cost_base: 52 +validator_validate_metadata_data_cost_per_byte: 2 +crypto_invalid_arguments_cost: 100 +bls12381_bls12381_min_sig_verify_cost_base: 52 +bls12381_bls12381_min_sig_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_sig_verify_msg_cost_per_block: 2 +bls12381_bls12381_min_pk_verify_cost_base: 52 +bls12381_bls12381_min_pk_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_pk_verify_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_keccak256_cost_base: 52 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_sha256_cost_base: 52 +ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_k1_decompress_pubkey_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_k1_secp256k1_verify_sha256_cost_base: 52 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_keccak256_cost_base: 52 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_sha256_cost_base: 52 +ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_keccak256_cost_base: 52 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_sha256_cost_base: 52 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: 2 +ecvrf_ecvrf_verify_cost_base: 52 +ecvrf_ecvrf_verify_alpha_string_cost_per_byte: 2 +ecvrf_ecvrf_verify_alpha_string_cost_per_block: 2 +ed25519_ed25519_verify_cost_base: 52 +ed25519_ed25519_verify_msg_cost_per_byte: 2 +ed25519_ed25519_verify_msg_cost_per_block: 2 +groth16_prepare_verifying_key_bls12381_cost_base: 52 +groth16_prepare_verifying_key_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_public_input_cost_per_byte: 2 +hash_blake2b256_cost_base: 52 +hash_blake2b256_data_cost_per_byte: 2 +hash_blake2b256_data_cost_per_block: 2 +hash_keccak256_cost_base: 52 +hash_keccak256_data_cost_per_byte: 2 +hash_keccak256_data_cost_per_block: 2 +poseidon_bn254_cost_base: 260 +poseidon_bn254_cost_per_block: 10 +group_ops_bls12381_decode_scalar_cost: 52 +group_ops_bls12381_decode_g1_cost: 52 +group_ops_bls12381_decode_g2_cost: 52 +group_ops_bls12381_decode_gt_cost: 52 +group_ops_bls12381_scalar_add_cost: 52 +group_ops_bls12381_g1_add_cost: 52 +group_ops_bls12381_g2_add_cost: 52 +group_ops_bls12381_gt_add_cost: 52 +group_ops_bls12381_scalar_sub_cost: 52 +group_ops_bls12381_g1_sub_cost: 52 +group_ops_bls12381_g2_sub_cost: 52 +group_ops_bls12381_gt_sub_cost: 52 +group_ops_bls12381_scalar_mul_cost: 52 +group_ops_bls12381_g1_mul_cost: 52 +group_ops_bls12381_g2_mul_cost: 52 +group_ops_bls12381_gt_mul_cost: 52 +group_ops_bls12381_scalar_div_cost: 52 +group_ops_bls12381_g1_div_cost: 52 +group_ops_bls12381_g2_div_cost: 52 +group_ops_bls12381_gt_div_cost: 52 +group_ops_bls12381_g1_hash_to_base_cost: 52 +group_ops_bls12381_g2_hash_to_base_cost: 52 +group_ops_bls12381_g1_hash_to_cost_per_byte: 2 +group_ops_bls12381_g2_hash_to_cost_per_byte: 2 +group_ops_bls12381_g1_msm_base_cost: 52 +group_ops_bls12381_g2_msm_base_cost: 52 +group_ops_bls12381_g1_msm_base_cost_per_input: 52 +group_ops_bls12381_g2_msm_base_cost_per_input: 52 +group_ops_bls12381_msm_max_len: 32 +group_ops_bls12381_pairing_cost: 52 +hmac_hmac_sha3_256_cost_base: 52 +hmac_hmac_sha3_256_input_cost_per_byte: 2 +hmac_hmac_sha3_256_input_cost_per_block: 2 +check_zklogin_id_cost_base: 200 +check_zklogin_issuer_cost_base: 200 +vdf_verify_vdf_cost: 1500 +vdf_hash_to_input_cost: 100 +bcs_per_byte_serialized_cost: 2 +bcs_legacy_min_output_size_cost: 1 +bcs_failure_cost: 52 +hash_sha2_256_base_cost: 52 +hash_sha2_256_per_byte_cost: 2 +hash_sha2_256_legacy_min_input_len_cost: 1 +hash_sha3_256_base_cost: 52 +hash_sha3_256_per_byte_cost: 2 +hash_sha3_256_legacy_min_input_len_cost: 1 +type_name_get_base_cost: 52 +type_name_get_per_byte_cost: 2 +string_check_utf8_base_cost: 52 +string_check_utf8_per_byte_cost: 2 +string_is_char_boundary_base_cost: 52 +string_sub_string_base_cost: 52 +string_sub_string_per_byte_cost: 2 +string_index_of_base_cost: 52 +string_index_of_per_byte_pattern_cost: 2 +string_index_of_per_byte_searched_cost: 2 +vector_empty_base_cost: 52 +vector_length_base_cost: 52 +vector_push_back_base_cost: 52 +vector_push_back_legacy_per_abstract_memory_unit_cost: 2 +vector_borrow_base_cost: 52 +vector_pop_back_base_cost: 52 +vector_destroy_empty_base_cost: 52 +vector_swap_base_cost: 52 +debug_print_base_cost: 52 +debug_print_stack_trace_base_cost: 52 +execution_version: 3 +consensus_bad_nodes_stake_threshold: 20 +max_jwk_votes_per_validator_per_epoch: 240 +max_age_of_jwk_in_epochs: 1 +random_beacon_reduction_allowed_delta: 800 +random_beacon_reduction_lower_bound: 700 +random_beacon_dkg_timeout_round: 3000 +random_beacon_min_round_interval_ms: 500 +random_beacon_dkg_version: 1 +consensus_max_transaction_size_bytes: 262144 +consensus_max_transactions_in_block_bytes: 524288 +consensus_max_num_transactions_in_block: 512 +max_accumulated_txn_cost_per_object_in_narwhal_commit: 100 +max_deferral_rounds_for_congestion_control: 10 +min_checkpoint_interval_ms: 200 +checkpoint_summary_version_specific_data: 1 +max_soft_bundle_size: 5 +bridge_should_try_to_finalize_committee: true +max_accumulated_txn_cost_per_object_in_mysticeti_commit: 10 + diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_62.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_62.snap new file mode 100644 index 0000000000000..e542ae397c56f --- /dev/null +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_62.snap @@ -0,0 +1,339 @@ +--- +source: crates/sui-protocol-config/src/lib.rs +expression: "ProtocolConfig::get_for_version(cur, *chain_id)" +--- +version: 62 +feature_flags: + package_upgrades: true + commit_root_state_digest: true + advance_epoch_start_time_in_safe_mode: true + loaded_child_objects_fixed: true + missing_type_is_compatibility_error: true + scoring_decision_with_validity_cutoff: true + consensus_order_end_of_epoch_last: true + disallow_adding_abilities_on_upgrade: true + disable_invariant_violation_check_in_swap_loc: true + advance_to_highest_supported_protocol_version: true + ban_entry_init: true + package_digest_hash_module: true + disallow_change_struct_type_params_on_upgrade: true + no_extraneous_module_bytes: true + narwhal_versioned_metadata: true + zklogin_auth: true + consensus_transaction_ordering: ByGasPrice + simplified_unwrap_then_delete: true + upgraded_multisig_supported: true + txn_base_cost_as_multiplier: true + shared_object_deletion: true + narwhal_new_leader_election_schedule: true + loaded_child_object_format: true + enable_jwk_consensus_updates: true + end_of_epoch_transaction_supported: true + simple_conservation_checks: true + loaded_child_object_format_type: true + receive_objects: true + random_beacon: true + bridge: true + enable_effects_v2: true + narwhal_certificate_v2: true + verify_legacy_zklogin_address: true + recompute_has_public_transfer_in_execution: true + accept_zklogin_in_multisig: true + include_consensus_digest_in_prologue: true + hardened_otw_check: true + allow_receiving_object_id: true + enable_poseidon: true + enable_coin_deny_list: true + enable_group_ops_native_functions: true + enable_group_ops_native_function_msm: true + reject_mutable_random_on_entry_functions: true + per_object_congestion_control_mode: TotalTxCount + consensus_choice: Mysticeti + consensus_network: Tonic + zklogin_max_epoch_upper_bound_delta: 30 + mysticeti_leader_scoring_and_schedule: true + reshare_at_same_initial_version: true + resolve_abort_locations_to_package_id: true + mysticeti_use_committed_subdag_digest: true + enable_vdf: true + record_consensus_determined_version_assignments_in_prologue: true + fresh_vm_on_framework_upgrade: true + prepend_prologue_tx_in_consensus_commit_in_checkpoints: true + mysticeti_num_leaders_per_round: 1 + soft_bundle: true + enable_coin_deny_list_v2: true + passkey_auth: true + authority_capabilities_v2: true + rethrow_serialization_type_layout_errors: true + consensus_distributed_vote_scoring_strategy: true + consensus_round_prober: true + validate_identifier_inputs: true + mysticeti_fastpath: true + relocate_event_module: true +max_tx_size_bytes: 131072 +max_input_objects: 2048 +max_size_written_objects: 5000000 +max_size_written_objects_system_tx: 50000000 +max_serialized_tx_effects_size_bytes: 524288 +max_serialized_tx_effects_size_bytes_system_tx: 8388608 +max_gas_payment_objects: 256 +max_modules_in_publish: 64 +max_package_dependencies: 32 +max_arguments: 512 +max_type_arguments: 16 +max_type_argument_depth: 16 +max_pure_argument_size: 16384 +max_programmable_tx_commands: 1024 +move_binary_format_version: 7 +min_move_binary_format_version: 6 +binary_module_handles: 100 +binary_struct_handles: 300 +binary_function_handles: 1500 +binary_function_instantiations: 750 +binary_signatures: 1000 +binary_constant_pool: 4000 +binary_identifiers: 10000 +binary_address_identifiers: 100 +binary_struct_defs: 200 +binary_struct_def_instantiations: 100 +binary_function_defs: 1000 +binary_field_handles: 500 +binary_field_instantiations: 250 +binary_friend_decls: 100 +max_move_object_size: 256000 +max_move_package_size: 102400 +max_publish_or_upgrade_per_ptb: 5 +max_tx_gas: 50000000000 +max_gas_price: 100000 +max_gas_computation_bucket: 5000000 +gas_rounding_step: 1000 +max_loop_depth: 5 +max_generic_instantiation_length: 32 +max_function_parameters: 128 +max_basic_blocks: 1024 +max_value_stack_size: 1024 +max_type_nodes: 256 +max_push_size: 10000 +max_struct_definitions: 200 +max_function_definitions: 1000 +max_fields_in_struct: 32 +max_dependency_depth: 100 +max_num_event_emit: 1024 +max_num_new_move_object_ids: 2048 +max_num_new_move_object_ids_system_tx: 32768 +max_num_deleted_move_object_ids: 2048 +max_num_deleted_move_object_ids_system_tx: 32768 +max_num_transferred_move_object_ids: 2048 +max_num_transferred_move_object_ids_system_tx: 32768 +max_event_emit_size: 256000 +max_event_emit_size_total: 65536000 +max_move_vector_len: 262144 +max_move_identifier_len: 128 +max_move_value_depth: 128 +max_back_edges_per_function: 10000 +max_back_edges_per_module: 10000 +max_verifier_meter_ticks_per_function: 16000000 +max_meter_ticks_per_module: 16000000 +max_meter_ticks_per_package: 16000000 +object_runtime_max_num_cached_objects: 1000 +object_runtime_max_num_cached_objects_system_tx: 16000 +object_runtime_max_num_store_entries: 1000 +object_runtime_max_num_store_entries_system_tx: 16000 +base_tx_cost_fixed: 1000 +package_publish_cost_fixed: 1000 +base_tx_cost_per_byte: 0 +package_publish_cost_per_byte: 80 +obj_access_cost_read_per_byte: 15 +obj_access_cost_mutate_per_byte: 40 +obj_access_cost_delete_per_byte: 40 +obj_access_cost_verify_per_byte: 200 +max_type_to_layout_nodes: 512 +gas_model_version: 8 +obj_data_cost_refundable: 100 +obj_metadata_cost_non_refundable: 50 +storage_rebate_rate: 9900 +storage_fund_reinvest_rate: 500 +reward_slashing_rate: 10000 +storage_gas_price: 76 +max_transactions_per_checkpoint: 10000 +max_checkpoint_size_bytes: 31457280 +buffer_stake_for_protocol_upgrade_bps: 5000 +address_from_bytes_cost_base: 52 +address_to_u256_cost_base: 52 +address_from_u256_cost_base: 52 +config_read_setting_impl_cost_base: 100 +config_read_setting_impl_cost_per_byte: 40 +dynamic_field_hash_type_and_key_cost_base: 100 +dynamic_field_hash_type_and_key_type_cost_per_byte: 2 +dynamic_field_hash_type_and_key_value_cost_per_byte: 2 +dynamic_field_hash_type_and_key_type_tag_cost_per_byte: 2 +dynamic_field_add_child_object_cost_base: 100 +dynamic_field_add_child_object_type_cost_per_byte: 10 +dynamic_field_add_child_object_value_cost_per_byte: 10 +dynamic_field_add_child_object_struct_tag_cost_per_byte: 10 +dynamic_field_borrow_child_object_cost_base: 100 +dynamic_field_borrow_child_object_child_ref_cost_per_byte: 10 +dynamic_field_borrow_child_object_type_cost_per_byte: 10 +dynamic_field_remove_child_object_cost_base: 100 +dynamic_field_remove_child_object_child_cost_per_byte: 2 +dynamic_field_remove_child_object_type_cost_per_byte: 2 +dynamic_field_has_child_object_cost_base: 100 +dynamic_field_has_child_object_with_ty_cost_base: 100 +dynamic_field_has_child_object_with_ty_type_cost_per_byte: 2 +dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: 2 +event_emit_cost_base: 52 +event_emit_value_size_derivation_cost_per_byte: 2 +event_emit_tag_size_derivation_cost_per_byte: 5 +event_emit_output_cost_per_byte: 10 +object_borrow_uid_cost_base: 52 +object_delete_impl_cost_base: 52 +object_record_new_uid_cost_base: 52 +transfer_transfer_internal_cost_base: 52 +transfer_freeze_object_cost_base: 52 +transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 +tx_context_derive_id_cost_base: 52 +types_is_one_time_witness_cost_base: 52 +types_is_one_time_witness_type_tag_cost_per_byte: 2 +types_is_one_time_witness_type_cost_per_byte: 2 +validator_validate_metadata_cost_base: 52 +validator_validate_metadata_data_cost_per_byte: 2 +crypto_invalid_arguments_cost: 100 +bls12381_bls12381_min_sig_verify_cost_base: 52 +bls12381_bls12381_min_sig_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_sig_verify_msg_cost_per_block: 2 +bls12381_bls12381_min_pk_verify_cost_base: 52 +bls12381_bls12381_min_pk_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_pk_verify_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_keccak256_cost_base: 52 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_sha256_cost_base: 52 +ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_k1_decompress_pubkey_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_k1_secp256k1_verify_sha256_cost_base: 52 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_keccak256_cost_base: 52 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_sha256_cost_base: 52 +ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_keccak256_cost_base: 52 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_sha256_cost_base: 52 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: 2 +ecvrf_ecvrf_verify_cost_base: 52 +ecvrf_ecvrf_verify_alpha_string_cost_per_byte: 2 +ecvrf_ecvrf_verify_alpha_string_cost_per_block: 2 +ed25519_ed25519_verify_cost_base: 52 +ed25519_ed25519_verify_msg_cost_per_byte: 2 +ed25519_ed25519_verify_msg_cost_per_block: 2 +groth16_prepare_verifying_key_bls12381_cost_base: 52 +groth16_prepare_verifying_key_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_public_input_cost_per_byte: 2 +hash_blake2b256_cost_base: 52 +hash_blake2b256_data_cost_per_byte: 2 +hash_blake2b256_data_cost_per_block: 2 +hash_keccak256_cost_base: 52 +hash_keccak256_data_cost_per_byte: 2 +hash_keccak256_data_cost_per_block: 2 +poseidon_bn254_cost_base: 260 +poseidon_bn254_cost_per_block: 10 +group_ops_bls12381_decode_scalar_cost: 52 +group_ops_bls12381_decode_g1_cost: 52 +group_ops_bls12381_decode_g2_cost: 52 +group_ops_bls12381_decode_gt_cost: 52 +group_ops_bls12381_scalar_add_cost: 52 +group_ops_bls12381_g1_add_cost: 52 +group_ops_bls12381_g2_add_cost: 52 +group_ops_bls12381_gt_add_cost: 52 +group_ops_bls12381_scalar_sub_cost: 52 +group_ops_bls12381_g1_sub_cost: 52 +group_ops_bls12381_g2_sub_cost: 52 +group_ops_bls12381_gt_sub_cost: 52 +group_ops_bls12381_scalar_mul_cost: 52 +group_ops_bls12381_g1_mul_cost: 52 +group_ops_bls12381_g2_mul_cost: 52 +group_ops_bls12381_gt_mul_cost: 52 +group_ops_bls12381_scalar_div_cost: 52 +group_ops_bls12381_g1_div_cost: 52 +group_ops_bls12381_g2_div_cost: 52 +group_ops_bls12381_gt_div_cost: 52 +group_ops_bls12381_g1_hash_to_base_cost: 52 +group_ops_bls12381_g2_hash_to_base_cost: 52 +group_ops_bls12381_g1_hash_to_cost_per_byte: 2 +group_ops_bls12381_g2_hash_to_cost_per_byte: 2 +group_ops_bls12381_g1_msm_base_cost: 52 +group_ops_bls12381_g2_msm_base_cost: 52 +group_ops_bls12381_g1_msm_base_cost_per_input: 52 +group_ops_bls12381_g2_msm_base_cost_per_input: 52 +group_ops_bls12381_msm_max_len: 32 +group_ops_bls12381_pairing_cost: 52 +hmac_hmac_sha3_256_cost_base: 52 +hmac_hmac_sha3_256_input_cost_per_byte: 2 +hmac_hmac_sha3_256_input_cost_per_block: 2 +check_zklogin_id_cost_base: 200 +check_zklogin_issuer_cost_base: 200 +vdf_verify_vdf_cost: 1500 +vdf_hash_to_input_cost: 100 +bcs_per_byte_serialized_cost: 2 +bcs_legacy_min_output_size_cost: 1 +bcs_failure_cost: 52 +hash_sha2_256_base_cost: 52 +hash_sha2_256_per_byte_cost: 2 +hash_sha2_256_legacy_min_input_len_cost: 1 +hash_sha3_256_base_cost: 52 +hash_sha3_256_per_byte_cost: 2 +hash_sha3_256_legacy_min_input_len_cost: 1 +type_name_get_base_cost: 52 +type_name_get_per_byte_cost: 2 +string_check_utf8_base_cost: 52 +string_check_utf8_per_byte_cost: 2 +string_is_char_boundary_base_cost: 52 +string_sub_string_base_cost: 52 +string_sub_string_per_byte_cost: 2 +string_index_of_base_cost: 52 +string_index_of_per_byte_pattern_cost: 2 +string_index_of_per_byte_searched_cost: 2 +vector_empty_base_cost: 52 +vector_length_base_cost: 52 +vector_push_back_base_cost: 52 +vector_push_back_legacy_per_abstract_memory_unit_cost: 2 +vector_borrow_base_cost: 52 +vector_pop_back_base_cost: 52 +vector_destroy_empty_base_cost: 52 +vector_swap_base_cost: 52 +debug_print_base_cost: 52 +debug_print_stack_trace_base_cost: 52 +execution_version: 3 +consensus_bad_nodes_stake_threshold: 20 +max_jwk_votes_per_validator_per_epoch: 240 +max_age_of_jwk_in_epochs: 1 +random_beacon_reduction_allowed_delta: 800 +random_beacon_reduction_lower_bound: 700 +random_beacon_dkg_timeout_round: 3000 +random_beacon_min_round_interval_ms: 500 +random_beacon_dkg_version: 1 +consensus_max_transaction_size_bytes: 262144 +consensus_max_transactions_in_block_bytes: 524288 +consensus_max_num_transactions_in_block: 512 +max_accumulated_txn_cost_per_object_in_narwhal_commit: 100 +max_deferral_rounds_for_congestion_control: 10 +min_checkpoint_interval_ms: 200 +checkpoint_summary_version_specific_data: 1 +max_soft_bundle_size: 5 +bridge_should_try_to_finalize_committee: true +max_accumulated_txn_cost_per_object_in_mysticeti_commit: 10 + diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_63.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_63.snap new file mode 100644 index 0000000000000..c004a35dfdee9 --- /dev/null +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_63.snap @@ -0,0 +1,339 @@ +--- +source: crates/sui-protocol-config/src/lib.rs +expression: "ProtocolConfig::get_for_version(cur, *chain_id)" +--- +version: 63 +feature_flags: + package_upgrades: true + commit_root_state_digest: true + advance_epoch_start_time_in_safe_mode: true + loaded_child_objects_fixed: true + missing_type_is_compatibility_error: true + scoring_decision_with_validity_cutoff: true + consensus_order_end_of_epoch_last: true + disallow_adding_abilities_on_upgrade: true + disable_invariant_violation_check_in_swap_loc: true + advance_to_highest_supported_protocol_version: true + ban_entry_init: true + package_digest_hash_module: true + disallow_change_struct_type_params_on_upgrade: true + no_extraneous_module_bytes: true + narwhal_versioned_metadata: true + zklogin_auth: true + consensus_transaction_ordering: ByGasPrice + simplified_unwrap_then_delete: true + upgraded_multisig_supported: true + txn_base_cost_as_multiplier: true + shared_object_deletion: true + narwhal_new_leader_election_schedule: true + loaded_child_object_format: true + enable_jwk_consensus_updates: true + end_of_epoch_transaction_supported: true + simple_conservation_checks: true + loaded_child_object_format_type: true + receive_objects: true + random_beacon: true + bridge: true + enable_effects_v2: true + narwhal_certificate_v2: true + verify_legacy_zklogin_address: true + recompute_has_public_transfer_in_execution: true + accept_zklogin_in_multisig: true + include_consensus_digest_in_prologue: true + hardened_otw_check: true + allow_receiving_object_id: true + enable_poseidon: true + enable_coin_deny_list: true + enable_group_ops_native_functions: true + enable_group_ops_native_function_msm: true + reject_mutable_random_on_entry_functions: true + per_object_congestion_control_mode: TotalGasBudgetWithCap + consensus_choice: Mysticeti + consensus_network: Tonic + zklogin_max_epoch_upper_bound_delta: 30 + mysticeti_leader_scoring_and_schedule: true + reshare_at_same_initial_version: true + resolve_abort_locations_to_package_id: true + mysticeti_use_committed_subdag_digest: true + enable_vdf: true + record_consensus_determined_version_assignments_in_prologue: true + fresh_vm_on_framework_upgrade: true + prepend_prologue_tx_in_consensus_commit_in_checkpoints: true + mysticeti_num_leaders_per_round: 1 + soft_bundle: true + enable_coin_deny_list_v2: true + passkey_auth: true + authority_capabilities_v2: true + rethrow_serialization_type_layout_errors: true + consensus_distributed_vote_scoring_strategy: true + consensus_round_prober: true + validate_identifier_inputs: true + mysticeti_fastpath: true + relocate_event_module: true +max_tx_size_bytes: 131072 +max_input_objects: 2048 +max_size_written_objects: 5000000 +max_size_written_objects_system_tx: 50000000 +max_serialized_tx_effects_size_bytes: 524288 +max_serialized_tx_effects_size_bytes_system_tx: 8388608 +max_gas_payment_objects: 256 +max_modules_in_publish: 64 +max_package_dependencies: 32 +max_arguments: 512 +max_type_arguments: 16 +max_type_argument_depth: 16 +max_pure_argument_size: 16384 +max_programmable_tx_commands: 1024 +move_binary_format_version: 7 +min_move_binary_format_version: 6 +binary_module_handles: 100 +binary_struct_handles: 300 +binary_function_handles: 1500 +binary_function_instantiations: 750 +binary_signatures: 1000 +binary_constant_pool: 4000 +binary_identifiers: 10000 +binary_address_identifiers: 100 +binary_struct_defs: 200 +binary_struct_def_instantiations: 100 +binary_function_defs: 1000 +binary_field_handles: 500 +binary_field_instantiations: 250 +binary_friend_decls: 100 +max_move_object_size: 256000 +max_move_package_size: 102400 +max_publish_or_upgrade_per_ptb: 5 +max_tx_gas: 50000000000 +max_gas_price: 100000 +max_gas_computation_bucket: 5000000 +gas_rounding_step: 1000 +max_loop_depth: 5 +max_generic_instantiation_length: 32 +max_function_parameters: 128 +max_basic_blocks: 1024 +max_value_stack_size: 1024 +max_type_nodes: 256 +max_push_size: 10000 +max_struct_definitions: 200 +max_function_definitions: 1000 +max_fields_in_struct: 32 +max_dependency_depth: 100 +max_num_event_emit: 1024 +max_num_new_move_object_ids: 2048 +max_num_new_move_object_ids_system_tx: 32768 +max_num_deleted_move_object_ids: 2048 +max_num_deleted_move_object_ids_system_tx: 32768 +max_num_transferred_move_object_ids: 2048 +max_num_transferred_move_object_ids_system_tx: 32768 +max_event_emit_size: 256000 +max_event_emit_size_total: 65536000 +max_move_vector_len: 262144 +max_move_identifier_len: 128 +max_move_value_depth: 128 +max_back_edges_per_function: 10000 +max_back_edges_per_module: 10000 +max_verifier_meter_ticks_per_function: 16000000 +max_meter_ticks_per_module: 16000000 +max_meter_ticks_per_package: 16000000 +object_runtime_max_num_cached_objects: 1000 +object_runtime_max_num_cached_objects_system_tx: 16000 +object_runtime_max_num_store_entries: 1000 +object_runtime_max_num_store_entries_system_tx: 16000 +base_tx_cost_fixed: 1000 +package_publish_cost_fixed: 1000 +base_tx_cost_per_byte: 0 +package_publish_cost_per_byte: 80 +obj_access_cost_read_per_byte: 15 +obj_access_cost_mutate_per_byte: 40 +obj_access_cost_delete_per_byte: 40 +obj_access_cost_verify_per_byte: 200 +max_type_to_layout_nodes: 512 +gas_model_version: 8 +obj_data_cost_refundable: 100 +obj_metadata_cost_non_refundable: 50 +storage_rebate_rate: 9900 +storage_fund_reinvest_rate: 500 +reward_slashing_rate: 10000 +storage_gas_price: 76 +max_transactions_per_checkpoint: 10000 +max_checkpoint_size_bytes: 31457280 +buffer_stake_for_protocol_upgrade_bps: 5000 +address_from_bytes_cost_base: 52 +address_to_u256_cost_base: 52 +address_from_u256_cost_base: 52 +config_read_setting_impl_cost_base: 100 +config_read_setting_impl_cost_per_byte: 40 +dynamic_field_hash_type_and_key_cost_base: 100 +dynamic_field_hash_type_and_key_type_cost_per_byte: 2 +dynamic_field_hash_type_and_key_value_cost_per_byte: 2 +dynamic_field_hash_type_and_key_type_tag_cost_per_byte: 2 +dynamic_field_add_child_object_cost_base: 100 +dynamic_field_add_child_object_type_cost_per_byte: 10 +dynamic_field_add_child_object_value_cost_per_byte: 10 +dynamic_field_add_child_object_struct_tag_cost_per_byte: 10 +dynamic_field_borrow_child_object_cost_base: 100 +dynamic_field_borrow_child_object_child_ref_cost_per_byte: 10 +dynamic_field_borrow_child_object_type_cost_per_byte: 10 +dynamic_field_remove_child_object_cost_base: 100 +dynamic_field_remove_child_object_child_cost_per_byte: 2 +dynamic_field_remove_child_object_type_cost_per_byte: 2 +dynamic_field_has_child_object_cost_base: 100 +dynamic_field_has_child_object_with_ty_cost_base: 100 +dynamic_field_has_child_object_with_ty_type_cost_per_byte: 2 +dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: 2 +event_emit_cost_base: 52 +event_emit_value_size_derivation_cost_per_byte: 2 +event_emit_tag_size_derivation_cost_per_byte: 5 +event_emit_output_cost_per_byte: 10 +object_borrow_uid_cost_base: 52 +object_delete_impl_cost_base: 52 +object_record_new_uid_cost_base: 52 +transfer_transfer_internal_cost_base: 52 +transfer_freeze_object_cost_base: 52 +transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 +tx_context_derive_id_cost_base: 52 +types_is_one_time_witness_cost_base: 52 +types_is_one_time_witness_type_tag_cost_per_byte: 2 +types_is_one_time_witness_type_cost_per_byte: 2 +validator_validate_metadata_cost_base: 52 +validator_validate_metadata_data_cost_per_byte: 2 +crypto_invalid_arguments_cost: 100 +bls12381_bls12381_min_sig_verify_cost_base: 52 +bls12381_bls12381_min_sig_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_sig_verify_msg_cost_per_block: 2 +bls12381_bls12381_min_pk_verify_cost_base: 52 +bls12381_bls12381_min_pk_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_pk_verify_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_keccak256_cost_base: 52 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_sha256_cost_base: 52 +ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_k1_decompress_pubkey_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_k1_secp256k1_verify_sha256_cost_base: 52 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_keccak256_cost_base: 52 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_sha256_cost_base: 52 +ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_keccak256_cost_base: 52 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_sha256_cost_base: 52 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: 2 +ecvrf_ecvrf_verify_cost_base: 52 +ecvrf_ecvrf_verify_alpha_string_cost_per_byte: 2 +ecvrf_ecvrf_verify_alpha_string_cost_per_block: 2 +ed25519_ed25519_verify_cost_base: 52 +ed25519_ed25519_verify_msg_cost_per_byte: 2 +ed25519_ed25519_verify_msg_cost_per_block: 2 +groth16_prepare_verifying_key_bls12381_cost_base: 52 +groth16_prepare_verifying_key_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_public_input_cost_per_byte: 2 +hash_blake2b256_cost_base: 52 +hash_blake2b256_data_cost_per_byte: 2 +hash_blake2b256_data_cost_per_block: 2 +hash_keccak256_cost_base: 52 +hash_keccak256_data_cost_per_byte: 2 +hash_keccak256_data_cost_per_block: 2 +poseidon_bn254_cost_base: 260 +poseidon_bn254_cost_per_block: 10 +group_ops_bls12381_decode_scalar_cost: 52 +group_ops_bls12381_decode_g1_cost: 52 +group_ops_bls12381_decode_g2_cost: 52 +group_ops_bls12381_decode_gt_cost: 52 +group_ops_bls12381_scalar_add_cost: 52 +group_ops_bls12381_g1_add_cost: 52 +group_ops_bls12381_g2_add_cost: 52 +group_ops_bls12381_gt_add_cost: 52 +group_ops_bls12381_scalar_sub_cost: 52 +group_ops_bls12381_g1_sub_cost: 52 +group_ops_bls12381_g2_sub_cost: 52 +group_ops_bls12381_gt_sub_cost: 52 +group_ops_bls12381_scalar_mul_cost: 52 +group_ops_bls12381_g1_mul_cost: 52 +group_ops_bls12381_g2_mul_cost: 52 +group_ops_bls12381_gt_mul_cost: 52 +group_ops_bls12381_scalar_div_cost: 52 +group_ops_bls12381_g1_div_cost: 52 +group_ops_bls12381_g2_div_cost: 52 +group_ops_bls12381_gt_div_cost: 52 +group_ops_bls12381_g1_hash_to_base_cost: 52 +group_ops_bls12381_g2_hash_to_base_cost: 52 +group_ops_bls12381_g1_hash_to_cost_per_byte: 2 +group_ops_bls12381_g2_hash_to_cost_per_byte: 2 +group_ops_bls12381_g1_msm_base_cost: 52 +group_ops_bls12381_g2_msm_base_cost: 52 +group_ops_bls12381_g1_msm_base_cost_per_input: 52 +group_ops_bls12381_g2_msm_base_cost_per_input: 52 +group_ops_bls12381_msm_max_len: 32 +group_ops_bls12381_pairing_cost: 52 +hmac_hmac_sha3_256_cost_base: 52 +hmac_hmac_sha3_256_input_cost_per_byte: 2 +hmac_hmac_sha3_256_input_cost_per_block: 2 +check_zklogin_id_cost_base: 200 +check_zklogin_issuer_cost_base: 200 +vdf_verify_vdf_cost: 1500 +vdf_hash_to_input_cost: 100 +bcs_per_byte_serialized_cost: 2 +bcs_legacy_min_output_size_cost: 1 +bcs_failure_cost: 52 +hash_sha2_256_base_cost: 52 +hash_sha2_256_per_byte_cost: 2 +hash_sha2_256_legacy_min_input_len_cost: 1 +hash_sha3_256_base_cost: 52 +hash_sha3_256_per_byte_cost: 2 +hash_sha3_256_legacy_min_input_len_cost: 1 +type_name_get_base_cost: 52 +type_name_get_per_byte_cost: 2 +string_check_utf8_base_cost: 52 +string_check_utf8_per_byte_cost: 2 +string_is_char_boundary_base_cost: 52 +string_sub_string_base_cost: 52 +string_sub_string_per_byte_cost: 2 +string_index_of_base_cost: 52 +string_index_of_per_byte_pattern_cost: 2 +string_index_of_per_byte_searched_cost: 2 +vector_empty_base_cost: 52 +vector_length_base_cost: 52 +vector_push_back_base_cost: 52 +vector_push_back_legacy_per_abstract_memory_unit_cost: 2 +vector_borrow_base_cost: 52 +vector_pop_back_base_cost: 52 +vector_destroy_empty_base_cost: 52 +vector_swap_base_cost: 52 +debug_print_base_cost: 52 +debug_print_stack_trace_base_cost: 52 +execution_version: 3 +consensus_bad_nodes_stake_threshold: 20 +max_jwk_votes_per_validator_per_epoch: 240 +max_age_of_jwk_in_epochs: 1 +random_beacon_reduction_allowed_delta: 800 +random_beacon_reduction_lower_bound: 700 +random_beacon_dkg_timeout_round: 3000 +random_beacon_min_round_interval_ms: 500 +random_beacon_dkg_version: 1 +consensus_max_transaction_size_bytes: 262144 +consensus_max_transactions_in_block_bytes: 524288 +consensus_max_num_transactions_in_block: 512 +max_accumulated_txn_cost_per_object_in_narwhal_commit: 240000000 +max_deferral_rounds_for_congestion_control: 10 +min_checkpoint_interval_ms: 200 +checkpoint_summary_version_specific_data: 1 +max_soft_bundle_size: 5 +bridge_should_try_to_finalize_committee: true +max_accumulated_txn_cost_per_object_in_mysticeti_commit: 18500000 +gas_budget_based_txn_cost_cap_factor: 400000 diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_64.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_64.snap new file mode 100644 index 0000000000000..eebe1e4c3f794 --- /dev/null +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_64.snap @@ -0,0 +1,340 @@ +--- +source: crates/sui-protocol-config/src/lib.rs +expression: "ProtocolConfig::get_for_version(cur, *chain_id)" +--- +version: 64 +feature_flags: + package_upgrades: true + commit_root_state_digest: true + advance_epoch_start_time_in_safe_mode: true + loaded_child_objects_fixed: true + missing_type_is_compatibility_error: true + scoring_decision_with_validity_cutoff: true + consensus_order_end_of_epoch_last: true + disallow_adding_abilities_on_upgrade: true + disable_invariant_violation_check_in_swap_loc: true + advance_to_highest_supported_protocol_version: true + ban_entry_init: true + package_digest_hash_module: true + disallow_change_struct_type_params_on_upgrade: true + no_extraneous_module_bytes: true + narwhal_versioned_metadata: true + zklogin_auth: true + consensus_transaction_ordering: ByGasPrice + simplified_unwrap_then_delete: true + upgraded_multisig_supported: true + txn_base_cost_as_multiplier: true + shared_object_deletion: true + narwhal_new_leader_election_schedule: true + loaded_child_object_format: true + enable_jwk_consensus_updates: true + end_of_epoch_transaction_supported: true + simple_conservation_checks: true + loaded_child_object_format_type: true + receive_objects: true + random_beacon: true + bridge: true + enable_effects_v2: true + narwhal_certificate_v2: true + verify_legacy_zklogin_address: true + recompute_has_public_transfer_in_execution: true + accept_zklogin_in_multisig: true + include_consensus_digest_in_prologue: true + hardened_otw_check: true + allow_receiving_object_id: true + enable_poseidon: true + enable_coin_deny_list: true + enable_group_ops_native_functions: true + enable_group_ops_native_function_msm: true + reject_mutable_random_on_entry_functions: true + per_object_congestion_control_mode: TotalTxCount + consensus_choice: Mysticeti + consensus_network: Tonic + zklogin_max_epoch_upper_bound_delta: 30 + mysticeti_leader_scoring_and_schedule: true + reshare_at_same_initial_version: true + resolve_abort_locations_to_package_id: true + mysticeti_use_committed_subdag_digest: true + enable_vdf: true + record_consensus_determined_version_assignments_in_prologue: true + fresh_vm_on_framework_upgrade: true + prepend_prologue_tx_in_consensus_commit_in_checkpoints: true + mysticeti_num_leaders_per_round: 1 + soft_bundle: true + enable_coin_deny_list_v2: true + passkey_auth: true + authority_capabilities_v2: true + rethrow_serialization_type_layout_errors: true + consensus_distributed_vote_scoring_strategy: true + consensus_round_prober: true + validate_identifier_inputs: true + mysticeti_fastpath: true + relocate_event_module: true +max_tx_size_bytes: 131072 +max_input_objects: 2048 +max_size_written_objects: 5000000 +max_size_written_objects_system_tx: 50000000 +max_serialized_tx_effects_size_bytes: 524288 +max_serialized_tx_effects_size_bytes_system_tx: 8388608 +max_gas_payment_objects: 256 +max_modules_in_publish: 64 +max_package_dependencies: 32 +max_arguments: 512 +max_type_arguments: 16 +max_type_argument_depth: 16 +max_pure_argument_size: 16384 +max_programmable_tx_commands: 1024 +move_binary_format_version: 7 +min_move_binary_format_version: 6 +binary_module_handles: 100 +binary_struct_handles: 300 +binary_function_handles: 1500 +binary_function_instantiations: 750 +binary_signatures: 1000 +binary_constant_pool: 4000 +binary_identifiers: 10000 +binary_address_identifiers: 100 +binary_struct_defs: 200 +binary_struct_def_instantiations: 100 +binary_function_defs: 1000 +binary_field_handles: 500 +binary_field_instantiations: 250 +binary_friend_decls: 100 +max_move_object_size: 256000 +max_move_package_size: 102400 +max_publish_or_upgrade_per_ptb: 5 +max_tx_gas: 50000000000 +max_gas_price: 100000 +max_gas_computation_bucket: 5000000 +gas_rounding_step: 1000 +max_loop_depth: 5 +max_generic_instantiation_length: 32 +max_function_parameters: 128 +max_basic_blocks: 1024 +max_value_stack_size: 1024 +max_type_nodes: 256 +max_push_size: 10000 +max_struct_definitions: 200 +max_function_definitions: 1000 +max_fields_in_struct: 32 +max_dependency_depth: 100 +max_num_event_emit: 1024 +max_num_new_move_object_ids: 2048 +max_num_new_move_object_ids_system_tx: 32768 +max_num_deleted_move_object_ids: 2048 +max_num_deleted_move_object_ids_system_tx: 32768 +max_num_transferred_move_object_ids: 2048 +max_num_transferred_move_object_ids_system_tx: 32768 +max_event_emit_size: 256000 +max_event_emit_size_total: 65536000 +max_move_vector_len: 262144 +max_move_identifier_len: 128 +max_move_value_depth: 128 +max_back_edges_per_function: 10000 +max_back_edges_per_module: 10000 +max_verifier_meter_ticks_per_function: 16000000 +max_meter_ticks_per_module: 16000000 +max_meter_ticks_per_package: 16000000 +object_runtime_max_num_cached_objects: 1000 +object_runtime_max_num_cached_objects_system_tx: 16000 +object_runtime_max_num_store_entries: 1000 +object_runtime_max_num_store_entries_system_tx: 16000 +base_tx_cost_fixed: 1000 +package_publish_cost_fixed: 1000 +base_tx_cost_per_byte: 0 +package_publish_cost_per_byte: 80 +obj_access_cost_read_per_byte: 15 +obj_access_cost_mutate_per_byte: 40 +obj_access_cost_delete_per_byte: 40 +obj_access_cost_verify_per_byte: 200 +max_type_to_layout_nodes: 512 +gas_model_version: 8 +obj_data_cost_refundable: 100 +obj_metadata_cost_non_refundable: 50 +storage_rebate_rate: 9900 +storage_fund_reinvest_rate: 500 +reward_slashing_rate: 10000 +storage_gas_price: 76 +max_transactions_per_checkpoint: 10000 +max_checkpoint_size_bytes: 31457280 +buffer_stake_for_protocol_upgrade_bps: 5000 +address_from_bytes_cost_base: 52 +address_to_u256_cost_base: 52 +address_from_u256_cost_base: 52 +config_read_setting_impl_cost_base: 100 +config_read_setting_impl_cost_per_byte: 40 +dynamic_field_hash_type_and_key_cost_base: 100 +dynamic_field_hash_type_and_key_type_cost_per_byte: 2 +dynamic_field_hash_type_and_key_value_cost_per_byte: 2 +dynamic_field_hash_type_and_key_type_tag_cost_per_byte: 2 +dynamic_field_add_child_object_cost_base: 100 +dynamic_field_add_child_object_type_cost_per_byte: 10 +dynamic_field_add_child_object_value_cost_per_byte: 10 +dynamic_field_add_child_object_struct_tag_cost_per_byte: 10 +dynamic_field_borrow_child_object_cost_base: 100 +dynamic_field_borrow_child_object_child_ref_cost_per_byte: 10 +dynamic_field_borrow_child_object_type_cost_per_byte: 10 +dynamic_field_remove_child_object_cost_base: 100 +dynamic_field_remove_child_object_child_cost_per_byte: 2 +dynamic_field_remove_child_object_type_cost_per_byte: 2 +dynamic_field_has_child_object_cost_base: 100 +dynamic_field_has_child_object_with_ty_cost_base: 100 +dynamic_field_has_child_object_with_ty_type_cost_per_byte: 2 +dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: 2 +event_emit_cost_base: 52 +event_emit_value_size_derivation_cost_per_byte: 2 +event_emit_tag_size_derivation_cost_per_byte: 5 +event_emit_output_cost_per_byte: 10 +object_borrow_uid_cost_base: 52 +object_delete_impl_cost_base: 52 +object_record_new_uid_cost_base: 52 +transfer_transfer_internal_cost_base: 52 +transfer_freeze_object_cost_base: 52 +transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 +tx_context_derive_id_cost_base: 52 +types_is_one_time_witness_cost_base: 52 +types_is_one_time_witness_type_tag_cost_per_byte: 2 +types_is_one_time_witness_type_cost_per_byte: 2 +validator_validate_metadata_cost_base: 52 +validator_validate_metadata_data_cost_per_byte: 2 +crypto_invalid_arguments_cost: 100 +bls12381_bls12381_min_sig_verify_cost_base: 52 +bls12381_bls12381_min_sig_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_sig_verify_msg_cost_per_block: 2 +bls12381_bls12381_min_pk_verify_cost_base: 52 +bls12381_bls12381_min_pk_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_pk_verify_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_keccak256_cost_base: 52 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_sha256_cost_base: 52 +ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_k1_decompress_pubkey_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_k1_secp256k1_verify_sha256_cost_base: 52 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_keccak256_cost_base: 52 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_sha256_cost_base: 52 +ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_keccak256_cost_base: 52 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_sha256_cost_base: 52 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: 2 +ecvrf_ecvrf_verify_cost_base: 52 +ecvrf_ecvrf_verify_alpha_string_cost_per_byte: 2 +ecvrf_ecvrf_verify_alpha_string_cost_per_block: 2 +ed25519_ed25519_verify_cost_base: 52 +ed25519_ed25519_verify_msg_cost_per_byte: 2 +ed25519_ed25519_verify_msg_cost_per_block: 2 +groth16_prepare_verifying_key_bls12381_cost_base: 52 +groth16_prepare_verifying_key_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_public_input_cost_per_byte: 2 +hash_blake2b256_cost_base: 52 +hash_blake2b256_data_cost_per_byte: 2 +hash_blake2b256_data_cost_per_block: 2 +hash_keccak256_cost_base: 52 +hash_keccak256_data_cost_per_byte: 2 +hash_keccak256_data_cost_per_block: 2 +poseidon_bn254_cost_base: 260 +poseidon_bn254_cost_per_block: 10 +group_ops_bls12381_decode_scalar_cost: 52 +group_ops_bls12381_decode_g1_cost: 52 +group_ops_bls12381_decode_g2_cost: 52 +group_ops_bls12381_decode_gt_cost: 52 +group_ops_bls12381_scalar_add_cost: 52 +group_ops_bls12381_g1_add_cost: 52 +group_ops_bls12381_g2_add_cost: 52 +group_ops_bls12381_gt_add_cost: 52 +group_ops_bls12381_scalar_sub_cost: 52 +group_ops_bls12381_g1_sub_cost: 52 +group_ops_bls12381_g2_sub_cost: 52 +group_ops_bls12381_gt_sub_cost: 52 +group_ops_bls12381_scalar_mul_cost: 52 +group_ops_bls12381_g1_mul_cost: 52 +group_ops_bls12381_g2_mul_cost: 52 +group_ops_bls12381_gt_mul_cost: 52 +group_ops_bls12381_scalar_div_cost: 52 +group_ops_bls12381_g1_div_cost: 52 +group_ops_bls12381_g2_div_cost: 52 +group_ops_bls12381_gt_div_cost: 52 +group_ops_bls12381_g1_hash_to_base_cost: 52 +group_ops_bls12381_g2_hash_to_base_cost: 52 +group_ops_bls12381_g1_hash_to_cost_per_byte: 2 +group_ops_bls12381_g2_hash_to_cost_per_byte: 2 +group_ops_bls12381_g1_msm_base_cost: 52 +group_ops_bls12381_g2_msm_base_cost: 52 +group_ops_bls12381_g1_msm_base_cost_per_input: 52 +group_ops_bls12381_g2_msm_base_cost_per_input: 52 +group_ops_bls12381_msm_max_len: 32 +group_ops_bls12381_pairing_cost: 52 +hmac_hmac_sha3_256_cost_base: 52 +hmac_hmac_sha3_256_input_cost_per_byte: 2 +hmac_hmac_sha3_256_input_cost_per_block: 2 +check_zklogin_id_cost_base: 200 +check_zklogin_issuer_cost_base: 200 +vdf_verify_vdf_cost: 1500 +vdf_hash_to_input_cost: 100 +bcs_per_byte_serialized_cost: 2 +bcs_legacy_min_output_size_cost: 1 +bcs_failure_cost: 52 +hash_sha2_256_base_cost: 52 +hash_sha2_256_per_byte_cost: 2 +hash_sha2_256_legacy_min_input_len_cost: 1 +hash_sha3_256_base_cost: 52 +hash_sha3_256_per_byte_cost: 2 +hash_sha3_256_legacy_min_input_len_cost: 1 +type_name_get_base_cost: 52 +type_name_get_per_byte_cost: 2 +string_check_utf8_base_cost: 52 +string_check_utf8_per_byte_cost: 2 +string_is_char_boundary_base_cost: 52 +string_sub_string_base_cost: 52 +string_sub_string_per_byte_cost: 2 +string_index_of_base_cost: 52 +string_index_of_per_byte_pattern_cost: 2 +string_index_of_per_byte_searched_cost: 2 +vector_empty_base_cost: 52 +vector_length_base_cost: 52 +vector_push_back_base_cost: 52 +vector_push_back_legacy_per_abstract_memory_unit_cost: 2 +vector_borrow_base_cost: 52 +vector_pop_back_base_cost: 52 +vector_destroy_empty_base_cost: 52 +vector_swap_base_cost: 52 +debug_print_base_cost: 52 +debug_print_stack_trace_base_cost: 52 +execution_version: 3 +consensus_bad_nodes_stake_threshold: 20 +max_jwk_votes_per_validator_per_epoch: 240 +max_age_of_jwk_in_epochs: 1 +random_beacon_reduction_allowed_delta: 800 +random_beacon_reduction_lower_bound: 700 +random_beacon_dkg_timeout_round: 3000 +random_beacon_min_round_interval_ms: 500 +random_beacon_dkg_version: 1 +consensus_max_transaction_size_bytes: 262144 +consensus_max_transactions_in_block_bytes: 524288 +consensus_max_num_transactions_in_block: 512 +max_accumulated_txn_cost_per_object_in_narwhal_commit: 40 +max_deferral_rounds_for_congestion_control: 10 +min_checkpoint_interval_ms: 200 +checkpoint_summary_version_specific_data: 1 +max_soft_bundle_size: 5 +bridge_should_try_to_finalize_committee: true +max_accumulated_txn_cost_per_object_in_mysticeti_commit: 3 +gas_budget_based_txn_cost_cap_factor: 400000 + diff --git a/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_65.snap b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_65.snap new file mode 100644 index 0000000000000..df9f16ca48440 --- /dev/null +++ b/crates/sui-protocol-config/src/snapshots/sui_protocol_config__test__version_65.snap @@ -0,0 +1,340 @@ +--- +source: crates/sui-protocol-config/src/lib.rs +expression: "ProtocolConfig::get_for_version(cur, *chain_id)" +--- +version: 65 +feature_flags: + package_upgrades: true + commit_root_state_digest: true + advance_epoch_start_time_in_safe_mode: true + loaded_child_objects_fixed: true + missing_type_is_compatibility_error: true + scoring_decision_with_validity_cutoff: true + consensus_order_end_of_epoch_last: true + disallow_adding_abilities_on_upgrade: true + disable_invariant_violation_check_in_swap_loc: true + advance_to_highest_supported_protocol_version: true + ban_entry_init: true + package_digest_hash_module: true + disallow_change_struct_type_params_on_upgrade: true + no_extraneous_module_bytes: true + narwhal_versioned_metadata: true + zklogin_auth: true + consensus_transaction_ordering: ByGasPrice + simplified_unwrap_then_delete: true + upgraded_multisig_supported: true + txn_base_cost_as_multiplier: true + shared_object_deletion: true + narwhal_new_leader_election_schedule: true + loaded_child_object_format: true + enable_jwk_consensus_updates: true + end_of_epoch_transaction_supported: true + simple_conservation_checks: true + loaded_child_object_format_type: true + receive_objects: true + random_beacon: true + bridge: true + enable_effects_v2: true + narwhal_certificate_v2: true + verify_legacy_zklogin_address: true + recompute_has_public_transfer_in_execution: true + accept_zklogin_in_multisig: true + include_consensus_digest_in_prologue: true + hardened_otw_check: true + allow_receiving_object_id: true + enable_poseidon: true + enable_coin_deny_list: true + enable_group_ops_native_functions: true + enable_group_ops_native_function_msm: true + reject_mutable_random_on_entry_functions: true + per_object_congestion_control_mode: TotalTxCount + consensus_choice: Mysticeti + consensus_network: Tonic + zklogin_max_epoch_upper_bound_delta: 30 + mysticeti_leader_scoring_and_schedule: true + reshare_at_same_initial_version: true + resolve_abort_locations_to_package_id: true + mysticeti_use_committed_subdag_digest: true + enable_vdf: true + record_consensus_determined_version_assignments_in_prologue: true + fresh_vm_on_framework_upgrade: true + prepend_prologue_tx_in_consensus_commit_in_checkpoints: true + mysticeti_num_leaders_per_round: 1 + soft_bundle: true + enable_coin_deny_list_v2: true + passkey_auth: true + authority_capabilities_v2: true + rethrow_serialization_type_layout_errors: true + consensus_distributed_vote_scoring_strategy: true + consensus_round_prober: true + validate_identifier_inputs: true + mysticeti_fastpath: true + relocate_event_module: true +max_tx_size_bytes: 131072 +max_input_objects: 2048 +max_size_written_objects: 5000000 +max_size_written_objects_system_tx: 50000000 +max_serialized_tx_effects_size_bytes: 524288 +max_serialized_tx_effects_size_bytes_system_tx: 8388608 +max_gas_payment_objects: 256 +max_modules_in_publish: 64 +max_package_dependencies: 32 +max_arguments: 512 +max_type_arguments: 16 +max_type_argument_depth: 16 +max_pure_argument_size: 16384 +max_programmable_tx_commands: 1024 +move_binary_format_version: 7 +min_move_binary_format_version: 6 +binary_module_handles: 100 +binary_struct_handles: 300 +binary_function_handles: 1500 +binary_function_instantiations: 750 +binary_signatures: 1000 +binary_constant_pool: 4000 +binary_identifiers: 10000 +binary_address_identifiers: 100 +binary_struct_defs: 200 +binary_struct_def_instantiations: 100 +binary_function_defs: 1000 +binary_field_handles: 500 +binary_field_instantiations: 250 +binary_friend_decls: 100 +max_move_object_size: 256000 +max_move_package_size: 102400 +max_publish_or_upgrade_per_ptb: 5 +max_tx_gas: 50000000000 +max_gas_price: 100000 +max_gas_computation_bucket: 5000000 +gas_rounding_step: 1000 +max_loop_depth: 5 +max_generic_instantiation_length: 32 +max_function_parameters: 128 +max_basic_blocks: 1024 +max_value_stack_size: 1024 +max_type_nodes: 256 +max_push_size: 10000 +max_struct_definitions: 200 +max_function_definitions: 1000 +max_fields_in_struct: 32 +max_dependency_depth: 100 +max_num_event_emit: 1024 +max_num_new_move_object_ids: 2048 +max_num_new_move_object_ids_system_tx: 32768 +max_num_deleted_move_object_ids: 2048 +max_num_deleted_move_object_ids_system_tx: 32768 +max_num_transferred_move_object_ids: 2048 +max_num_transferred_move_object_ids_system_tx: 32768 +max_event_emit_size: 256000 +max_event_emit_size_total: 65536000 +max_move_vector_len: 262144 +max_move_identifier_len: 128 +max_move_value_depth: 128 +max_back_edges_per_function: 10000 +max_back_edges_per_module: 10000 +max_verifier_meter_ticks_per_function: 16000000 +max_meter_ticks_per_module: 16000000 +max_meter_ticks_per_package: 16000000 +object_runtime_max_num_cached_objects: 1000 +object_runtime_max_num_cached_objects_system_tx: 16000 +object_runtime_max_num_store_entries: 1000 +object_runtime_max_num_store_entries_system_tx: 16000 +base_tx_cost_fixed: 1000 +package_publish_cost_fixed: 1000 +base_tx_cost_per_byte: 0 +package_publish_cost_per_byte: 80 +obj_access_cost_read_per_byte: 15 +obj_access_cost_mutate_per_byte: 40 +obj_access_cost_delete_per_byte: 40 +obj_access_cost_verify_per_byte: 200 +max_type_to_layout_nodes: 512 +gas_model_version: 8 +obj_data_cost_refundable: 100 +obj_metadata_cost_non_refundable: 50 +storage_rebate_rate: 9900 +storage_fund_reinvest_rate: 500 +reward_slashing_rate: 10000 +storage_gas_price: 76 +max_transactions_per_checkpoint: 10000 +max_checkpoint_size_bytes: 31457280 +buffer_stake_for_protocol_upgrade_bps: 5000 +address_from_bytes_cost_base: 52 +address_to_u256_cost_base: 52 +address_from_u256_cost_base: 52 +config_read_setting_impl_cost_base: 100 +config_read_setting_impl_cost_per_byte: 40 +dynamic_field_hash_type_and_key_cost_base: 100 +dynamic_field_hash_type_and_key_type_cost_per_byte: 2 +dynamic_field_hash_type_and_key_value_cost_per_byte: 2 +dynamic_field_hash_type_and_key_type_tag_cost_per_byte: 2 +dynamic_field_add_child_object_cost_base: 100 +dynamic_field_add_child_object_type_cost_per_byte: 10 +dynamic_field_add_child_object_value_cost_per_byte: 10 +dynamic_field_add_child_object_struct_tag_cost_per_byte: 10 +dynamic_field_borrow_child_object_cost_base: 100 +dynamic_field_borrow_child_object_child_ref_cost_per_byte: 10 +dynamic_field_borrow_child_object_type_cost_per_byte: 10 +dynamic_field_remove_child_object_cost_base: 100 +dynamic_field_remove_child_object_child_cost_per_byte: 2 +dynamic_field_remove_child_object_type_cost_per_byte: 2 +dynamic_field_has_child_object_cost_base: 100 +dynamic_field_has_child_object_with_ty_cost_base: 100 +dynamic_field_has_child_object_with_ty_type_cost_per_byte: 2 +dynamic_field_has_child_object_with_ty_type_tag_cost_per_byte: 2 +event_emit_cost_base: 52 +event_emit_value_size_derivation_cost_per_byte: 2 +event_emit_tag_size_derivation_cost_per_byte: 5 +event_emit_output_cost_per_byte: 10 +object_borrow_uid_cost_base: 52 +object_delete_impl_cost_base: 52 +object_record_new_uid_cost_base: 52 +transfer_transfer_internal_cost_base: 52 +transfer_freeze_object_cost_base: 52 +transfer_share_object_cost_base: 52 +transfer_receive_object_cost_base: 52 +tx_context_derive_id_cost_base: 52 +types_is_one_time_witness_cost_base: 52 +types_is_one_time_witness_type_tag_cost_per_byte: 2 +types_is_one_time_witness_type_cost_per_byte: 2 +validator_validate_metadata_cost_base: 52 +validator_validate_metadata_data_cost_per_byte: 2 +crypto_invalid_arguments_cost: 100 +bls12381_bls12381_min_sig_verify_cost_base: 52 +bls12381_bls12381_min_sig_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_sig_verify_msg_cost_per_block: 2 +bls12381_bls12381_min_pk_verify_cost_base: 52 +bls12381_bls12381_min_pk_verify_msg_cost_per_byte: 2 +bls12381_bls12381_min_pk_verify_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_keccak256_cost_base: 52 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_k1_ecrecover_sha256_cost_base: 52 +ecdsa_k1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_k1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_k1_decompress_pubkey_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_cost_base: 52 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_k1_secp256k1_verify_sha256_cost_base: 52 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_k1_secp256k1_verify_sha256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_keccak256_cost_base: 52 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_keccak256_msg_cost_per_block: 2 +ecdsa_r1_ecrecover_sha256_cost_base: 52 +ecdsa_r1_ecrecover_sha256_msg_cost_per_byte: 2 +ecdsa_r1_ecrecover_sha256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_keccak256_cost_base: 52 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_keccak256_msg_cost_per_block: 2 +ecdsa_r1_secp256r1_verify_sha256_cost_base: 52 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_byte: 2 +ecdsa_r1_secp256r1_verify_sha256_msg_cost_per_block: 2 +ecvrf_ecvrf_verify_cost_base: 52 +ecvrf_ecvrf_verify_alpha_string_cost_per_byte: 2 +ecvrf_ecvrf_verify_alpha_string_cost_per_block: 2 +ed25519_ed25519_verify_cost_base: 52 +ed25519_ed25519_verify_msg_cost_per_byte: 2 +ed25519_ed25519_verify_msg_cost_per_block: 2 +groth16_prepare_verifying_key_bls12381_cost_base: 52 +groth16_prepare_verifying_key_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_base: 52 +groth16_verify_groth16_proof_internal_bls12381_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_bn254_cost_base: 52 +groth16_verify_groth16_proof_internal_bn254_cost_per_public_input: 2 +groth16_verify_groth16_proof_internal_public_input_cost_per_byte: 2 +hash_blake2b256_cost_base: 52 +hash_blake2b256_data_cost_per_byte: 2 +hash_blake2b256_data_cost_per_block: 2 +hash_keccak256_cost_base: 52 +hash_keccak256_data_cost_per_byte: 2 +hash_keccak256_data_cost_per_block: 2 +poseidon_bn254_cost_base: 260 +poseidon_bn254_cost_per_block: 10 +group_ops_bls12381_decode_scalar_cost: 52 +group_ops_bls12381_decode_g1_cost: 52 +group_ops_bls12381_decode_g2_cost: 52 +group_ops_bls12381_decode_gt_cost: 52 +group_ops_bls12381_scalar_add_cost: 52 +group_ops_bls12381_g1_add_cost: 52 +group_ops_bls12381_g2_add_cost: 52 +group_ops_bls12381_gt_add_cost: 52 +group_ops_bls12381_scalar_sub_cost: 52 +group_ops_bls12381_g1_sub_cost: 52 +group_ops_bls12381_g2_sub_cost: 52 +group_ops_bls12381_gt_sub_cost: 52 +group_ops_bls12381_scalar_mul_cost: 52 +group_ops_bls12381_g1_mul_cost: 52 +group_ops_bls12381_g2_mul_cost: 52 +group_ops_bls12381_gt_mul_cost: 52 +group_ops_bls12381_scalar_div_cost: 52 +group_ops_bls12381_g1_div_cost: 52 +group_ops_bls12381_g2_div_cost: 52 +group_ops_bls12381_gt_div_cost: 52 +group_ops_bls12381_g1_hash_to_base_cost: 52 +group_ops_bls12381_g2_hash_to_base_cost: 52 +group_ops_bls12381_g1_hash_to_cost_per_byte: 2 +group_ops_bls12381_g2_hash_to_cost_per_byte: 2 +group_ops_bls12381_g1_msm_base_cost: 52 +group_ops_bls12381_g2_msm_base_cost: 52 +group_ops_bls12381_g1_msm_base_cost_per_input: 52 +group_ops_bls12381_g2_msm_base_cost_per_input: 52 +group_ops_bls12381_msm_max_len: 32 +group_ops_bls12381_pairing_cost: 52 +hmac_hmac_sha3_256_cost_base: 52 +hmac_hmac_sha3_256_input_cost_per_byte: 2 +hmac_hmac_sha3_256_input_cost_per_block: 2 +check_zklogin_id_cost_base: 200 +check_zklogin_issuer_cost_base: 200 +vdf_verify_vdf_cost: 1500 +vdf_hash_to_input_cost: 100 +bcs_per_byte_serialized_cost: 2 +bcs_legacy_min_output_size_cost: 1 +bcs_failure_cost: 52 +hash_sha2_256_base_cost: 52 +hash_sha2_256_per_byte_cost: 2 +hash_sha2_256_legacy_min_input_len_cost: 1 +hash_sha3_256_base_cost: 52 +hash_sha3_256_per_byte_cost: 2 +hash_sha3_256_legacy_min_input_len_cost: 1 +type_name_get_base_cost: 52 +type_name_get_per_byte_cost: 2 +string_check_utf8_base_cost: 52 +string_check_utf8_per_byte_cost: 2 +string_is_char_boundary_base_cost: 52 +string_sub_string_base_cost: 52 +string_sub_string_per_byte_cost: 2 +string_index_of_base_cost: 52 +string_index_of_per_byte_pattern_cost: 2 +string_index_of_per_byte_searched_cost: 2 +vector_empty_base_cost: 52 +vector_length_base_cost: 52 +vector_push_back_base_cost: 52 +vector_push_back_legacy_per_abstract_memory_unit_cost: 2 +vector_borrow_base_cost: 52 +vector_pop_back_base_cost: 52 +vector_destroy_empty_base_cost: 52 +vector_swap_base_cost: 52 +debug_print_base_cost: 52 +debug_print_stack_trace_base_cost: 52 +execution_version: 3 +consensus_bad_nodes_stake_threshold: 20 +max_jwk_votes_per_validator_per_epoch: 240 +max_age_of_jwk_in_epochs: 1 +random_beacon_reduction_allowed_delta: 800 +random_beacon_reduction_lower_bound: 700 +random_beacon_dkg_timeout_round: 3000 +random_beacon_min_round_interval_ms: 500 +random_beacon_dkg_version: 1 +consensus_max_transaction_size_bytes: 262144 +consensus_max_transactions_in_block_bytes: 524288 +consensus_max_num_transactions_in_block: 512 +max_accumulated_txn_cost_per_object_in_narwhal_commit: 40 +max_deferral_rounds_for_congestion_control: 10 +min_checkpoint_interval_ms: 200 +checkpoint_summary_version_specific_data: 1 +max_soft_bundle_size: 5 +bridge_should_try_to_finalize_committee: true +max_accumulated_txn_cost_per_object_in_mysticeti_commit: 3 +gas_budget_based_txn_cost_cap_factor: 400000 + diff --git a/crates/sui-proxy/src/admin.rs b/crates/sui-proxy/src/admin.rs index d9e12f1409ee3..6436726b42205 100644 --- a/crates/sui-proxy/src/admin.rs +++ b/crates/sui-proxy/src/admin.rs @@ -25,7 +25,8 @@ use sui_tls::{ use tokio::signal; use tower::ServiceBuilder; use tower_http::{ - trace::{DefaultOnResponse, TraceLayer}, + timeout::TimeoutLayer, + trace::{DefaultOnFailure, DefaultOnResponse, TraceLayer}, LatencyUnit, }; use tracing::{info, Level}; @@ -112,16 +113,29 @@ pub fn app( .layer(Extension(Arc::new(allower))); } router + // Enforce on all routes. + // If the request does not complete within the specified timeout it will be aborted + // and a 408 Request Timeout response will be sent. + .layer(TimeoutLayer::new(Duration::from_secs(var!( + "NODE_CLIENT_TIMEOUT", + 20 + )))) .layer(Extension(relay)) .layer(Extension(labels)) .layer(Extension(client)) .layer( ServiceBuilder::new().layer( - TraceLayer::new_for_http().on_response( - DefaultOnResponse::new() - .level(Level::INFO) - .latency_unit(LatencyUnit::Seconds), - ), + TraceLayer::new_for_http() + .on_response( + DefaultOnResponse::new() + .level(Level::INFO) + .latency_unit(LatencyUnit::Seconds), + ) + .on_failure( + DefaultOnFailure::new() + .level(Level::ERROR) + .latency_unit(LatencyUnit::Seconds), + ), ), ) } diff --git a/crates/sui-proxy/src/lib.rs b/crates/sui-proxy/src/lib.rs index 273c06c1b6b63..74f209410cfb7 100644 --- a/crates/sui-proxy/src/lib.rs +++ b/crates/sui-proxy/src/lib.rs @@ -64,12 +64,32 @@ mod tests { axum::serve(listener, app).await.unwrap(); } - /// axum_acceptor is a basic e2e test that creates a mock remote_write post endpoint and has a simple + async fn run_dummy_remote_write_very_slow(listener: TcpListener) { + /// i accept everything, send me the trash, but i will sleep and never return before a timeout + /// this is for testing slow clients and this is the easiest way to do so without adding a special + /// route in the server to do so + async fn handler() -> StatusCode { + // Simulate a route that hangs while waiting for a client to send data + // but the server itself doesn't delay its processing + tokio::time::sleep(Duration::from_secs(60)).await; // A very long sleep + StatusCode::OK + } + + // build our application with a route + let app = Router::new().route("/v1/push", post(handler)); + + // run it + listener.set_nonblocking(true).unwrap(); + let listener = tokio::net::TcpListener::from_std(listener).unwrap(); + axum::serve(listener, app).await.unwrap(); + } + + /// test_axum_acceptor is a basic e2e test that creates a mock remote_write post endpoint and has a simple /// sui-node client that posts data to the proxy using the protobuf format. The server processes this /// data and sends it to the mock remote_write which accepts everything. Future work is to make this more /// robust and expand the scope of coverage, probabaly moving this test elsewhere and renaming it. #[tokio::test] - async fn axum_acceptor() { + async fn test_axum_acceptor() { // generate self-signed certificates let CertKeyPair(client_priv_cert, client_pub_key) = admin::generate_self_cert("sui".into()); let CertKeyPair(server_priv_cert, _) = admin::generate_self_cert("localhost".into()); @@ -175,4 +195,119 @@ mod tests { assert_eq!("created", body); assert_eq!(status, reqwest::StatusCode::CREATED); } + + /// this is a long test to ensure we are timing out clients that are slow + #[tokio::test] + async fn test_client_timeout() { + // generate self-signed certificates + let CertKeyPair(client_priv_cert, client_pub_key) = admin::generate_self_cert("sui".into()); + let CertKeyPair(server_priv_cert, _) = admin::generate_self_cert("localhost".into()); + + // create a fake rpc server + let dummy_remote_write_listener = std::net::TcpListener::bind("localhost:0").unwrap(); + let dummy_remote_write_address = dummy_remote_write_listener.local_addr().unwrap(); + let dummy_remote_write_url = format!( + "http://localhost:{}/v1/push", + dummy_remote_write_address.port() + ); + + let _dummy_remote_write = tokio::spawn(async move { + run_dummy_remote_write_very_slow(dummy_remote_write_listener).await + }); + + // init the tls config and allower + let mut allower = SuiNodeProvider::new("".into(), Duration::from_secs(30), vec![]); + let tls_config = ClientCertVerifier::new( + allower.clone(), + sui_tls::SUI_VALIDATOR_SERVER_NAME.to_string(), + ) + .rustls_server_config( + vec![server_priv_cert.rustls_certificate()], + server_priv_cert.rustls_private_key(), + ) + .unwrap(); + + let client = admin::make_reqwest_client( + RemoteWriteConfig { + url: dummy_remote_write_url.to_owned(), + username: "bar".into(), + password: "foo".into(), + ..Default::default() + }, + "dummy user agent", + ); + + // this will affect other tests if they are run in parallel, but we only have two tests, so it shouldn't be an issue (yet) + // even still, the other tests complete very fast so those tests would also need to slow down by orders and orders to be + // bothered by this env var + std::env::set_var("NODE_CLIENT_TIMEOUT", "5"); + + let app = admin::app( + Labels { + network: "unittest-network".into(), + inventory_hostname: "ansible_inventory_name".into(), + }, + client, + HistogramRelay::new(), + Some(allower.clone()), + ); + + let listener = std::net::TcpListener::bind("localhost:0").unwrap(); + let server_address = listener.local_addr().unwrap(); + let server_url = format!( + "https://localhost:{}/publish/metrics", + server_address.port() + ); + + let acceptor = TlsAcceptor::new(tls_config); + let _server = tokio::spawn(async move { + admin::server(listener, app, Some(acceptor)).await.unwrap(); + }); + + // build a client + let client = reqwest::Client::builder() + .add_root_certificate(server_priv_cert.reqwest_certificate()) + .identity(client_priv_cert.reqwest_identity()) + .https_only(true) + .build() + .unwrap(); + + // Client request is rejected because it isn't in the allowlist + client.get(&server_url).send().await.unwrap_err(); + + // Insert the client's public key into the allowlist and verify the request is successful + allower.get_sui_mut().write().unwrap().insert( + client_pub_key.to_owned(), + peers::AllowedPeer { + name: "some-node".into(), + public_key: client_pub_key.to_owned(), + }, + ); + + let mf = create_metric_family( + "foo_metric", + "some help this is", + None, + RepeatedField::from_vec(vec![create_metric_counter( + RepeatedField::from_vec(create_labels(vec![("some", "label")])), + create_counter(2046.0), + )]), + ); + + let mut buf = vec![]; + let encoder = prometheus::ProtobufEncoder::new(); + encoder.encode(&[mf], &mut buf).unwrap(); + + let res = client + .post(&server_url) + .header(reqwest::header::CONTENT_TYPE, PROTOBUF_FORMAT) + .body(buf) + .send() + .await + .expect("expected a successful post with a self-signed certificate"); + let status = res.status(); + assert_eq!(status, StatusCode::REQUEST_TIMEOUT); + // Clean up the environment variable + std::env::remove_var("NODE_CLIENT_TIMEOUT"); + } } diff --git a/crates/sui-proxy/src/peers.rs b/crates/sui-proxy/src/peers.rs index ca44e7f1e26a3..b139865f3a4fa 100644 --- a/crates/sui-proxy/src/peers.rs +++ b/crates/sui-proxy/src/peers.rs @@ -10,12 +10,14 @@ use once_cell::sync::Lazy; use prometheus::{register_counter_vec, register_histogram_vec}; use prometheus::{CounterVec, HistogramVec}; use serde::Deserialize; +use std::collections::BTreeMap; use std::{ collections::HashMap, sync::{Arc, RwLock}, time::Duration, }; use sui_tls::Allower; +use sui_types::base_types::SuiAddress; use sui_types::bridge::BridgeSummary; use sui_types::sui_system_state::sui_system_state_summary::SuiSystemStateSummary; use tracing::{debug, error, info, warn}; @@ -269,9 +271,24 @@ impl SuiNodeProvider { } async fn update_bridge_validator_set(&self, metrics_keys: MetricsPubKeys) { + let sui_system = match Self::get_validators(self.rpc_url.to_owned()).await { + Ok(summary) => summary, + Err(error) => { + JSON_RPC_STATE + .with_label_values(&["update_bridge_peer_count", "failed"]) + .inc(); + error!("unable to get sui system state: {error}"); + return; + } + }; match Self::get_bridge_validators(self.rpc_url.to_owned()).await { Ok(summary) => { - let validators = extract_bridge(summary, metrics_keys).await; + let names = sui_system + .active_validators + .into_iter() + .map(|v| (v.sui_address, v.name)) + .collect(); + let validators = extract_bridge(summary, Arc::new(names), metrics_keys).await; let mut allow = self.bridge_nodes.write().unwrap(); allow.clear(); allow.extend(validators); @@ -288,6 +305,7 @@ impl SuiNodeProvider { } }; } + /// poll_peer_list will act as a refresh interval for our cache pub fn poll_peer_list(&self) { info!("Started polling for peers using rpc: {}", self.rpc_url); @@ -342,8 +360,10 @@ fn extract( } }) } + async fn extract_bridge( summary: BridgeSummary, + names: Arc>, metrics_keys: MetricsPubKeys, ) -> Vec<(Ed25519PublicKey, AllowedPeer)> { { @@ -356,12 +376,16 @@ async fn extract_bridge( }); } - let client = reqwest::Client::builder().build().unwrap(); + let client = reqwest::Client::builder() + .timeout(Duration::from_secs(10)) + .build() + .unwrap(); let committee_members = summary.committee.members.clone(); let results: Vec<_> = stream::iter(committee_members) .filter_map(|(_, cm)| { let client = client.clone(); let metrics_keys = metrics_keys.clone(); + let names = names.clone(); async move { debug!( address =% cm.sui_address, @@ -397,7 +421,15 @@ async fn extract_bridge( return None; } }; - let bridge_name = String::from(bridge_host); + let bridge_name = names.get(&cm.sui_address).cloned().unwrap_or_else(|| { + warn!( + address =% cm.sui_address, + "Bridge node not found in sui committee, using base URL as the name", + ); + String::from(bridge_host) + }); + let bridge_name = format!("bridge-{}", bridge_name); + let bridge_request_url = bridge_url.as_str(); let metrics_pub_key = match client.get(bridge_request_url).send().await { @@ -429,6 +461,11 @@ async fn extract_bridge( // Successfully fetched the key, update the cache let mut metrics_keys_write = metrics_keys.write().unwrap(); metrics_keys_write.insert(url_str.clone(), pubkey.clone()); + debug!( + url_str, + public_key = ?pubkey, + "Successfully added bridge peer to metrics_keys" + ); pubkey } Err(error) => { @@ -479,7 +516,7 @@ fn fallback_to_cached_key( }, )) } else { - error!( + warn!( url_str, "Failed to fetch public key and no cached key available" ); @@ -556,7 +593,7 @@ mod tests { Ed25519PublicKey::from_bytes(&[1u8; 32]).unwrap(), ); } - let result = extract_bridge(summary, metrics_keys.clone()).await; + let result = extract_bridge(summary, Arc::new(BTreeMap::new()), metrics_keys.clone()).await; assert_eq!( result.len(), @@ -590,7 +627,7 @@ mod tests { Ed25519PublicKey::from_bytes(&[1u8; 32]).unwrap(), ); } - let result = extract_bridge(summary, metrics_keys.clone()).await; + let result = extract_bridge(summary, Arc::new(BTreeMap::new()), metrics_keys.clone()).await; assert_eq!( result.len(), diff --git a/crates/sui-rest-api/Cargo.toml b/crates/sui-rest-api/Cargo.toml index aa046b7c70b2a..3f6412fa11af6 100644 --- a/crates/sui-rest-api/Cargo.toml +++ b/crates/sui-rest-api/Cargo.toml @@ -22,15 +22,18 @@ thiserror.workspace = true tokio.workspace = true async-trait.workspace = true itertools.workspace = true -sui-sdk2.workspace = true +sui-sdk-types.workspace = true prometheus.workspace = true openapiv3 = { git = "https://github.com/bmwill/openapiv3.git", rev = "ca4b4845b7c159a39f5c68ad8f7f76cb6f4d6963" } schemars.workspace = true +documented = "0.6.0" fastcrypto.workspace = true sui-types.workspace = true mysten-network.workspace = true sui-protocol-config.workspace = true +move-binary-format.workspace = true +move-core-types.workspace = true [dev-dependencies] diffy = "0.3" diff --git a/crates/sui-rest-api/openapi/openapi.json b/crates/sui-rest-api/openapi/openapi.json index ced704ee4d2c5..494555500fef9 100644 --- a/crates/sui-rest-api/openapi/openapi.json +++ b/crates/sui-rest-api/openapi/openapi.json @@ -11,7 +11,7 @@ "name": "Apache 2.0", "url": "https://www.apache.org/licenses/LICENSE-2.0.html" }, - "version": "0.0.0" + "version": "unknown" }, "servers": [ { @@ -24,7 +24,8 @@ "tags": [ "General" ], - "operationId": "GetNodeInfo", + "description": "[![stable](https://img.shields.io/badge/api-stable-53b576?style=for-the-badge)](#)\n\nGet basic information about the state of a Node", + "operationId": "Get NodeInfo", "responses": { "200": { "description": "", @@ -35,21 +36,27 @@ } } } + }, + "500": { + "description": "" } } } }, - "/health": { + "/-/health": { "get": { "tags": [ "General" ], - "operationId": "HealthCheck", + "description": "[![stable](https://img.shields.io/badge/api-stable-53b576?style=for-the-badge)](#)\n\nPerform a service health check\n\nBy default the health check only verifies that the latest checkpoint can be fetched from the\nnode's store before returning a 200. Optionally the `threshold_seconds` parameter can be\nprovided to test for how up to date the node needs to be to be considered healthy.", + "operationId": "Health Check", "parameters": [ { "in": "query", "name": "threshold_seconds", + "description": "The threshold, or delta, between the server's system time and the timestamp in the most recently executed checkpoint for which the server is considered to be healthy.\n\nIf not provided, the server will be considered healthy if it can simply fetch the latest checkpoint from its store.", "schema": { + "description": "The threshold, or delta, between the server's system time and the timestamp in the most recently executed checkpoint for which the server is considered to be healthy.\n\nIf not provided, the server will be considered healthy if it can simply fetch the latest checkpoint from its store.", "type": "integer", "format": "uint32", "minimum": 0.0 @@ -63,6 +70,9 @@ "content": { "text/plain; charset=utf-8": {} } + }, + "500": { + "description": "" } } } @@ -72,6 +82,7 @@ "tags": [ "Account" ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", "operationId": "ListAccountObjects", "parameters": [ { @@ -132,6 +143,7 @@ "tags": [ "Objects" ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", "operationId": "GetObject", "parameters": [ { @@ -167,6 +179,7 @@ "tags": [ "Objects" ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", "operationId": "GetObjectWithVersion", "parameters": [ { @@ -213,6 +226,7 @@ "tags": [ "Objects" ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", "operationId": "ListDynamicFields", "parameters": [ { @@ -273,6 +287,7 @@ "tags": [ "Checkpoint" ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", "operationId": "ListCheckpoints", "parameters": [ { @@ -340,6 +355,7 @@ "tags": [ "Checkpoint" ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", "operationId": "GetCheckpoint", "parameters": [ { @@ -380,6 +396,7 @@ "tags": [ "Transactions" ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", "operationId": "GetTransaction", "parameters": [ { @@ -415,6 +432,7 @@ "tags": [ "Transactions" ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", "operationId": "ListTransactions", "parameters": [ { @@ -476,6 +494,7 @@ "tags": [ "Transactions" ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", "operationId": "ExecuteTransaction", "parameters": [ { @@ -548,6 +567,7 @@ "tags": [ "System" ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", "operationId": "GetCommittee", "parameters": [ { @@ -585,6 +605,7 @@ "tags": [ "System" ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", "operationId": "GetLatestCommittee", "responses": { "200": { @@ -606,6 +627,7 @@ "tags": [ "System" ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", "operationId": "GetSystemStateSummary", "responses": { "200": { @@ -626,6 +648,7 @@ "tags": [ "System" ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", "operationId": "GetCurrentProtocolConfig", "responses": { "200": { @@ -660,6 +683,7 @@ "tags": [ "System" ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", "operationId": "GetProtocolConfig", "parameters": [ { @@ -710,6 +734,7 @@ "tags": [ "System" ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", "operationId": "GetGasInfo", "responses": { "200": { @@ -725,11 +750,151 @@ } } }, + "/transactions/simulate": { + "post": { + "tags": [ + "Transactions" + ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", + "operationId": "SimulateTransaction", + "parameters": [ + { + "in": "query", + "name": "balance_changes", + "description": "Request `BalanceChanges` be included in the Response.", + "schema": { + "description": "Request `BalanceChanges` be included in the Response.", + "default": "false", + "type": "boolean" + }, + "style": "form" + }, + { + "in": "query", + "name": "input_objects", + "description": "Request input `Object`s be included in the Response.", + "schema": { + "description": "Request input `Object`s be included in the Response.", + "default": "false", + "type": "boolean" + }, + "style": "form" + }, + { + "in": "query", + "name": "output_objects", + "description": "Request output `Object`s be included in the Response.", + "schema": { + "description": "Request output `Object`s be included in the Response.", + "default": "false", + "type": "boolean" + }, + "style": "form" + } + ], + "requestBody": { + "content": { + "application/bcs": {} + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TransactionSimulationResponse" + } + }, + "application/bcs": {} + } + } + } + } + }, + "/transactions/resolve": { + "post": { + "tags": [ + "Transactions" + ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", + "operationId": "ResolveTransaction", + "parameters": [ + { + "in": "query", + "name": "balance_changes", + "description": "Request `BalanceChanges` be included in the Response.", + "schema": { + "description": "Request `BalanceChanges` be included in the Response.", + "default": "false", + "type": "boolean" + }, + "style": "form" + }, + { + "in": "query", + "name": "input_objects", + "description": "Request input `Object`s be included in the Response.", + "schema": { + "description": "Request input `Object`s be included in the Response.", + "default": "false", + "type": "boolean" + }, + "style": "form" + }, + { + "in": "query", + "name": "output_objects", + "description": "Request output `Object`s be included in the Response.", + "schema": { + "description": "Request output `Object`s be included in the Response.", + "default": "false", + "type": "boolean" + }, + "style": "form" + }, + { + "in": "query", + "name": "simulate", + "description": "Request that the fully resolved transaction be simulated and have its results sent back in the response.", + "schema": { + "description": "Request that the fully resolved transaction be simulated and have its results sent back in the response.", + "default": false, + "type": "boolean" + }, + "style": "form" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnresolvedTransaction" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ResolveTransactionResponse" + } + }, + "application/bcs": {} + } + } + } + } + }, "/coins/{coin_type}": { "get": { "tags": [ "Coins" ], + "description": "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n", "operationId": "GetCoinInfo", "parameters": [ { @@ -762,9 +927,10 @@ "/openapi": { "get": { "tags": [ - "OpenApi" + "OpenAPI" ], - "operationId": "OpenApi Explorer", + "description": "[![stable](https://img.shields.io/badge/api-stable-53b576?style=for-the-badge)](#)\n\nProvides a web UI for exploring the OpenAPI v3.1.0 definition for this service", + "operationId": "OpenAPI Explorer", "responses": { "200": { "description": "", @@ -778,8 +944,9 @@ "/openapi.json": { "get": { "tags": [ - "OpenApi" + "OpenAPI" ], + "description": "[![stable](https://img.shields.io/badge/api-stable-53b576?style=for-the-badge)](#)\n\nReturn the OpenAPI v3.1.0 definition for this service as a JSON document", "operationId": "openapi.json", "responses": { "200": { @@ -794,8 +961,9 @@ "/openapi.yaml": { "get": { "tags": [ - "OpenApi" + "OpenAPI" ], + "description": "[![stable](https://img.shields.io/badge/api-stable-53b576?style=for-the-badge)](#)\n\nReturn the OpenAPI v3.1.0 definition for this service as a YAML document", "operationId": "openapi.yaml", "responses": { "200": { @@ -3307,41 +3475,46 @@ ] }, "NodeInfo": { + "description": "Basic information about the state of a Node", "type": "object", "required": [ "chain", "chain_id", "checkpoint_height", "epoch", - "lowest_available_checkpoint", - "lowest_available_checkpoint_objects", "software_version", "timestamp_ms" ], "properties": { "chain": { + "description": "Human readable name of the chain that this Node is on", "type": "string" }, "chain_id": { - "$ref": "#/components/schemas/CheckpointDigest" + "description": "The chain identifier of the chain that this Node is on", + "allOf": [ + { + "$ref": "#/components/schemas/CheckpointDigest" + } + ] }, "checkpoint_height": { - "description": "Radix-10 encoded 64-bit unsigned integer", + "description": "Checkpoint height of the most recently executed checkpoint", "type": "string", "format": "u64" }, "epoch": { - "description": "Radix-10 encoded 64-bit unsigned integer", + "description": "Current epoch of the Node based on its highest executed checkpoint", "type": "string", "format": "u64" }, "lowest_available_checkpoint": { - "description": "Radix-10 encoded 64-bit unsigned integer", + "description": "The lowest checkpoint for which checkpoints and transaction data is available", "type": "string", "format": "u64" }, "lowest_available_checkpoint_objects": { - "description": "Radix-10 encoded 64-bit unsigned integer", + "description": "The lowest checkpoint for which object data is available", "type": "string", "format": "u64" }, @@ -3349,7 +3522,7 @@ "type": "string" }, "timestamp_ms": { - "description": "Radix-10 encoded 64-bit unsigned integer", + "description": "Unix timestamp of the most recently executed checkpoint", "type": "string", "format": "u64" } @@ -3788,6 +3961,21 @@ } } }, + "ResolveTransactionResponse": { + "description": "Response type for the execute transaction endpoint", + "type": "object", + "required": [ + "transaction" + ], + "properties": { + "simulation": { + "$ref": "#/components/schemas/TransactionSimulationResponse" + }, + "transaction": { + "$ref": "#/components/schemas/Transaction" + } + } + }, "Secp256k1PublicKey": { "description": "Base64 encoded data", "type": "string", @@ -3823,24 +4011,6 @@ } } }, - "SignedTransaction": { - "type": "object", - "required": [ - "signatures", - "transaction" - ], - "properties": { - "signatures": { - "type": "array", - "items": { - "$ref": "#/components/schemas/UserSignature" - } - }, - "transaction": { - "$ref": "#/components/schemas/Transaction" - } - } - }, "SimpleSignature": { "oneOf": [ { @@ -4946,6 +5116,39 @@ } } }, + "TransactionSimulationResponse": { + "description": "Response type for the transaction simulation endpoint", + "type": "object", + "required": [ + "effects" + ], + "properties": { + "balance_changes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BalanceChange" + } + }, + "effects": { + "$ref": "#/components/schemas/TransactionEffects" + }, + "events": { + "$ref": "#/components/schemas/TransactionEvents" + }, + "input_objects": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Object" + } + }, + "output_objects": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Object" + } + } + } + }, "TypeArgumentError": { "oneOf": [ { @@ -5114,6 +5317,145 @@ } } }, + "UnresolvedGasPayment": { + "type": "object", + "required": [ + "owner" + ], + "properties": { + "budget": { + "description": "Radix-10 encoded 64-bit unsigned integer", + "type": "string", + "format": "u64" + }, + "objects": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UnresolvedObjectReference" + } + }, + "owner": { + "$ref": "#/components/schemas/Address" + }, + "price": { + "description": "Radix-10 encoded 64-bit unsigned integer", + "type": "string", + "format": "u64" + } + } + }, + "UnresolvedInputArgument": { + "type": "object", + "properties": { + "digest": { + "$ref": "#/components/schemas/ObjectDigest" + }, + "kind": { + "$ref": "#/components/schemas/UnresolvedInputArgumentKind" + }, + "mutable": { + "type": "boolean" + }, + "object_id": { + "$ref": "#/components/schemas/ObjectId" + }, + "value": { + "$ref": "#/components/schemas/UnresolvedValue" + }, + "version": { + "description": "Either the `initial_shared_version` if object is a shared object, or the `version` if this is an owned object", + "type": "string", + "format": "u64" + } + } + }, + "UnresolvedInputArgumentKind": { + "type": "string", + "enum": [ + "pure", + "shared", + "receiving", + "immutable_or_owned", + "immutable", + "owned", + "literal" + ] + }, + "UnresolvedObjectReference": { + "type": "object", + "required": [ + "object_id" + ], + "properties": { + "digest": { + "$ref": "#/components/schemas/ObjectDigest" + }, + "object_id": { + "$ref": "#/components/schemas/ObjectId" + }, + "version": { + "description": "Radix-10 encoded 64-bit unsigned integer", + "type": "string", + "format": "u64" + } + } + }, + "UnresolvedTransaction": { + "type": "object", + "required": [ + "commands", + "expiration", + "inputs", + "sender" + ], + "properties": { + "commands": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Command" + } + }, + "expiration": { + "$ref": "#/components/schemas/TransactionExpiration" + }, + "gas_payment": { + "$ref": "#/components/schemas/UnresolvedGasPayment" + }, + "inputs": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UnresolvedInputArgument" + } + }, + "sender": { + "$ref": "#/components/schemas/Address" + } + } + }, + "UnresolvedValue": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + { + "type": "string" + }, + { + "type": "array", + "items": { + "$ref": "#/components/schemas/UnresolvedValue" + } + } + ] + }, "UpgradeInfo": { "description": "Upgraded package info for the linkage table", "type": "object", @@ -5241,7 +5583,7 @@ ] }, "signatures": { - "description": "The plain signature encoded with signature scheme.", + "description": "The plain signature encoded with signature scheme.\n\nThe signatures must be in the same order as they are listed in the committee.", "type": "array", "items": { "$ref": "#/components/schemas/MultisigMemberSignature" @@ -5648,7 +5990,7 @@ "name": "Objects" }, { - "name": "OpenApi" + "name": "OpenAPI" }, { "name": "System" diff --git a/crates/sui-rest-api/src/accept.rs b/crates/sui-rest-api/src/accept.rs index b4a0d954660cd..193158f1520c0 100644 --- a/crates/sui-rest-api/src/accept.rs +++ b/crates/sui-rest-api/src/accept.rs @@ -126,5 +126,19 @@ mod tests { .unwrap(); let accept = AcceptFormat::from_request(req, &()).await.unwrap(); assert_eq!(accept, AcceptFormat::Json); + + let req = Request::builder() + .header(header::ACCEPT, "application/json, application/bcs") + .body(axum::body::Body::empty()) + .unwrap(); + let accept = AcceptFormat::from_request(req, &()).await.unwrap(); + assert_eq!(accept, AcceptFormat::Json); + + let req = Request::builder() + .header(header::ACCEPT, "application/bcs, application/json") + .body(axum::body::Body::empty()) + .unwrap(); + let accept = AcceptFormat::from_request(req, &()).await.unwrap(); + assert_eq!(accept, AcceptFormat::Bcs); } } diff --git a/crates/sui-rest-api/src/accounts.rs b/crates/sui-rest-api/src/accounts.rs index f174ddc7bca43..db06cc03bf940 100644 --- a/crates/sui-rest-api/src/accounts.rs +++ b/crates/sui-rest-api/src/accounts.rs @@ -8,8 +8,8 @@ use crate::{Page, RestService}; use axum::extract::Query; use axum::extract::{Path, State}; use openapiv3::v3_1::Operation; -use sui_sdk2::types::{Address, ObjectId, StructTag, Version}; -use sui_types::sui_sdk2_conversions::struct_tag_core_to_sdk; +use sui_sdk_types::types::{Address, ObjectId, StructTag, Version}; +use sui_types::sui_sdk_types_conversions::struct_tag_core_to_sdk; use tap::Pipe; pub struct ListAccountObjects; diff --git a/crates/sui-rest-api/src/checkpoints.rs b/crates/sui-rest-api/src/checkpoints.rs index 2c813af3f55f7..425828d398c6a 100644 --- a/crates/sui-rest-api/src/checkpoints.rs +++ b/crates/sui-rest-api/src/checkpoints.rs @@ -3,7 +3,7 @@ use axum::extract::Query; use axum::extract::{Path, State}; -use sui_sdk2::types::{ +use sui_sdk_types::types::{ CheckpointData, CheckpointDigest, CheckpointSequenceNumber, SignedCheckpointSummary, }; use sui_types::storage::ReadStore; diff --git a/crates/sui-rest-api/src/client/mod.rs b/crates/sui-rest-api/src/client/mod.rs index 9619747510f73..a5582a0928662 100644 --- a/crates/sui-rest-api/src/client/mod.rs +++ b/crates/sui-rest-api/src/client/mod.rs @@ -30,6 +30,10 @@ impl Client { } } + pub fn inner(&self) -> &sdk::Client { + &self.inner + } + pub async fn get_latest_checkpoint(&self) -> Result { self.inner .get_latest_checkpoint() diff --git a/crates/sui-rest-api/src/client/sdk.rs b/crates/sui-rest-api/src/client/sdk.rs index cbd83889ebefa..36702a316cc17 100644 --- a/crates/sui-rest-api/src/client/sdk.rs +++ b/crates/sui-rest-api/src/client/sdk.rs @@ -4,19 +4,21 @@ use reqwest::header::HeaderValue; use reqwest::StatusCode; use reqwest::Url; -use sui_sdk2::types::Address; -use sui_sdk2::types::CheckpointData; -use sui_sdk2::types::CheckpointDigest; -use sui_sdk2::types::CheckpointSequenceNumber; -use sui_sdk2::types::EpochId; -use sui_sdk2::types::Object; -use sui_sdk2::types::ObjectId; -use sui_sdk2::types::SignedCheckpointSummary; -use sui_sdk2::types::SignedTransaction; -use sui_sdk2::types::StructTag; -use sui_sdk2::types::TransactionDigest; -use sui_sdk2::types::ValidatorCommittee; -use sui_sdk2::types::Version; +use sui_sdk_types::types::Address; +use sui_sdk_types::types::CheckpointData; +use sui_sdk_types::types::CheckpointDigest; +use sui_sdk_types::types::CheckpointSequenceNumber; +use sui_sdk_types::types::EpochId; +use sui_sdk_types::types::Object; +use sui_sdk_types::types::ObjectId; +use sui_sdk_types::types::SignedCheckpointSummary; +use sui_sdk_types::types::SignedTransaction; +use sui_sdk_types::types::StructTag; +use sui_sdk_types::types::Transaction; +use sui_sdk_types::types::TransactionDigest; +use sui_sdk_types::types::UnresolvedTransaction; +use sui_sdk_types::types::ValidatorCommittee; +use sui_sdk_types::types::Version; use tap::Pipe; use crate::accounts::AccountOwnedObjectInfo; @@ -33,8 +35,11 @@ use crate::system::SystemStateSummary; use crate::system::X_SUI_MAX_SUPPORTED_PROTOCOL_VERSION; use crate::system::X_SUI_MIN_SUPPORTED_PROTOCOL_VERSION; use crate::transactions::ListTransactionsQueryParameters; +use crate::transactions::ResolveTransactionQueryParameters; +use crate::transactions::ResolveTransactionResponse; use crate::transactions::TransactionExecutionResponse; use crate::transactions::TransactionResponse; +use crate::transactions::TransactionSimulationResponse; use crate::types::X_SUI_CHAIN; use crate::types::X_SUI_CHAIN_ID; use crate::types::X_SUI_CHECKPOINT_HEIGHT; @@ -400,6 +405,62 @@ impl Client { self.bcs(response).await } + pub async fn simulate_transaction( + &self, + transaction: &Transaction, + ) -> Result> { + let url = self.url().join("transactions/simulate")?; + + let body = bcs::to_bytes(transaction)?; + + let response = self + .inner + .post(url) + .header(reqwest::header::ACCEPT, crate::APPLICATION_BCS) + .header(reqwest::header::CONTENT_TYPE, crate::APPLICATION_BCS) + .body(body) + .send() + .await?; + + self.bcs(response).await + } + + pub async fn resolve_transaction( + &self, + unresolved_transaction: &UnresolvedTransaction, + ) -> Result> { + let url = self.url.join("transactions/resolve")?; + + let response = self + .inner + .post(url) + .header(reqwest::header::ACCEPT, crate::APPLICATION_BCS) + .json(unresolved_transaction) + .send() + .await?; + + self.bcs(response).await + } + + pub async fn resolve_transaction_with_parameters( + &self, + unresolved_transaction: &UnresolvedTransaction, + parameters: &ResolveTransactionQueryParameters, + ) -> Result> { + let url = self.url.join("transactions/resolve")?; + + let response = self + .inner + .post(url) + .query(¶meters) + .header(reqwest::header::ACCEPT, crate::APPLICATION_BCS) + .json(unresolved_transaction) + .send() + .await?; + + self.bcs(response).await + } + async fn check_response( &self, response: reqwest::Response, @@ -667,8 +728,8 @@ impl From for Error { } } -impl From for Error { - fn from(value: sui_types::sui_sdk2_conversions::SdkTypeConversionError) -> Self { +impl From for Error { + fn from(value: sui_types::sui_sdk_types_conversions::SdkTypeConversionError) -> Self { Self::from_error(value) } } diff --git a/crates/sui-rest-api/src/coins.rs b/crates/sui-rest-api/src/coins.rs index b17069b2f3c6e..a0fc0df40d194 100644 --- a/crates/sui-rest-api/src/coins.rs +++ b/crates/sui-rest-api/src/coins.rs @@ -9,8 +9,8 @@ use axum::extract::{Path, State}; use axum::Json; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use sui_sdk2::types::{ObjectId, StructTag}; -use sui_types::sui_sdk2_conversions::struct_tag_sdk_to_core; +use sui_sdk_types::types::{ObjectId, StructTag}; +use sui_types::sui_sdk_types_conversions::struct_tag_sdk_to_core; pub struct GetCoinInfo; diff --git a/crates/sui-rest-api/src/committee.rs b/crates/sui-rest-api/src/committee.rs index 166405f6f2fa5..8ec03dbb81688 100644 --- a/crates/sui-rest-api/src/committee.rs +++ b/crates/sui-rest-api/src/committee.rs @@ -9,7 +9,7 @@ use crate::{ RestService, Result, }; use axum::extract::{Path, State}; -use sui_sdk2::types::{EpochId, ValidatorCommittee}; +use sui_sdk_types::types::{EpochId, ValidatorCommittee}; use sui_types::storage::ReadStore; use tap::Pipe; diff --git a/crates/sui-rest-api/src/error.rs b/crates/sui-rest-api/src/error.rs index 2d0042c27d0cb..f33cf4332d8af 100644 --- a/crates/sui-rest-api/src/error.rs +++ b/crates/sui-rest-api/src/error.rs @@ -5,6 +5,7 @@ use axum::http::StatusCode; pub type Result = std::result::Result; +#[derive(Debug)] pub struct RestError { status: StatusCode, message: Option, @@ -47,8 +48,8 @@ impl From for RestError { } } -impl From for RestError { - fn from(value: sui_types::sui_sdk2_conversions::SdkTypeConversionError) -> Self { +impl From for RestError { + fn from(value: sui_types::sui_sdk_types_conversions::SdkTypeConversionError) -> Self { Self { status: StatusCode::INTERNAL_SERVER_ERROR, message: Some(value.to_string()), diff --git a/crates/sui-rest-api/src/health.rs b/crates/sui-rest-api/src/health.rs index d76e622124647..cf45ae8a95382 100644 --- a/crates/sui-rest-api/src/health.rs +++ b/crates/sui-rest-api/src/health.rs @@ -11,10 +11,17 @@ use axum::{ http::StatusCode, response::IntoResponse, }; +use documented::Documented; use std::time::{Duration, SystemTime}; use sui_types::storage::ReadStore; use tap::Pipe; +/// Perform a service health check +/// +/// By default the health check only verifies that the latest checkpoint can be fetched from the +/// node's store before returning a 200. Optionally the `threshold_seconds` parameter can be +/// provided to test for how up to date the node needs to be to be considered healthy. +#[derive(Documented)] pub struct HealthCheck; impl ApiEndpoint for HealthCheck { @@ -23,7 +30,11 @@ impl ApiEndpoint for HealthCheck { } fn path(&self) -> &'static str { - "/health" + "/-/health" + } + + fn stable(&self) -> bool { + true } fn operation( @@ -32,9 +43,11 @@ impl ApiEndpoint for HealthCheck { ) -> openapiv3::v3_1::Operation { OperationBuilder::new() .tag("General") - .operation_id("HealthCheck") + .operation_id("Health Check") + .description(Self::DOCS) .query_parameters::(generator) .response(200, ResponseBuilder::new().text_content().build()) + .response(500, ResponseBuilder::new().build()) .build() } @@ -45,6 +58,11 @@ impl ApiEndpoint for HealthCheck { #[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)] pub struct Threshold { + /// The threshold, or delta, between the server's system time and the timestamp in the most + /// recently executed checkpoint for which the server is considered to be healthy. + /// + /// If not provided, the server will be considered healthy if it can simply fetch the latest + /// checkpoint from its store. pub threshold_seconds: Option, } diff --git a/crates/sui-rest-api/src/info.rs b/crates/sui-rest-api/src/info.rs index 868802194129d..29ba245ba5146 100644 --- a/crates/sui-rest-api/src/info.rs +++ b/crates/sui-rest-api/src/info.rs @@ -7,9 +7,12 @@ use crate::openapi::{ApiEndpoint, OperationBuilder, ResponseBuilder, RouteHandle use crate::{RestService, Result}; use axum::extract::State; use axum::Json; -use sui_sdk2::types::CheckpointDigest; +use documented::Documented; +use sui_sdk_types::types::CheckpointDigest; use tap::Pipe; +/// Get basic information about the state of a Node +#[derive(Documented)] pub struct GetNodeInfo; impl ApiEndpoint for GetNodeInfo { @@ -21,19 +24,25 @@ impl ApiEndpoint for GetNodeInfo { "/" } + fn stable(&self) -> bool { + true + } + fn operation( &self, generator: &mut schemars::gen::SchemaGenerator, ) -> openapiv3::v3_1::Operation { OperationBuilder::new() .tag("General") - .operation_id("GetNodeInfo") + .operation_id("Get NodeInfo") + .description(Self::DOCS) .response( 200, ResponseBuilder::new() .json_content::(generator) .build(), ) + .response(500, ResponseBuilder::new().build()) .build() } @@ -44,11 +53,16 @@ impl ApiEndpoint for GetNodeInfo { async fn get_node_info(State(state): State) -> Result> { let latest_checkpoint = state.reader.inner().get_latest_checkpoint()?; - let lowest_available_checkpoint = state.reader.inner().get_lowest_available_checkpoint()?; + let lowest_available_checkpoint = state + .reader + .inner() + .get_lowest_available_checkpoint()? + .pipe(Some); let lowest_available_checkpoint_objects = state .reader .inner() - .get_lowest_available_checkpoint_objects()?; + .get_lowest_available_checkpoint_objects()? + .pipe(Some); NodeInfo { checkpoint_height: latest_checkpoint.sequence_number, @@ -64,26 +78,42 @@ async fn get_node_info(State(state): State) -> Result, + + /// Current epoch of the Node based on its highest executed checkpoint #[serde_as(as = "sui_types::sui_serde::BigInt")] #[schemars(with = "crate::_schemars::U64")] pub epoch: u64, + + /// Checkpoint height of the most recently executed checkpoint #[serde_as(as = "sui_types::sui_serde::BigInt")] #[schemars(with = "crate::_schemars::U64")] pub checkpoint_height: u64, + + /// Unix timestamp of the most recently executed checkpoint #[serde_as(as = "sui_types::sui_serde::BigInt")] #[schemars(with = "crate::_schemars::U64")] pub timestamp_ms: u64, - #[serde_as(as = "sui_types::sui_serde::BigInt")] - #[schemars(with = "crate::_schemars::U64")] - pub lowest_available_checkpoint: u64, - #[serde_as(as = "sui_types::sui_serde::BigInt")] - #[schemars(with = "crate::_schemars::U64")] - pub lowest_available_checkpoint_objects: u64, + + /// The lowest checkpoint for which checkpoints and transaction data is available + #[serde_as(as = "Option>")] + #[schemars(with = "Option")] + #[serde(skip_serializing_if = "Option::is_none")] + pub lowest_available_checkpoint: Option, + + /// The lowest checkpoint for which object data is available + #[serde_as(as = "Option>")] + #[schemars(with = "Option")] + #[serde(skip_serializing_if = "Option::is_none")] + pub lowest_available_checkpoint_objects: Option, pub software_version: Cow<'static, str>, //TODO include current protocol version } diff --git a/crates/sui-rest-api/src/lib.rs b/crates/sui-rest-api/src/lib.rs index 18993abb1b23e..b0bd1696a4f3b 100644 --- a/crates/sui-rest-api/src/lib.rs +++ b/crates/sui-rest-api/src/lib.rs @@ -66,8 +66,10 @@ impl axum::response::IntoResponse for } const ENDPOINTS: &[&dyn ApiEndpoint] = &[ + // stable APIs &info::GetNodeInfo, &health::HealthCheck, + // unstable APIs &accounts::ListAccountObjects, &objects::GetObject, &objects::GetObjectWithVersion, @@ -84,6 +86,8 @@ const ENDPOINTS: &[&dyn ApiEndpoint] = &[ &system::GetProtocolConfig, &system::GetGasInfo, &transactions::ExecuteTransaction, + &transactions::SimulateTransaction, + &transactions::ResolveTransaction, &coins::GetCoinInfo, ]; @@ -94,6 +98,7 @@ pub struct RestService { chain_id: sui_types::digests::ChainIdentifier, software_version: &'static str, metrics: Option>, + config: Config, } impl axum::extract::FromRef for StateReader { @@ -117,6 +122,7 @@ impl RestService { chain_id, software_version, metrics: None, + config: Config::default(), } } @@ -124,6 +130,10 @@ impl RestService { Self::new(reader, "unknown") } + pub fn with_config(&mut self, config: Config) { + self.config = config; + } + pub fn with_executor(&mut self, executor: Arc) { self.executor = Some(executor); } @@ -143,9 +153,14 @@ impl RestService { pub fn into_router(self) -> Router { let metrics = self.metrics.clone(); - let mut api = openapi::Api::new(info()); + let mut api = openapi::Api::new(info(self.software_version())); - api.register_endpoints(ENDPOINTS.to_owned()); + api.register_endpoints( + ENDPOINTS + .iter() + .copied() + .filter(|endpoint| endpoint.stable() || self.config.enable_unstable_apis()), + ); Router::new() .nest("/v2/", api.to_router().with_state(self.clone())) @@ -176,7 +191,7 @@ impl RestService { } } -fn info() -> openapiv3::v3_1::Info { +fn info(version: &'static str) -> openapiv3::v3_1::Info { use openapiv3::v3_1::Contact; use openapiv3::v3_1::License; @@ -193,7 +208,7 @@ fn info() -> openapiv3::v3_1::Info { url: Some("https://www.apache.org/licenses/LICENSE-2.0.html".to_owned()), ..Default::default() }), - version: "0.0.0".to_owned(), + version: version.to_owned(), ..Default::default() } } @@ -202,6 +217,31 @@ async fn redirect(axum::extract::Path(path): axum::extract::Path) -> Red Redirect::permanent(&format!("/v2/{path}")) } +#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct Config { + /// Enable serving of unstable APIs + /// + /// Defaults to `false`, with unstable APIs being disabled + #[serde(skip_serializing_if = "Option::is_none")] + pub enable_unstable_apis: Option, + + // Only include this till we have another field that isn't set with a non-default value for + // testing + #[doc(hidden)] + #[serde(skip)] + pub _hidden: (), +} + +impl Config { + pub fn enable_unstable_apis(&self) -> bool { + // TODO + // Until the rest service as a whole is "stabalized" with a sane set of default stable + // apis, have the default be to enable all apis + self.enable_unstable_apis.unwrap_or(true) + } +} + mod _schemars { use schemars::schema::InstanceType; use schemars::schema::Metadata; @@ -244,9 +284,9 @@ mod test { concat!(env!("CARGO_MANIFEST_DIR"), "/openapi/openapi.json"); let openapi = { - let mut api = openapi::Api::new(info()); + let mut api = openapi::Api::new(info("unknown")); - api.register_endpoints(ENDPOINTS.to_owned()); + api.register_endpoints(ENDPOINTS.iter().copied()); api.openapi() }; @@ -282,7 +322,7 @@ mod test { } let openapi = { - let mut api = openapi::Api::new(info()); + let mut api = openapi::Api::new(info("unknown")); api.register_endpoints(ENDPOINTS.to_owned()); api.openapi() }; diff --git a/crates/sui-rest-api/src/objects.rs b/crates/sui-rest-api/src/objects.rs index 61b9034cb65e3..714f652f14a78 100644 --- a/crates/sui-rest-api/src/objects.rs +++ b/crates/sui-rest-api/src/objects.rs @@ -11,11 +11,11 @@ use crate::{ use axum::extract::Query; use axum::extract::{Path, State}; use serde::{Deserialize, Serialize}; -use sui_sdk2::types::{Object, ObjectId, TypeTag, Version}; -use sui_types::sui_sdk2_conversions::type_tag_core_to_sdk; +use sui_sdk_types::types::{Object, ObjectId, TypeTag, Version}; +use sui_types::sui_sdk_types_conversions::type_tag_core_to_sdk; use sui_types::{ storage::{DynamicFieldIndexInfo, DynamicFieldKey}, - sui_sdk2_conversions::SdkTypeConversionError, + sui_sdk_types_conversions::SdkTypeConversionError, }; use tap::Pipe; diff --git a/crates/sui-rest-api/src/openapi.rs b/crates/sui-rest-api/src/openapi.rs index 26f287dfc6a89..424be1d2f8048 100644 --- a/crates/sui-rest-api/src/openapi.rs +++ b/crates/sui-rest-api/src/openapi.rs @@ -15,6 +15,7 @@ use axum::{ routing::{get, MethodRouter}, Router, }; +use documented::Documented; use openapiv3::v3_1::{ Components, Header, Info, MediaType, OpenApi, Operation, Parameter, ParameterData, PathItem, Paths, ReferenceOr, RequestBody, Response, SchemaObject, Tag, @@ -22,6 +23,12 @@ use openapiv3::v3_1::{ use schemars::{gen::SchemaGenerator, JsonSchema}; use tap::Pipe; +const STABLE_BADGE_MARKDOWN: &str = + "[![stable](https://img.shields.io/badge/api-stable-53b576?style=for-the-badge)](#)\n\n"; + +const UNSTABLE_BADGE_MARKDOWN: &str = + "[![unstable](https://img.shields.io/badge/api-unstable-red?style=for-the-badge)](#) _Api subject to change; use at your own risk_\n\n"; + pub trait ApiEndpoint { fn method(&self) -> Method; fn path(&self) -> &'static str; @@ -29,6 +36,19 @@ pub trait ApiEndpoint { false } + /// Indicates the stability of the API + /// + /// Stable APIs are enabled in the REST service by default, unstable ones need to be explicitly + /// configured to be enabled via config. + /// + /// Both stable and unstable APIs have a badge, indicating the api's stability, added to the + /// top of the description field of their OpenAPI definition. + /// + /// By default all apis are unstable, individual apis need to explicitly opt-in to being stable + fn stable(&self) -> bool { + false + } + fn operation(&self, _generator: &mut SchemaGenerator) -> Operation { Operation::default() } @@ -200,7 +220,19 @@ impl<'a, S> Api<'a, S> { other => panic!("unexpected method `{}`", other), }; - let operation = endpoint.operation(generator); + let mut operation = endpoint.operation(generator); + + if endpoint.stable() { + operation + .description + .get_or_insert_with(String::new) + .insert_str(0, STABLE_BADGE_MARKDOWN); + } else { + operation + .description + .get_or_insert_with(String::new) + .insert_str(0, UNSTABLE_BADGE_MARKDOWN); + } // Collect tags defined by this operation tags.extend(operation.tags.clone()); @@ -268,6 +300,8 @@ impl OpenApiDocument { } } +/// Return the OpenAPI v3.1.0 definition for this service as a JSON document +#[derive(Documented)] pub struct OpenApiJson; impl ApiEndpoint> for OpenApiJson { @@ -279,13 +313,18 @@ impl ApiEndpoint> for OpenApiJson { "/openapi.json" } + fn stable(&self) -> bool { + true + } + fn operation( &self, _generator: &mut schemars::gen::SchemaGenerator, ) -> openapiv3::v3_1::Operation { OperationBuilder::new() - .tag("OpenApi") + .tag("OpenAPI") .operation_id("openapi.json") + .description(Self::DOCS) .response( 200, ResponseBuilder::new() @@ -300,6 +339,8 @@ impl ApiEndpoint> for OpenApiJson { } } +/// Return the OpenAPI v3.1.0 definition for this service as a YAML document +#[derive(Documented)] pub struct OpenApiYaml; impl ApiEndpoint> for OpenApiYaml { @@ -311,13 +352,18 @@ impl ApiEndpoint> for OpenApiYaml { "/openapi.yaml" } + fn stable(&self) -> bool { + true + } + fn operation( &self, _generator: &mut schemars::gen::SchemaGenerator, ) -> openapiv3::v3_1::Operation { OperationBuilder::new() - .tag("OpenApi") + .tag("OpenAPI") .operation_id("openapi.yaml") + .description(Self::DOCS) .response( 200, ResponseBuilder::new() @@ -332,6 +378,8 @@ impl ApiEndpoint> for OpenApiYaml { } } +/// Provides a web UI for exploring the OpenAPI v3.1.0 definition for this service +#[derive(Documented)] pub struct OpenApiExplorer; impl ApiEndpoint> for OpenApiExplorer { @@ -343,13 +391,18 @@ impl ApiEndpoint> for OpenApiExplorer { "/openapi" } + fn stable(&self) -> bool { + true + } + fn operation( &self, _generator: &mut schemars::gen::SchemaGenerator, ) -> openapiv3::v3_1::Operation { OperationBuilder::new() - .tag("OpenApi") - .operation_id("OpenApi Explorer") + .tag("OpenAPI") + .operation_id("OpenAPI Explorer") + .description(Self::DOCS) .response( 200, ResponseBuilder::new() diff --git a/crates/sui-rest-api/src/reader.rs b/crates/sui-rest-api/src/reader.rs index 810b2c8aa8827..a3beeda2d2595 100644 --- a/crates/sui-rest-api/src/reader.rs +++ b/crates/sui-rest-api/src/reader.rs @@ -3,8 +3,10 @@ use std::sync::Arc; -use sui_sdk2::types::{CheckpointSequenceNumber, EpochId, SignedTransaction, ValidatorCommittee}; -use sui_sdk2::types::{Object, ObjectId, Version}; +use sui_sdk_types::types::{ + CheckpointSequenceNumber, EpochId, SignedTransaction, ValidatorCommittee, +}; +use sui_sdk_types::types::{Object, ObjectId, Version}; use sui_types::storage::error::{Error as StorageError, Result}; use sui_types::storage::ObjectStore; use sui_types::storage::RestStateReader; @@ -62,11 +64,11 @@ impl StateReader { pub fn get_transaction( &self, - digest: sui_sdk2::types::TransactionDigest, + digest: sui_sdk_types::types::TransactionDigest, ) -> crate::Result<( - sui_sdk2::types::SignedTransaction, - sui_sdk2::types::TransactionEffects, - Option, + sui_sdk_types::types::SignedTransaction, + sui_sdk_types::types::TransactionEffects, + Option, )> { use super::transactions::TransactionNotFoundError; use sui_types::effects::TransactionEffectsAPI; @@ -101,7 +103,7 @@ impl StateReader { pub fn get_transaction_response( &self, - digest: sui_sdk2::types::TransactionDigest, + digest: sui_sdk_types::types::TransactionDigest, ) -> crate::Result { let ( SignedTransaction { diff --git a/crates/sui-rest-api/src/system.rs b/crates/sui-rest-api/src/system.rs index 2a4fd9994b2bc..fc1d386dbce8c 100644 --- a/crates/sui-rest-api/src/system.rs +++ b/crates/sui-rest-api/src/system.rs @@ -15,7 +15,7 @@ use schemars::JsonSchema; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; use sui_protocol_config::{ProtocolConfig, ProtocolConfigValue, ProtocolVersion}; -use sui_sdk2::types::{Address, ObjectId}; +use sui_sdk_types::types::{Address, ObjectId}; pub struct GetSystemStateSummary; @@ -236,9 +236,9 @@ pub struct SystemStateSummary { pub struct ValidatorSummary { // Metadata pub address: Address, - pub protocol_public_key: sui_sdk2::types::Bls12381PublicKey, - pub network_public_key: sui_sdk2::types::Ed25519PublicKey, - pub worker_public_key: sui_sdk2::types::Ed25519PublicKey, + pub protocol_public_key: sui_sdk_types::types::Bls12381PublicKey, + pub network_public_key: sui_sdk_types::types::Ed25519PublicKey, + pub worker_public_key: sui_sdk_types::types::Ed25519PublicKey, #[serde_as(as = "fastcrypto::encoding::Base64")] #[schemars(with = "String")] pub proof_of_possession_bytes: Vec, @@ -250,9 +250,9 @@ pub struct ValidatorSummary { pub p2p_address: String, pub primary_address: String, pub worker_address: String, - pub next_epoch_protocol_public_key: Option, - pub next_epoch_network_public_key: Option, - pub next_epoch_worker_public_key: Option, + pub next_epoch_protocol_public_key: Option, + pub next_epoch_network_public_key: Option, + pub next_epoch_worker_public_key: Option, #[serde_as(as = "Option")] #[schemars(with = "Option")] pub next_epoch_proof_of_possession: Option>, @@ -374,14 +374,18 @@ impl From for ExecuteTransaction { &self, generator: &mut schemars::gen::SchemaGenerator, ) -> openapiv3::v3_1::Operation { - generator.subschema_for::(); - OperationBuilder::new() .tag("Transactions") .operation_id("ExecuteTransaction") @@ -172,7 +168,7 @@ async fn execute_transaction( } /// Query parameters for the execute transaction endpoint -#[derive(Debug, serde::Serialize, serde::Deserialize, JsonSchema)] +#[derive(Debug, Default, serde::Serialize, serde::Deserialize, JsonSchema)] pub struct ExecuteTransactionQueryParameters { // TODO once transaction finality support is more fully implemented up and down the stack, add // back in this parameter, which will be mutally-exclusive with the other parameters. When @@ -328,14 +324,16 @@ fn derive_balance_changes( let balances = coins(input_objects).fold( std::collections::BTreeMap::<_, i128>::new(), |mut acc, (address, coin)| { - *acc.entry((address, coin.coin_type())).or_default() -= coin.balance() as i128; + *acc.entry((address, coin.coin_type().to_owned())) + .or_default() -= coin.balance() as i128; acc }, ); // 2. add all mutated coins let balances = coins(output_objects).fold(balances, |mut acc, (address, coin)| { - *acc.entry((address, coin.coin_type())).or_default() += coin.balance() as i128; + *acc.entry((address, coin.coin_type().to_owned())) + .or_default() += coin.balance() as i128; acc }); @@ -348,9 +346,141 @@ fn derive_balance_changes( Some(BalanceChange { address: *address, - coin_type: coin_type.to_owned(), + coin_type, amount, }) }) .collect() } + +pub struct SimulateTransaction; + +impl ApiEndpoint for SimulateTransaction { + fn method(&self) -> axum::http::Method { + axum::http::Method::POST + } + + fn path(&self) -> &'static str { + "/transactions/simulate" + } + + fn operation( + &self, + generator: &mut schemars::gen::SchemaGenerator, + ) -> openapiv3::v3_1::Operation { + OperationBuilder::new() + .tag("Transactions") + .operation_id("SimulateTransaction") + .query_parameters::(generator) + .request_body(RequestBodyBuilder::new().bcs_content().build()) + .response( + 200, + ResponseBuilder::new() + .json_content::(generator) + .bcs_content() + .build(), + ) + .build() + } + + fn handler(&self) -> RouteHandler { + RouteHandler::new(self.method(), simulate_transaction) + } +} + +async fn simulate_transaction( + State(state): State>>, + Query(parameters): Query, + accept: AcceptFormat, + //TODO allow accepting JSON as well as BCS + Bcs(transaction): Bcs, +) -> Result> { + let executor = state.ok_or_else(|| anyhow::anyhow!("No Transaction Executor"))?; + + simulate_transaction_impl(&executor, ¶meters, transaction).map(|response| match accept { + AcceptFormat::Json => ResponseContent::Json(response), + AcceptFormat::Bcs => ResponseContent::Bcs(response), + }) +} + +pub(super) fn simulate_transaction_impl( + executor: &Arc, + parameters: &SimulateTransactionQueryParameters, + transaction: Transaction, +) -> Result { + if transaction.gas_payment.objects.is_empty() { + return Err(RestError::new( + axum::http::StatusCode::BAD_REQUEST, + "no gas payment provided", + )); + } + + let SimulateTransactionResult { + input_objects, + output_objects, + events, + effects, + mock_gas_id, + } = executor + .simulate_transaction(transaction.try_into()?) + .map_err(anyhow::Error::from)?; + + if mock_gas_id.is_some() { + return Err(RestError::new( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + "simulate unexpectedly used a mock gas payment", + )); + } + + let events = events.map(TryInto::try_into).transpose()?; + let effects = effects.try_into()?; + + let input_objects = input_objects + .into_values() + .map(TryInto::try_into) + .collect::, _>>()?; + let output_objects = output_objects + .into_values() + .map(TryInto::try_into) + .collect::, _>>()?; + let balance_changes = derive_balance_changes(&effects, &input_objects, &output_objects); + + TransactionSimulationResponse { + events, + effects, + balance_changes: parameters.balance_changes.then_some(balance_changes), + input_objects: parameters.input_objects.then_some(input_objects), + output_objects: parameters.output_objects.then_some(output_objects), + } + .pipe(Ok) +} + +/// Response type for the transaction simulation endpoint +#[derive(Debug, serde::Serialize, serde::Deserialize, JsonSchema)] +pub struct TransactionSimulationResponse { + pub effects: TransactionEffects, + pub events: Option, + pub balance_changes: Option>, + pub input_objects: Option>, + pub output_objects: Option>, +} + +/// Query parameters for the simulate transaction endpoint +#[derive(Debug, Default, serde::Serialize, serde::Deserialize, JsonSchema)] +pub struct SimulateTransactionQueryParameters { + /// Request `BalanceChanges` be included in the Response. + #[serde(default)] + #[serde(with = "serde_with::As::")] + #[schemars(with = "bool")] + pub balance_changes: bool, + /// Request input `Object`s be included in the Response. + #[serde(default)] + #[serde(with = "serde_with::As::")] + #[schemars(with = "bool")] + pub input_objects: bool, + /// Request output `Object`s be included in the Response. + #[serde(default)] + #[serde(with = "serde_with::As::")] + #[schemars(with = "bool")] + pub output_objects: bool, +} diff --git a/crates/sui-rest-api/src/transactions/mod.rs b/crates/sui-rest-api/src/transactions/mod.rs index 6e9ffed21492d..362aa4c40ed2c 100644 --- a/crates/sui-rest-api/src/transactions/mod.rs +++ b/crates/sui-rest-api/src/transactions/mod.rs @@ -5,13 +5,23 @@ mod execution; pub use execution::EffectsFinality; pub use execution::ExecuteTransaction; pub use execution::ExecuteTransactionQueryParameters; +pub use execution::SimulateTransaction; +pub use execution::SimulateTransactionQueryParameters; pub use execution::TransactionExecutionResponse; +pub use execution::TransactionSimulationResponse; + +mod resolve; +pub use resolve::ResolveTransaction; +pub use resolve::ResolveTransactionQueryParameters; +pub use resolve::ResolveTransactionResponse; use axum::extract::{Path, Query, State}; use axum::http::StatusCode; -use sui_sdk2::types::CheckpointSequenceNumber; -use sui_sdk2::types::Transaction; -use sui_sdk2::types::{TransactionDigest, TransactionEffects, TransactionEvents, UserSignature}; +use sui_sdk_types::types::CheckpointSequenceNumber; +use sui_sdk_types::types::Transaction; +use sui_sdk_types::types::{ + TransactionDigest, TransactionEffects, TransactionEvents, UserSignature, +}; use tap::Pipe; use crate::openapi::ApiEndpoint; diff --git a/crates/sui-rest-api/src/transactions/resolve/literal.rs b/crates/sui-rest-api/src/transactions/resolve/literal.rs new file mode 100644 index 0000000000000..6b25013faae3a --- /dev/null +++ b/crates/sui-rest-api/src/transactions/resolve/literal.rs @@ -0,0 +1,787 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::HashMap; + +use super::NormalizedPackage; +use crate::RestError; +use crate::Result; +use move_binary_format::normalized::Type; +use sui_sdk_types::types::Command; +use sui_sdk_types::types::ObjectId; +use sui_sdk_types::types::UnresolvedValue; +use sui_types::base_types::ObjectID; +use sui_types::base_types::STD_ASCII_MODULE_NAME; +use sui_types::base_types::STD_ASCII_STRUCT_NAME; +use sui_types::base_types::STD_OPTION_MODULE_NAME; +use sui_types::base_types::STD_OPTION_STRUCT_NAME; +use sui_types::base_types::STD_UTF8_MODULE_NAME; +use sui_types::base_types::STD_UTF8_STRUCT_NAME; +use sui_types::MOVE_STDLIB_ADDRESS; + +pub(super) fn resolve_literal( + called_packages: &HashMap, + commands: &[Command], + arg_idx: usize, + value: UnresolvedValue, +) -> Result> { + let literal_type = determine_literal_type(called_packages, commands, arg_idx)?; + + let mut buf = Vec::new(); + + resolve_literal_to_type(&mut buf, &literal_type, &value)?; + + Ok(buf) +} + +fn determine_literal_type( + called_packages: &HashMap, + commands: &[Command], + arg_idx: usize, +) -> Result { + fn set_type(maybe_type: &mut Option, ty: Type) -> Result<()> { + match maybe_type { + Some(literal_type) if literal_type == &ty => {} + Some(_) => { + return Err(RestError::new( + axum::http::StatusCode::BAD_REQUEST, + "unable to resolve literal as it is used as multiple different types across commands", + )) + } + None => { + *maybe_type = Some(ty); + } + } + + Ok(()) + } + let mut literal_type = None; + + for (command, idx) in super::find_arg_uses(arg_idx, commands) { + match (command, idx) { + (Command::MoveCall(move_call), Some(idx)) => { + let arg_type = super::arg_type_of_move_call_input(called_packages, move_call, idx)?; + set_type(&mut literal_type, arg_type.to_owned())?; + } + (Command::TransferObjects(_), None) => { + set_type(&mut literal_type, Type::Address)?; + } + + (Command::SplitCoins(_), Some(_)) => { + set_type(&mut literal_type, Type::U64)?; + } + (Command::MakeMoveVector(make_move_vector), Some(_)) => { + if let Some(ty) = &make_move_vector.type_ { + let ty = + sui_types::sui_sdk_types_conversions::type_tag_sdk_to_core(ty.clone())?; + set_type(&mut literal_type, ty.into())?; + } else { + return Err(RestError::new( + axum::http::StatusCode::BAD_REQUEST, + "unable to resolve literal as an unknown type", + )); + } + } + + // Invalid uses of Literal Arguments + + // Pure arg can't be used as an object to transfer + (Command::TransferObjects(_), Some(_)) + | (Command::Upgrade(_), _) + | (Command::MergeCoins(_), _) + | (Command::SplitCoins(_), None) => { + return Err(RestError::new( + axum::http::StatusCode::BAD_REQUEST, + "invalid use of literal", + )); + } + + // bug in find_arg_uses + (Command::MakeMoveVector(_), None) + | (Command::Publish(_), _) + | (Command::MoveCall(_), None) => { + return Err(RestError::new( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + "error determining type of literal", + )); + } + } + } + + literal_type.ok_or_else(|| { + RestError::new( + axum::http::StatusCode::BAD_REQUEST, + "unable to determine type of literal", + ) + }) +} + +fn resolve_literal_to_type(buf: &mut Vec, type_: &Type, value: &UnresolvedValue) -> Result<()> { + match type_ { + Type::Bool => resolve_as_bool(buf, value), + Type::U8 => resolve_as_number::(buf, value), + Type::U16 => resolve_as_number::(buf, value), + Type::U32 => resolve_as_number::(buf, value), + Type::U64 => resolve_as_number::(buf, value), + Type::U128 => resolve_as_number::(buf, value), + Type::U256 => resolve_as_number::(buf, value), + Type::Address => resolve_as_address(buf, value), + + // 0x1::ascii::String and 0x1::string::String + Type::Struct { + address, + module, + name, + type_arguments, + } if address == &MOVE_STDLIB_ADDRESS + // 0x1::ascii::String + && ((module.as_ref() == STD_ASCII_MODULE_NAME + && name.as_ref() == STD_ASCII_STRUCT_NAME) + // 0x1::string::String + || (module.as_ref() == STD_UTF8_MODULE_NAME + && name.as_ref() == STD_UTF8_STRUCT_NAME)) + && type_arguments.is_empty() => + { + resolve_as_string(buf, value) + } + + // Option + Type::Struct { + address, + module, + name, + type_arguments, + } if address == &MOVE_STDLIB_ADDRESS + && module.as_ref() == STD_OPTION_MODULE_NAME + && name.as_ref() == STD_OPTION_STRUCT_NAME + && type_arguments.len() == 1 => + { + let ty = type_arguments + .first() + .expect("length of type_arguments is 1"); + + resolve_as_option(buf, ty, value) + } + + // Vec + Type::Vector(ty) => resolve_as_vector(buf, ty, value), + + Type::Signer + | Type::Struct { .. } + | Type::TypeParameter(_) + | Type::Reference(_) + | Type::MutableReference(_) => Err(RestError::new( + axum::http::StatusCode::BAD_REQUEST, + format!("literal cannot be resolved into type {type_}"), + )), + } +} + +fn resolve_as_bool(buf: &mut Vec, value: &UnresolvedValue) -> Result<()> { + let b: bool = match value { + UnresolvedValue::Bool(b) => *b, + UnresolvedValue::String(s) => s.parse().map_err(|e| { + RestError::new( + axum::http::StatusCode::BAD_REQUEST, + format!("literal cannot be resolved as bool: {e}"), + ) + })?, + UnresolvedValue::Null | UnresolvedValue::Number(_) | UnresolvedValue::Array(_) => { + return Err(RestError::new( + axum::http::StatusCode::BAD_REQUEST, + "literal cannot be resolved into type bool", + )) + } + }; + + bcs::serialize_into(buf, &b)?; + + Ok(()) +} + +fn resolve_as_number(buf: &mut Vec, value: &UnresolvedValue) -> Result<()> +where + T: std::str::FromStr + TryFrom + serde::Serialize, + ::Err: std::fmt::Display, + >::Error: std::fmt::Display, +{ + let n: T = match value { + UnresolvedValue::Number(n) => T::try_from(*n).map_err(|e| { + RestError::new( + axum::http::StatusCode::BAD_REQUEST, + format!( + "literal cannot be resolved as {}: {e}", + std::any::type_name::() + ), + ) + })?, + + UnresolvedValue::String(s) => s.parse().map_err(|e| { + RestError::new( + axum::http::StatusCode::BAD_REQUEST, + format!( + "literal cannot be resolved as {}: {e}", + std::any::type_name::() + ), + ) + })?, + + UnresolvedValue::Null | UnresolvedValue::Bool(_) | UnresolvedValue::Array(_) => { + return Err(RestError::new( + axum::http::StatusCode::BAD_REQUEST, + format!( + "literal cannot be resolved into type {}", + std::any::type_name::() + ), + )) + } + }; + + bcs::serialize_into(buf, &n)?; + + Ok(()) +} + +fn resolve_as_address(buf: &mut Vec, value: &UnresolvedValue) -> Result<()> { + let address = match value { + // parse as ObjectID to handle the case where 0x is present or missing + UnresolvedValue::String(s) => s.parse::().map_err(|e| { + RestError::new( + axum::http::StatusCode::BAD_REQUEST, + format!("literal cannot be resolved as bool: {e}"), + ) + })?, + UnresolvedValue::Null + | UnresolvedValue::Bool(_) + | UnresolvedValue::Number(_) + | UnresolvedValue::Array(_) => { + return Err(RestError::new( + axum::http::StatusCode::BAD_REQUEST, + "literal cannot be resolved into type address", + )) + } + }; + + bcs::serialize_into(buf, &address)?; + + Ok(()) +} + +fn resolve_as_string(buf: &mut Vec, value: &UnresolvedValue) -> Result<()> { + match value { + UnresolvedValue::String(s) => { + bcs::serialize_into(buf, s)?; + } + UnresolvedValue::Bool(_) + | UnresolvedValue::Null + | UnresolvedValue::Number(_) + | UnresolvedValue::Array(_) => { + return Err(RestError::new( + axum::http::StatusCode::BAD_REQUEST, + "literal cannot be resolved into string", + )) + } + }; + + Ok(()) +} + +fn resolve_as_option(buf: &mut Vec, type_: &Type, value: &UnresolvedValue) -> Result<()> { + match value { + UnresolvedValue::Null => { + buf.push(0); + } + UnresolvedValue::Bool(_) + | UnresolvedValue::Number(_) + | UnresolvedValue::String(_) + | UnresolvedValue::Array(_) => { + buf.push(1); + resolve_literal_to_type(buf, type_, value)?; + } + } + + Ok(()) +} + +fn resolve_as_vector(buf: &mut Vec, type_: &Type, value: &UnresolvedValue) -> Result<()> { + fn write_u32_as_uleb128(buf: &mut Vec, mut value: u32) { + while value >= 0x80 { + // Write 7 (lowest) bits of data and set the 8th bit to 1. + let byte = (value & 0x7f) as u8; + buf.push(byte | 0x80); + value >>= 7; + } + // Write the remaining bits of data and set the highest bit to 0. + buf.push(value as u8); + } + + match value { + UnresolvedValue::Array(array) => { + write_u32_as_uleb128(buf, array.len() as u32); + for value in array { + resolve_literal_to_type(buf, type_, value)?; + } + } + UnresolvedValue::Bool(_) + | UnresolvedValue::Number(_) + | UnresolvedValue::String(_) + | UnresolvedValue::Null => { + return Err(RestError::new( + axum::http::StatusCode::BAD_REQUEST, + format!("literal cannot be resolved into type Vector<{type_}>"), + )); + } + } + + Ok(()) +} + +#[cfg(test)] +mod test { + use super::*; + use move_binary_format::normalized::Type; + use move_core_types::{account_address::AccountAddress, u256::U256}; + + fn test_resolve_literal(ty: Type, value: UnresolvedValue, expected: Option>) { + let mut buf = Vec::new(); + match (resolve_literal_to_type(&mut buf, &ty, &value), expected) { + (Ok(_), None) => { + panic!("resolving literal succeeded but failure was expected: {ty} {value:?}") + } + (Ok(()), Some(expected)) => assert_eq!(buf, expected), + (Err(_), None) => {} + (Err(_), Some(_)) => { + panic!("failed to resolve literal {value:?} as {ty}"); + } + } + } + + #[test] + fn resolve_bool() { + let test_cases = [ + (Type::Bool, UnresolvedValue::Bool(true), Some(vec![1])), + (Type::Bool, UnresolvedValue::Bool(false), Some(vec![0])), + ( + Type::Bool, + UnresolvedValue::String("true".into()), + Some(vec![1]), + ), + ( + Type::Bool, + UnresolvedValue::String("false".into()), + Some(vec![0]), + ), + (Type::Bool, UnresolvedValue::Null, None), + (Type::Bool, UnresolvedValue::Number(0), None), + (Type::Bool, UnresolvedValue::Array(vec![]), None), + (Type::Bool, UnresolvedValue::String("foo".into()), None), + ]; + + for (ty, value, expected) in test_cases { + test_resolve_literal(ty, value, expected); + } + } + + #[test] + fn resolve_number() { + let test_cases = [ + // U8 Successful cases + ( + Type::U8, + UnresolvedValue::Number(u8::MAX.into()), + Some(bcs::to_bytes(&u8::MAX).unwrap()), + ), + ( + Type::U8, + UnresolvedValue::Number(u8::MIN.into()), + Some(bcs::to_bytes(&u8::MIN).unwrap()), + ), + ( + Type::U8, + UnresolvedValue::String(u8::MAX.to_string()), + Some(bcs::to_bytes(&u8::MAX).unwrap()), + ), + ( + Type::U8, + UnresolvedValue::String(u8::MIN.to_string()), + Some(bcs::to_bytes(&u8::MIN).unwrap()), + ), + // U8 failure cases + (Type::U8, UnresolvedValue::Bool(true), None), + (Type::U8, UnresolvedValue::Array(vec![]), None), + (Type::U8, UnresolvedValue::Null, None), + (Type::U8, UnresolvedValue::String("foo".into()), None), + ( + Type::U8, + UnresolvedValue::String(u64::MAX.to_string()), + None, + ), + (Type::U8, UnresolvedValue::Number(u64::MAX), None), + // U16 Successful cases + ( + Type::U16, + UnresolvedValue::Number(u16::MAX.into()), + Some(bcs::to_bytes(&u16::MAX).unwrap()), + ), + ( + Type::U16, + UnresolvedValue::Number(u16::MIN.into()), + Some(bcs::to_bytes(&u16::MIN).unwrap()), + ), + ( + Type::U16, + UnresolvedValue::String(u16::MAX.to_string()), + Some(bcs::to_bytes(&u16::MAX).unwrap()), + ), + ( + Type::U16, + UnresolvedValue::String(u16::MIN.to_string()), + Some(bcs::to_bytes(&u16::MIN).unwrap()), + ), + // U16 failure cases + (Type::U16, UnresolvedValue::Bool(true), None), + (Type::U16, UnresolvedValue::Array(vec![]), None), + (Type::U16, UnresolvedValue::Null, None), + (Type::U16, UnresolvedValue::String("foo".into()), None), + ( + Type::U16, + UnresolvedValue::String(u64::MAX.to_string()), + None, + ), + (Type::U16, UnresolvedValue::Number(u64::MAX), None), + // U32 Successful cases + ( + Type::U32, + UnresolvedValue::Number(u32::MAX.into()), + Some(bcs::to_bytes(&u32::MAX).unwrap()), + ), + ( + Type::U32, + UnresolvedValue::Number(u32::MIN.into()), + Some(bcs::to_bytes(&u32::MIN).unwrap()), + ), + ( + Type::U32, + UnresolvedValue::String(u32::MAX.to_string()), + Some(bcs::to_bytes(&u32::MAX).unwrap()), + ), + ( + Type::U32, + UnresolvedValue::String(u32::MIN.to_string()), + Some(bcs::to_bytes(&u32::MIN).unwrap()), + ), + // U32 failure cases + (Type::U32, UnresolvedValue::Bool(true), None), + (Type::U32, UnresolvedValue::Array(vec![]), None), + (Type::U32, UnresolvedValue::Null, None), + (Type::U32, UnresolvedValue::String("foo".into()), None), + ( + Type::U32, + UnresolvedValue::String(u64::MAX.to_string()), + None, + ), + (Type::U32, UnresolvedValue::Number(u64::MAX), None), + // U64 Successful cases + ( + Type::U64, + UnresolvedValue::Number(u64::MAX), + Some(bcs::to_bytes(&u64::MAX).unwrap()), + ), + ( + Type::U64, + UnresolvedValue::Number(u64::MIN), + Some(bcs::to_bytes(&u64::MIN).unwrap()), + ), + ( + Type::U64, + UnresolvedValue::String(u64::MAX.to_string()), + Some(bcs::to_bytes(&u64::MAX).unwrap()), + ), + ( + Type::U64, + UnresolvedValue::String(u64::MIN.to_string()), + Some(bcs::to_bytes(&u64::MIN).unwrap()), + ), + // U64 failure cases + (Type::U64, UnresolvedValue::Bool(true), None), + (Type::U64, UnresolvedValue::Array(vec![]), None), + (Type::U64, UnresolvedValue::Null, None), + (Type::U64, UnresolvedValue::String("foo".into()), None), + ( + Type::U64, + UnresolvedValue::String(u128::MAX.to_string()), + None, + ), + // U128 Successful cases + ( + Type::U128, + UnresolvedValue::Number(u64::MAX), + Some(bcs::to_bytes(&u128::from(u64::MAX)).unwrap()), + ), + ( + Type::U128, + UnresolvedValue::Number(u64::MIN), + Some(bcs::to_bytes(&u128::MIN).unwrap()), + ), + ( + Type::U128, + UnresolvedValue::String(u128::MAX.to_string()), + Some(bcs::to_bytes(&u128::MAX).unwrap()), + ), + ( + Type::U128, + UnresolvedValue::String(u128::MIN.to_string()), + Some(bcs::to_bytes(&u128::MIN).unwrap()), + ), + // U128 failure cases + (Type::U128, UnresolvedValue::Bool(true), None), + (Type::U128, UnresolvedValue::Array(vec![]), None), + (Type::U128, UnresolvedValue::Null, None), + (Type::U128, UnresolvedValue::String("foo".into()), None), + ( + Type::U128, + UnresolvedValue::String(U256::max_value().to_string()), + None, + ), + // U256 Successful cases + ( + Type::U256, + UnresolvedValue::Number(u64::MAX), + Some(bcs::to_bytes(&U256::from(u64::MAX)).unwrap()), + ), + ( + Type::U256, + UnresolvedValue::Number(u64::MIN), + Some(bcs::to_bytes(&U256::zero()).unwrap()), + ), + ( + Type::U256, + UnresolvedValue::String(U256::max_value().to_string()), + Some(bcs::to_bytes(&U256::max_value()).unwrap()), + ), + ( + Type::U256, + UnresolvedValue::String(U256::zero().to_string()), + Some(bcs::to_bytes(&U256::zero()).unwrap()), + ), + // U256 failure cases + (Type::U256, UnresolvedValue::Bool(true), None), + (Type::U256, UnresolvedValue::Array(vec![]), None), + (Type::U256, UnresolvedValue::Null, None), + (Type::U256, UnresolvedValue::String("foo".into()), None), + ]; + + for (ty, value, expected) in test_cases { + test_resolve_literal(ty, value, expected); + } + } + + #[test] + fn resolve_address() { + let test_cases = [ + // Address Successful cases + ( + Type::Address, + // with 0x prefix + UnresolvedValue::String(AccountAddress::TWO.to_canonical_string(true)), + Some(bcs::to_bytes(&AccountAddress::TWO).unwrap()), + ), + ( + Type::Address, + // without 0x prefix + UnresolvedValue::String(AccountAddress::TWO.to_canonical_string(false)), + Some(bcs::to_bytes(&AccountAddress::TWO).unwrap()), + ), + ( + Type::Address, + // with 0x prefix and trimmed 0s + UnresolvedValue::String(AccountAddress::TWO.to_hex_literal()), + Some(bcs::to_bytes(&AccountAddress::TWO).unwrap()), + ), + // Address failure cases + (Type::Address, UnresolvedValue::Bool(true), None), + (Type::Address, UnresolvedValue::Array(vec![]), None), + (Type::Address, UnresolvedValue::Null, None), + (Type::Address, UnresolvedValue::String("foo".into()), None), + (Type::Address, UnresolvedValue::Number(0), None), + ( + Type::Address, + // without 0x prefix and with trimmed 0s + UnresolvedValue::String(AccountAddress::TWO.short_str_lossless()), + None, + ), + ]; + + for (ty, value, expected) in test_cases { + test_resolve_literal(ty, value, expected); + } + } + + #[test] + fn resolve_string() { + fn utf8() -> Type { + Type::Struct { + address: MOVE_STDLIB_ADDRESS, + module: STD_UTF8_MODULE_NAME.to_owned(), + name: STD_UTF8_STRUCT_NAME.to_owned(), + type_arguments: vec![], + } + } + fn ascii() -> Type { + Type::Struct { + address: MOVE_STDLIB_ADDRESS, + module: STD_ASCII_MODULE_NAME.to_owned(), + name: STD_ASCII_STRUCT_NAME.to_owned(), + type_arguments: vec![], + } + } + + let test_cases = [ + // string Successful cases + ( + utf8(), + UnresolvedValue::String("foo".into()), + Some(bcs::to_bytes(&"foo").unwrap()), + ), + ( + ascii(), + UnresolvedValue::String("foo".into()), + Some(bcs::to_bytes(&"foo").unwrap()), + ), + ( + utf8(), + UnresolvedValue::String("".into()), + Some(bcs::to_bytes(&"").unwrap()), + ), + ( + ascii(), + UnresolvedValue::String("".into()), + Some(bcs::to_bytes(&"").unwrap()), + ), + // String failure cases + (utf8(), UnresolvedValue::Bool(true), None), + (utf8(), UnresolvedValue::Array(vec![]), None), + (utf8(), UnresolvedValue::Null, None), + (utf8(), UnresolvedValue::Number(0), None), + (ascii(), UnresolvedValue::Bool(true), None), + (ascii(), UnresolvedValue::Array(vec![]), None), + (ascii(), UnresolvedValue::Null, None), + (ascii(), UnresolvedValue::Number(0), None), + ]; + + for (ty, value, expected) in test_cases { + test_resolve_literal(ty, value, expected); + } + } + + #[test] + fn resolve_option() { + fn option_type(t: Type) -> Type { + Type::Struct { + address: MOVE_STDLIB_ADDRESS, + module: STD_OPTION_MODULE_NAME.to_owned(), + name: STD_OPTION_STRUCT_NAME.to_owned(), + type_arguments: vec![t], + } + } + + let test_cases = [ + // Option Successful cases + ( + option_type(Type::Address), + UnresolvedValue::String(AccountAddress::TWO.to_canonical_string(true)), + Some(bcs::to_bytes(&Some(AccountAddress::TWO)).unwrap()), + ), + ( + option_type(Type::Address), + UnresolvedValue::Null, + Some(vec![0]), + ), + ( + option_type(Type::U64), + UnresolvedValue::Number(u64::MIN), + Some(bcs::to_bytes(&Some(u64::MIN)).unwrap()), + ), + ( + option_type(Type::U64), + UnresolvedValue::String(u64::MAX.to_string()), + Some(bcs::to_bytes(&Some(u64::MAX)).unwrap()), + ), + ( + option_type(Type::Bool), + UnresolvedValue::Bool(true), + Some(bcs::to_bytes(&Some(true)).unwrap()), + ), + ( + option_type(Type::Bool), + UnresolvedValue::Null, + Some(vec![0]), + ), + // Option failure cases + (option_type(Type::Bool), UnresolvedValue::Number(0), None), + ]; + + for (ty, value, expected) in test_cases { + test_resolve_literal(ty, value, expected); + } + } + + #[test] + fn resolve_vector() { + fn vector_type(t: Type) -> Type { + Type::Vector(Box::new(t)) + } + + let test_cases = [ + // Vector Successful cases + ( + vector_type(Type::Address), + UnresolvedValue::Array(vec![ + UnresolvedValue::String(AccountAddress::TWO.to_canonical_string(true)), + UnresolvedValue::String(AccountAddress::ONE.to_canonical_string(true)), + ]), + Some(bcs::to_bytes(&vec![AccountAddress::TWO, AccountAddress::ONE]).unwrap()), + ), + ( + vector_type(Type::U8), + UnresolvedValue::Array(vec![UnresolvedValue::Number(9)]), + Some(vec![1, 9]), + ), + ( + vector_type(Type::U8), + UnresolvedValue::Array(vec![]), + Some(vec![0]), + ), + ( + vector_type(vector_type(Type::U8)), + UnresolvedValue::Array(vec![UnresolvedValue::Array(vec![ + UnresolvedValue::Number(9), + ])]), + Some(bcs::to_bytes(&vec![vec![9u8]]).unwrap()), + ), + ( + vector_type(Type::Bool), + // verify we handle uleb128 encoding of length properly + UnresolvedValue::Array(vec![UnresolvedValue::Bool(true); 256]), + Some(bcs::to_bytes(&vec![true; 256]).unwrap()), + ), + // Vector failure cases + (vector_type(Type::U64), UnresolvedValue::Bool(true), None), + (vector_type(Type::U64), UnresolvedValue::Number(0), None), + (vector_type(Type::U64), UnresolvedValue::Null, None), + (vector_type(Type::U64), UnresolvedValue::Number(0), None), + ( + vector_type(Type::Address), + UnresolvedValue::Array(vec![ + UnresolvedValue::String(AccountAddress::TWO.to_canonical_string(true)), + UnresolvedValue::Number(5), + ]), + None, + ), + ]; + + for (ty, value, expected) in test_cases { + test_resolve_literal(ty, value, expected); + } + } +} diff --git a/crates/sui-rest-api/src/transactions/resolve/mod.rs b/crates/sui-rest-api/src/transactions/resolve/mod.rs new file mode 100644 index 0000000000000..dcb905d8f9104 --- /dev/null +++ b/crates/sui-rest-api/src/transactions/resolve/mod.rs @@ -0,0 +1,838 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::collections::BTreeMap; +use std::collections::HashMap; + +use super::execution::SimulateTransactionQueryParameters; +use super::TransactionSimulationResponse; +use crate::accept::AcceptFormat; +use crate::objects::ObjectNotFoundError; +use crate::openapi::ApiEndpoint; +use crate::openapi::OperationBuilder; +use crate::openapi::RequestBodyBuilder; +use crate::openapi::ResponseBuilder; +use crate::openapi::RouteHandler; +use crate::reader::StateReader; +use crate::response::ResponseContent; +use crate::RestError; +use crate::RestService; +use crate::Result; +use axum::extract::Query; +use axum::extract::State; +use axum::Json; +use itertools::Itertools; +use move_binary_format::normalized; +use schemars::JsonSchema; +use sui_protocol_config::ProtocolConfig; +use sui_sdk_types::types::Argument; +use sui_sdk_types::types::Command; +use sui_sdk_types::types::ObjectId; +use sui_sdk_types::types::Transaction; +use sui_sdk_types::types::UnresolvedInputArgument; +use sui_sdk_types::types::UnresolvedObjectReference; +use sui_sdk_types::types::UnresolvedProgrammableTransaction; +use sui_sdk_types::types::UnresolvedTransaction; +use sui_sdk_types::types::UnresolvedValue; +use sui_types::base_types::ObjectID; +use sui_types::base_types::ObjectRef; +use sui_types::base_types::SuiAddress; +use sui_types::effects::TransactionEffectsAPI; +use sui_types::gas::GasCostSummary; +use sui_types::gas_coin::GasCoin; +use sui_types::move_package::MovePackage; +use sui_types::transaction::CallArg; +use sui_types::transaction::GasData; +use sui_types::transaction::ObjectArg; +use sui_types::transaction::ProgrammableTransaction; +use sui_types::transaction::TransactionData; +use sui_types::transaction::TransactionDataAPI; +use tap::Pipe; + +mod literal; + +pub struct ResolveTransaction; + +impl ApiEndpoint for ResolveTransaction { + fn method(&self) -> axum::http::Method { + axum::http::Method::POST + } + + fn path(&self) -> &'static str { + "/transactions/resolve" + } + + fn operation( + &self, + generator: &mut schemars::gen::SchemaGenerator, + ) -> openapiv3::v3_1::Operation { + OperationBuilder::new() + .tag("Transactions") + .operation_id("ResolveTransaction") + .query_parameters::(generator) + .request_body( + RequestBodyBuilder::new() + .json_content::(generator) + .build(), + ) + .response( + 200, + ResponseBuilder::new() + .json_content::(generator) + .bcs_content() + .build(), + ) + .build() + } + + fn handler(&self) -> RouteHandler { + RouteHandler::new(self.method(), resolve_transaction) + } +} + +async fn resolve_transaction( + State(state): State, + Query(parameters): Query, + accept: AcceptFormat, + Json(unresolved_transaction): Json, +) -> Result> { + let executor = state + .executor + .as_ref() + .ok_or_else(|| anyhow::anyhow!("No Transaction Executor"))?; + let (reference_gas_price, protocol_config) = { + let system_state = state.reader.get_system_state_summary()?; + + let current_protocol_version = state.reader.get_system_state_summary()?.protocol_version; + + let protocol_config = ProtocolConfig::get_for_version_if_supported( + current_protocol_version.into(), + state.reader.inner().get_chain_identifier()?.chain(), + ) + .ok_or_else(|| { + RestError::new( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + "unable to get current protocol config", + ) + })?; + + (system_state.reference_gas_price, protocol_config) + }; + let called_packages = + called_packages(&state.reader, &protocol_config, &unresolved_transaction)?; + let user_provided_budget = unresolved_transaction + .gas_payment + .as_ref() + .and_then(|payment| payment.budget); + let mut resolved_transaction = resolve_unresolved_transaction( + &state.reader, + &called_packages, + reference_gas_price, + protocol_config.max_tx_gas(), + unresolved_transaction, + )?; + + // If the user didn't provide a budget we need to run a quick simulation in order to calculate + // a good estimated budget to use + let budget = if let Some(user_provided_budget) = user_provided_budget { + user_provided_budget + } else { + let simulation_result = executor + .simulate_transaction(resolved_transaction.clone()) + .map_err(anyhow::Error::from)?; + + let estimate = estimate_gas_budget_from_gas_cost( + simulation_result.effects.gas_cost_summary(), + reference_gas_price, + ); + resolved_transaction.gas_data_mut().budget = estimate; + estimate + }; + + // If the user didn't provide any gas payment we need to do gas selection now + if resolved_transaction.gas_data().payment.is_empty() { + let input_objects = resolved_transaction + .input_objects() + .map_err(anyhow::Error::from)? + .iter() + .flat_map(|obj| match obj { + sui_types::transaction::InputObjectKind::ImmOrOwnedMoveObject((id, _, _)) => { + Some(*id) + } + _ => None, + }) + .collect_vec(); + let gas_coins = select_gas( + &state.reader, + resolved_transaction.gas_data().owner, + budget, + protocol_config.max_gas_payment_objects(), + &input_objects, + )?; + resolved_transaction.gas_data_mut().payment = gas_coins; + } + + let simulation = if parameters.simulate { + super::execution::simulate_transaction_impl( + executor, + ¶meters.simulate_transaction_parameters, + resolved_transaction.clone().try_into()?, + )? + .pipe(Some) + } else { + None + }; + + ResolveTransactionResponse { + transaction: resolved_transaction.try_into()?, + simulation, + } + .pipe(|response| match accept { + AcceptFormat::Json => ResponseContent::Json(response), + AcceptFormat::Bcs => ResponseContent::Bcs(response), + }) + .pipe(Ok) +} + +/// Query parameters for the resolve transaction endpoint +#[derive(Debug, Default, serde::Serialize, serde::Deserialize, JsonSchema)] +pub struct ResolveTransactionQueryParameters { + /// Request that the fully resolved transaction be simulated and have its results sent back in + /// the response. + #[serde(default)] + pub simulate: bool, + #[serde(flatten)] + pub simulate_transaction_parameters: SimulateTransactionQueryParameters, +} + +struct NormalizedPackage { + #[allow(unused)] + package: MovePackage, + normalized_modules: BTreeMap, +} + +fn called_packages( + reader: &StateReader, + protocol_config: &ProtocolConfig, + unresolved_transaction: &UnresolvedTransaction, +) -> Result> { + let binary_config = sui_types::execution_config_utils::to_binary_config(protocol_config); + let mut packages = HashMap::new(); + + for move_call in unresolved_transaction + .ptb + .commands + .iter() + .filter_map(|command| { + if let Command::MoveCall(move_call) = command { + Some(move_call) + } else { + None + } + }) + { + let package = reader + .inner() + .get_object(&(move_call.package.into()))? + .ok_or_else(|| ObjectNotFoundError::new(move_call.package))? + .data + .try_as_package() + .ok_or_else(|| { + RestError::new( + axum::http::StatusCode::BAD_REQUEST, + format!("object {} is not a package", move_call.package), + ) + })? + .to_owned(); + + // Normalization doesn't take the linkage or type origin tables into account, which means + // that if you have an upgraded package that introduces a new type, then that type's + // package ID is going to appear incorrectly if you fetch it from its normalized module. + // + // Despite the above this is safe given we are only using the signature information (and in + // particular the reference kind) from the normalized package. + let normalized_modules = package.normalize(&binary_config).map_err(|e| { + RestError::new( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + format!("unable to normalize package {}: {e}", move_call.package), + ) + })?; + let package = NormalizedPackage { + package, + normalized_modules, + }; + + packages.insert(move_call.package, package); + } + + Ok(packages) +} + +fn resolve_unresolved_transaction( + reader: &StateReader, + called_packages: &HashMap, + reference_gas_price: u64, + max_gas_budget: u64, + unresolved_transaction: UnresolvedTransaction, +) -> Result { + let sender = unresolved_transaction.sender.into(); + let gas_data = if let Some(unresolved_gas_payment) = unresolved_transaction.gas_payment { + let payment = unresolved_gas_payment + .objects + .into_iter() + .map(|unresolved| resolve_object_reference(reader, unresolved)) + .collect::>>()?; + GasData { + payment, + owner: unresolved_gas_payment.owner.into(), + price: unresolved_gas_payment.price.unwrap_or(reference_gas_price), + budget: unresolved_gas_payment.budget.unwrap_or(max_gas_budget), + } + } else { + GasData { + payment: vec![], + owner: sender, + price: reference_gas_price, + budget: max_gas_budget, + } + }; + let expiration = unresolved_transaction.expiration.into(); + let ptb = resolve_ptb(reader, called_packages, unresolved_transaction.ptb)?; + Ok(TransactionData::V1( + sui_types::transaction::TransactionDataV1 { + kind: sui_types::transaction::TransactionKind::ProgrammableTransaction(ptb), + sender, + gas_data, + expiration, + }, + )) +} + +/// Response type for the execute transaction endpoint +#[derive(Debug, serde::Serialize, serde::Deserialize, JsonSchema)] +pub struct ResolveTransactionResponse { + pub transaction: Transaction, + pub simulation: Option, +} + +fn resolve_object_reference( + reader: &StateReader, + unresolved_object_reference: UnresolvedObjectReference, +) -> Result { + let object_id = unresolved_object_reference.object_id; + let object = reader + .inner() + .get_object(&object_id.into())? + .ok_or_else(|| ObjectNotFoundError::new(object_id))?; + resolve_object_reference_with_object(&object, unresolved_object_reference) +} + +// Resolve an object reference against the provided object. +// +// Callers should check that the object_id matches the id in the `unresolved_object_reference` +// before calling. +fn resolve_object_reference_with_object( + object: &sui_types::object::Object, + unresolved_object_reference: UnresolvedObjectReference, +) -> Result { + let UnresolvedObjectReference { + object_id, + version, + digest, + } = unresolved_object_reference; + + match object.owner() { + sui_types::object::Owner::AddressOwner(_) | sui_types::object::Owner::Immutable => {} + _ => { + return Err(RestError::new( + axum::http::StatusCode::BAD_REQUEST, + format!("object {object_id} is not Immutable or AddressOwned"), + )) + } + } + + let id = object.id(); + let v = object.version(); + let d = object.digest(); + + // This really should be an assert + if object_id.inner() != &id.into_bytes() { + return Err(RestError::new( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + "provided object and object_id should match", + )); + } + + if version.is_some_and(|version| version != v.value()) { + return Err(RestError::new( + axum::http::StatusCode::BAD_REQUEST, + format!("provided version doesn't match, provided: {version:?} actual: {v}"), + )); + } + + if digest.is_some_and(|digest| digest.inner() != d.inner()) { + return Err(RestError::new( + axum::http::StatusCode::BAD_REQUEST, + format!("provided digest doesn't match, provided: {digest:?} actual: {d}"), + )); + } + + Ok((id, v, d)) +} + +fn resolve_ptb( + reader: &StateReader, + called_packages: &HashMap, + unresolved_ptb: UnresolvedProgrammableTransaction, +) -> Result { + let inputs = unresolved_ptb + .inputs + .into_iter() + .enumerate() + .map(|(arg_idx, arg)| { + resolve_arg( + reader, + called_packages, + &unresolved_ptb.commands, + arg, + arg_idx, + ) + }) + .collect::>()?; + + ProgrammableTransaction { + inputs, + commands: unresolved_ptb + .commands + .into_iter() + .map(TryInto::try_into) + .collect::>()?, + } + .pipe(Ok) +} + +fn resolve_arg( + reader: &StateReader, + called_packages: &HashMap, + commands: &[Command], + arg: UnresolvedInputArgument, + arg_idx: usize, +) -> Result { + use fastcrypto::encoding::Base64; + use fastcrypto::encoding::Encoding; + use sui_sdk_types::types::UnresolvedInputArgumentKind::*; + + let UnresolvedInputArgument { + kind, + value, + object_id, + version, + digest, + mutable, + } = arg; + + match (kind, value, object_id, version, digest, mutable) { + // pre serialized BCS input encoded as a base64 string + (Some(Pure), Some(UnresolvedValue::String(v)), None, None, None, None) => { + let value = Base64::decode(&v).map_err(|e| { + RestError::new( + axum::http::StatusCode::BAD_REQUEST, + format!("argument is an invalid pure arguement: {e}"), + ) + })?; + CallArg::Pure(value) + } + // pre serialized BCS input encoded as a a JSON array of u8s + (Some(Pure), Some(array @ UnresolvedValue::Array(_)), None, None, None, None) => { + let value = serde_json::from_value(serde_json::Value::from(array)).map_err(|e| { + RestError::new( + axum::http::StatusCode::BAD_REQUEST, + format!("argument is an invalid pure arguement: {e}"), + ) + })?; + CallArg::Pure(value) + } + + // Literal, unresolved pure argument + (Some(Literal), Some(value), None, None, None, None) + | (None, Some(value), None, None, None, None) => CallArg::Pure(literal::resolve_literal( + called_packages, + commands, + arg_idx, + value, + )?), + + // Immutable or owned + ( + Some(ImmutableOrOwned | Immutable | Owned), + None, + Some(object_id), + version, + digest, + None, + ) => CallArg::Object(ObjectArg::ImmOrOwnedObject(resolve_object_reference( + reader, + UnresolvedObjectReference { + object_id, + version, + digest, + }, + )?)), + + // Shared object + (Some(Shared), None, Some(object_id), _version, None, _mutable) => CallArg::Object( + resolve_shared_input(reader, called_packages, commands, arg_idx, object_id)?, + ), + + // Receiving + (Some(Receiving), None, Some(object_id), version, digest, None) => { + CallArg::Object(ObjectArg::Receiving(resolve_object_reference( + reader, + UnresolvedObjectReference { + object_id, + version, + digest, + }, + )?)) + } + + // Object, could be Immutable, Owned, Shared, or Receiving + (None, None, Some(object_id), version, digest, mutable) => CallArg::Object(resolve_object( + reader, + called_packages, + commands, + arg_idx, + object_id, + version, + digest, + mutable, + )?), + + _ => { + return Err(RestError::new( + axum::http::StatusCode::BAD_REQUEST, + "invalid unresolved input argument", + )) + } + } + .pipe(Ok) +} + +fn resolve_object( + reader: &StateReader, + called_packages: &HashMap, + commands: &[Command], + arg_idx: usize, + object_id: ObjectId, + version: Option, + digest: Option, + _mutable: Option, +) -> Result { + let id = object_id.into(); + let object = reader + .inner() + .get_object(&id)? + .ok_or_else(|| ObjectNotFoundError::new(object_id))?; + + match object.owner() { + sui_types::object::Owner::Immutable => resolve_object_reference_with_object( + &object, + UnresolvedObjectReference { + object_id, + version, + digest, + }, + ) + .map(ObjectArg::ImmOrOwnedObject), + + sui_types::object::Owner::AddressOwner(_) => { + let object_ref = resolve_object_reference_with_object( + &object, + UnresolvedObjectReference { + object_id, + version, + digest, + }, + )?; + + if is_input_argument_receiving(called_packages, commands, arg_idx)? { + ObjectArg::Receiving(object_ref) + } else { + ObjectArg::ImmOrOwnedObject(object_ref) + } + .pipe(Ok) + } + sui_types::object::Owner::Shared { .. } => { + resolve_shared_input_with_object(called_packages, commands, arg_idx, object) + } + sui_types::object::Owner::ObjectOwner(_) => Err(RestError::new( + axum::http::StatusCode::BAD_REQUEST, + format!("object {object_id} is object owned and cannot be used as an input"), + )), + } +} + +fn resolve_shared_input( + reader: &StateReader, + called_packages: &HashMap, + commands: &[Command], + arg_idx: usize, + object_id: ObjectId, +) -> Result { + let id = object_id.into(); + let object = reader + .inner() + .get_object(&id)? + .ok_or_else(|| ObjectNotFoundError::new(object_id))?; + resolve_shared_input_with_object(called_packages, commands, arg_idx, object) +} + +// Checks if the provided input argument is used as a recieving object +fn is_input_argument_receiving( + called_packages: &HashMap, + commands: &[Command], + arg_idx: usize, +) -> Result { + let (receiving_package, receiving_module, receiving_struct) = + sui_types::transfer::RESOLVED_RECEIVING_STRUCT; + + let mut receiving = false; + for (command, idx) in find_arg_uses(arg_idx, commands) { + if let (Command::MoveCall(move_call), Some(idx)) = (command, idx) { + let arg_type = arg_type_of_move_call_input(called_packages, move_call, idx)?; + + if let move_binary_format::normalized::Type::Struct { + address, + module, + name, + .. + } = arg_type + { + if receiving_package == address + && receiving_module == module.as_ref() + && receiving_struct == name.as_ref() + { + receiving = true; + } + } + } + + //XXX do we want to ensure its only used once as receiving? + if receiving { + break; + } + } + + Ok(receiving) +} + +// TODO still need to handle the case where a function parameter is a generic parameter and the +// real type needs to be lookedup from the provided type args in the MoveCall itself +fn arg_type_of_move_call_input<'a>( + called_packages: &'a HashMap, + move_call: &sui_sdk_types::types::MoveCall, + idx: usize, +) -> Result<&'a move_binary_format::normalized::Type> { + let function = called_packages + // Find the package + .get(&move_call.package) + // Find the module + .and_then(|package| package.normalized_modules.get(move_call.module.as_str())) + // Find the function + .and_then(|module| module.functions.get(move_call.function.as_str())) + .ok_or_else(|| { + RestError::new( + axum::http::StatusCode::BAD_REQUEST, + format!( + "unable to find function {package}::{module}::{function}", + package = move_call.package, + module = move_call.module, + function = move_call.function + ), + ) + })?; + function.parameters.get(idx).ok_or_else(|| { + RestError::new( + axum::http::StatusCode::BAD_REQUEST, + "invalid input parameter", + ) + }) +} + +fn resolve_shared_input_with_object( + called_packages: &HashMap, + commands: &[Command], + arg_idx: usize, + object: sui_types::object::Object, +) -> Result { + let object_id = object.id(); + let initial_shared_version = if let sui_types::object::Owner::Shared { + initial_shared_version, + } = object.owner() + { + *initial_shared_version + } else { + return Err(RestError::new( + axum::http::StatusCode::BAD_REQUEST, + format!("object {object_id} is not a shared object"), + )); + }; + let mut mutable = false; + for (command, idx) in find_arg_uses(arg_idx, commands) { + match (command, idx) { + (Command::MoveCall(move_call), Some(idx)) => { + let arg_type = arg_type_of_move_call_input(called_packages, move_call, idx)?; + if matches!( + arg_type, + move_binary_format::normalized::Type::MutableReference(_) + | move_binary_format::normalized::Type::Struct { .. } + ) { + mutable = true; + } + } + (Command::SplitCoins(_) | Command::MergeCoins(_) | Command::MakeMoveVector(_), _) => { + mutable = true; + } + _ => {} + } + // Early break out of the loop if we've already determined that the shared object + // is needed to be mutable + if mutable { + break; + } + } + + Ok(ObjectArg::SharedObject { + id: object_id, + initial_shared_version, + mutable, + }) +} + +/// Given an particular input argument, find all of its uses. +/// +/// The returned iterator contains all commands where the argument is used and an optional index +/// to indicate where the argument is used in that command. +fn find_arg_uses( + arg_idx: usize, + commands: &[Command], +) -> impl Iterator)> { + fn matches_input_arg(arg: Argument, arg_idx: usize) -> bool { + matches!(arg, Argument::Input(idx) if idx as usize == arg_idx) + } + + commands.iter().filter_map(move |command| { + match command { + Command::MoveCall(move_call) => move_call + .arguments + .iter() + .position(|elem| matches_input_arg(*elem, arg_idx)) + .map(Some), + Command::TransferObjects(transfer_objects) => { + if matches_input_arg(transfer_objects.address, arg_idx) { + Some(None) + } else { + transfer_objects + .objects + .iter() + .position(|elem| matches_input_arg(*elem, arg_idx)) + .map(Some) + } + } + Command::SplitCoins(split_coins) => { + if matches_input_arg(split_coins.coin, arg_idx) { + Some(None) + } else { + split_coins + .amounts + .iter() + .position(|amount| matches_input_arg(*amount, arg_idx)) + .map(Some) + } + } + Command::MergeCoins(merge_coins) => { + if matches_input_arg(merge_coins.coin, arg_idx) { + Some(None) + } else { + merge_coins + .coins_to_merge + .iter() + .position(|elem| matches_input_arg(*elem, arg_idx)) + .map(Some) + } + } + Command::Publish(_) => None, + Command::MakeMoveVector(make_move_vector) => make_move_vector + .elements + .iter() + .position(|elem| matches_input_arg(*elem, arg_idx)) + .map(Some), + Command::Upgrade(upgrade) => matches_input_arg(upgrade.ticket, arg_idx).then_some(None), + } + .map(|x| (command, x)) + }) +} + +/// Estimate the gas budget using the gas_cost_summary from a previous DryRun +/// +/// The estimated gas budget is computed as following: +/// * the maximum between A and B, where: +/// A = computation cost + GAS_SAFE_OVERHEAD * reference gas price +/// B = computation cost + storage cost - storage rebate + GAS_SAFE_OVERHEAD * reference gas price +/// overhead +/// +/// This gas estimate is computed similarly as in the TypeScript SDK +fn estimate_gas_budget_from_gas_cost( + gas_cost_summary: &GasCostSummary, + reference_gas_price: u64, +) -> u64 { + const GAS_SAFE_OVERHEAD: u64 = 1000; + + let safe_overhead = GAS_SAFE_OVERHEAD * reference_gas_price; + let computation_cost_with_overhead = gas_cost_summary.computation_cost + safe_overhead; + + let gas_usage = gas_cost_summary.net_gas_usage() + safe_overhead as i64; + computation_cost_with_overhead.max(if gas_usage < 0 { 0 } else { gas_usage as u64 }) +} + +fn select_gas( + reader: &StateReader, + owner: SuiAddress, + budget: u64, + max_gas_payment_objects: u32, + input_objects: &[ObjectID], +) -> Result> { + //TODO implement index of gas coins sorted in order of decreasing value + let gas_coins = reader + .inner() + .account_owned_objects_info_iter(owner, None)? + .filter(|info| info.type_.is_gas_coin()) + .filter(|info| !input_objects.contains(&info.object_id)) + .filter_map(|info| reader.inner().get_object(&info.object_id).ok().flatten()) + .filter_map(|object| { + GasCoin::try_from(&object) + .ok() + .map(|coin| (object.compute_object_reference(), coin.value())) + }) + .take(max_gas_payment_objects as usize); + + let mut selected_gas = vec![]; + let mut selected_gas_value = 0; + + for (object_ref, value) in gas_coins { + selected_gas.push(object_ref); + selected_gas_value += value; + } + + if selected_gas_value >= budget { + Ok(selected_gas) + } else { + Err(RestError::new( + axum::http::StatusCode::BAD_REQUEST, + format!( + "unable to select sufficient gas coins from account {owner} \ + to satisfy required budget {budget}" + ), + )) + } +} diff --git a/crates/sui-rosetta/Cargo.toml b/crates/sui-rosetta/Cargo.toml index 4915e29c17732..dbf36f99d556c 100644 --- a/crates/sui-rosetta/Cargo.toml +++ b/crates/sui-rosetta/Cargo.toml @@ -37,6 +37,7 @@ sui-keys.workspace = true sui-json-rpc-types.workspace = true mysten-metrics.workspace = true shared-crypto.workspace = true +lru.workspace = true move-core-types.workspace = true @@ -50,3 +51,4 @@ tempfile.workspace = true rand.workspace = true reqwest.workspace = true move-cli.workspace = true +quick-js = "0.4.1" diff --git a/crates/sui-rosetta/src/construction.rs b/crates/sui-rosetta/src/construction.rs index 97265f9ab62cc..b39dbc99c4795 100644 --- a/crates/sui-rosetta/src/construction.rs +++ b/crates/sui-rosetta/src/construction.rs @@ -386,7 +386,7 @@ pub async fn metadata( sender, coins, objects, - total_coin_value, + total_coin_value: total_coin_value.into(), gas_price, budget, currency, diff --git a/crates/sui-rosetta/src/lib.rs b/crates/sui-rosetta/src/lib.rs index 27f6117f9f1a0..e539fcf8b2159 100644 --- a/crates/sui-rosetta/src/lib.rs +++ b/crates/sui-rosetta/src/lib.rs @@ -1,13 +1,14 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use std::collections::HashMap; use std::net::SocketAddr; +use std::num::NonZeroUsize; use std::string::ToString; use std::sync::Arc; use axum::routing::post; use axum::{Extension, Router}; +use lru::LruCache; use move_core_types::language_storage::TypeTag; use once_cell::sync::Lazy; use tokio::sync::Mutex; @@ -20,6 +21,10 @@ use crate::errors::Error::MissingMetadata; use crate::state::{CheckpointBlockProvider, OnlineServerContext}; use crate::types::{Currency, CurrencyMetadata, SuiEnv}; +#[cfg(test)] +#[path = "unit_tests/lib_tests.rs"] +mod lib_tests; + /// This lib implements the Rosetta online and offline server defined by the [Rosetta API Spec](https://www.rosetta-api.org/docs/Reference.html) mod account; mod block; @@ -45,7 +50,7 @@ pub struct RosettaOnlineServer { impl RosettaOnlineServer { pub fn new(env: SuiEnv, client: SuiClient) -> Self { - let coin_cache = CoinMetadataCache::new(client.clone()); + let coin_cache = CoinMetadataCache::new(client.clone(), NonZeroUsize::new(1000).unwrap()); let blocks = Arc::new(CheckpointBlockProvider::new( client.clone(), coin_cache.clone(), @@ -115,20 +120,20 @@ impl RosettaOfflineServer { #[derive(Clone)] pub struct CoinMetadataCache { client: SuiClient, - metadata: Arc>>, + metadata: Arc>>, } impl CoinMetadataCache { - pub fn new(client: SuiClient) -> Self { - CoinMetadataCache { + pub fn new(client: SuiClient, size: NonZeroUsize) -> Self { + Self { client, - metadata: Default::default(), + metadata: Arc::new(Mutex::new(LruCache::new(size))), } } pub async fn get_currency(&self, type_tag: &TypeTag) -> Result { let mut cache = self.metadata.lock().await; - if !cache.contains_key(type_tag) { + if !cache.contains(type_tag) { let metadata = self .client .coin_read_api() @@ -143,7 +148,7 @@ impl CoinMetadataCache { coin_type: type_tag.to_string(), }, }; - cache.insert(type_tag.clone(), ccy); + cache.push(type_tag.clone(), ccy); } cache.get(type_tag).cloned().ok_or(MissingMetadata) } diff --git a/crates/sui-rosetta/src/operations.rs b/crates/sui-rosetta/src/operations.rs index 38ea2cdca2e21..ced23f58e402b 100644 --- a/crates/sui-rosetta/src/operations.rs +++ b/crates/sui-rosetta/src/operations.rs @@ -299,7 +299,7 @@ impl Operations { .map(|amount| { let value: u64 = match *amount { SuiArgument::Input(i) => { - u64::from_str(inputs[i as usize].pure()?.to_json_value().as_str()?) + u64::from_str(inputs.get(i as usize)?.pure()?.to_json_value().as_str()?) .ok()? } SuiArgument::GasCoin @@ -319,7 +319,7 @@ impl Operations { recipient: SuiArgument, ) -> Option> { let addr = match recipient { - SuiArgument::Input(i) => inputs[i as usize].pure()?.to_sui_address().ok()?, + SuiArgument::Input(i) => inputs.get(i as usize)?.pure()?.to_sui_address().ok()?, SuiArgument::GasCoin | SuiArgument::Result(_) | SuiArgument::NestedResult(_, _) => { return None } @@ -361,7 +361,7 @@ impl Operations { // We use the position of the validator arg as a indicator of if the rosetta stake // transaction is staking the whole wallet or not, if staking whole wallet, // we have to omit the amount value in the final operation output. - SuiArgument::Input(i) => (*i==1, inputs[*i as usize].pure().map(|v|v.to_sui_address()).transpose()), + SuiArgument::Input(i) => (*i==1, inputs.get(*i as usize).and_then(|input| input.pure()).map(|v|v.to_sui_address()).transpose()), _=> return Ok(None), }; (some_amount.then_some(*amount), validator) @@ -380,7 +380,7 @@ impl Operations { [_, stake_id] => { match stake_id { SuiArgument::Input(i) => { - let id = inputs[*i as usize].object().ok_or_else(|| anyhow!("Cannot find stake id from input args."))?; + let id = inputs.get(*i as usize).and_then(|input| input.object()).ok_or_else(|| anyhow!("Cannot find stake id from input args."))?; // [WORKAROUND] - this is a hack to work out if the withdraw stake ops is for a selected stake or None (all stakes). // this hack is similar to the one in stake_call. let some_id = i % 2 == 1; @@ -651,7 +651,9 @@ impl Operations { .ok_or_else(|| anyhow!("Response balance changes should not be empty."))? { if let Ok(currency) = cache.get_currency(&balance_change.coin_type).await { - balance_changes.push((balance_change.clone(), currency)); + if !currency.symbol.is_empty() { + balance_changes.push((balance_change.clone(), currency)); + } } } diff --git a/crates/sui-rosetta/src/types.rs b/crates/sui-rosetta/src/types.rs index 57d906efd9a2b..2d499a494767f 100644 --- a/crates/sui-rosetta/src/types.rs +++ b/crates/sui-rosetta/src/types.rs @@ -663,7 +663,8 @@ pub struct ConstructionMetadata { pub sender: SuiAddress, pub coins: Vec, pub objects: Vec, - pub total_coin_value: u64, + #[serde(with = "str_format")] + pub total_coin_value: i128, pub gas_price: u64, pub budget: u64, pub currency: Option, @@ -987,7 +988,8 @@ impl InternalOperation { let state = builder.input(CallArg::SUI_SYSTEM_MUT)?; (validator, state, amount) } else { - let amount = builder.pure(metadata.total_coin_value - metadata.budget)?; + let amount = + builder.pure(metadata.total_coin_value as u64 - metadata.budget)?; let state = builder.input(CallArg::SUI_SYSTEM_MUT)?; let validator = builder.input(CallArg::Pure(bcs::to_bytes(&validator)?))?; (validator, state, amount) diff --git a/crates/sui-rosetta/src/unit_tests/balance_changing_tx_tests.rs b/crates/sui-rosetta/src/unit_tests/balance_changing_tx_tests.rs index a5bff2c3edee9..56187da0c4be2 100644 --- a/crates/sui-rosetta/src/unit_tests/balance_changing_tx_tests.rs +++ b/crates/sui-rosetta/src/unit_tests/balance_changing_tx_tests.rs @@ -12,6 +12,7 @@ use serde_json::json; use shared_crypto::intent::Intent; use signature::rand_core::OsRng; use std::collections::{BTreeMap, HashMap}; +use std::num::NonZeroUsize; use std::path::PathBuf; use std::str::FromStr; use sui_json_rpc_types::SuiTransactionBlockResponseOptions; @@ -750,7 +751,7 @@ async fn test_transaction( SuiExecutionStatus::Failure { .. } )); } - let coin_cache = CoinMetadataCache::new(client.clone()); + let coin_cache = CoinMetadataCache::new(client.clone(), NonZeroUsize::new(2).unwrap()); let ops = Operations::try_from_response(response.clone(), &coin_cache) .await .unwrap(); diff --git a/crates/sui-rosetta/src/unit_tests/lib_tests.rs b/crates/sui-rosetta/src/unit_tests/lib_tests.rs new file mode 100644 index 0000000000000..37e92dc329093 --- /dev/null +++ b/crates/sui-rosetta/src/unit_tests/lib_tests.rs @@ -0,0 +1,147 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::CoinMetadataCache; +use anyhow::anyhow; +use rand::prelude::IteratorRandom; +use rand::rngs::OsRng; +use shared_crypto::intent::Intent; +use std::num::NonZeroUsize; +use std::path::PathBuf; +use sui_json_rpc_types::{ + ObjectChange, SuiObjectDataOptions, SuiObjectResponseQuery, SuiTransactionBlockResponseOptions, +}; +use sui_keys::keystore::AccountKeystore; +use sui_move_build::BuildConfig; +use sui_sdk::SuiClient; +use sui_types::base_types::{ObjectID, ObjectRef, SuiAddress}; +use sui_types::gas_coin::GAS; +use sui_types::programmable_transaction_builder::ProgrammableTransactionBuilder; +use sui_types::quorum_driver_types::ExecuteTransactionRequestType; +use sui_types::transaction::{ + InputObjectKind, Transaction, TransactionData, TransactionDataAPI, TransactionKind, + TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE, +}; +use test_cluster::TestClusterBuilder; + +#[tokio::test] +async fn test_cache() { + let network = TestClusterBuilder::new().build().await; + let client = network.wallet.get_client().await.unwrap(); + let keystore = &network.wallet.config.keystore; + let rgp = network.get_reference_gas_price().await; + + // Test publish + let addresses = network.get_addresses(); + let sender = addresses[0]; + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.extend(["..", "..", "examples", "move", "coin"]); + let compiled_package = BuildConfig::new_for_testing().build(&path).unwrap(); + let compiled_modules_bytes = + compiled_package.get_package_bytes(/* with_unpublished_deps */ false); + let dependencies = compiled_package.get_dependency_storage_package_ids(); + + let pt = { + let mut builder = ProgrammableTransactionBuilder::new(); + builder.publish_immutable(compiled_modules_bytes, dependencies); + builder.finish() + }; + let input_objects = pt + .input_objects() + .unwrap_or_default() + .iter() + .flat_map(|obj| { + if let InputObjectKind::ImmOrOwnedMoveObject((id, ..)) = obj { + Some(*id) + } else { + None + } + }) + .collect::>(); + let gas = vec![get_random_sui(&client, sender, input_objects).await]; + let data = TransactionData::new_with_gas_coins( + TransactionKind::programmable(pt.clone()), + sender, + gas, + rgp * TEST_ONLY_GAS_UNIT_FOR_HEAVY_COMPUTATION_STORAGE, + rgp, + ); + + let signature = keystore + .sign_secure(&data.sender(), &data, Intent::sui_transaction()) + .unwrap(); + let response = client + .quorum_driver_api() + .execute_transaction_block( + Transaction::from_data(data.clone(), vec![signature]), + SuiTransactionBlockResponseOptions::full_content(), + Some(ExecuteTransactionRequestType::WaitForLocalExecution), + ) + .await + .map_err(|e| anyhow!("TX execution failed for {data:#?}, error : {e}")) + .unwrap(); + let object_changes = response.object_changes.unwrap(); + let my_coin_type = object_changes + .into_iter() + .find_map(|change| { + if let ObjectChange::Created { object_type, .. } = change { + if object_type.to_string().contains("2::coin::TreasuryCap") { + let coin_tag = object_type.type_params.into_iter().next().unwrap(); + return Some(coin_tag); + } + } + None + }) + .unwrap(); + + let coin_cache = CoinMetadataCache::new(client.clone(), NonZeroUsize::new(1).unwrap()); + + assert_eq!(0, coin_cache.metadata.lock().await.len()); + + let _ = coin_cache.get_currency(&GAS::type_tag()).await; + + assert_eq!(1, coin_cache.metadata.lock().await.len()); + assert!(coin_cache.metadata.lock().await.contains(&GAS::type_tag())); + assert!(!coin_cache.metadata.lock().await.contains(&my_coin_type)); + + let _ = coin_cache.get_currency(&my_coin_type).await; + + assert_eq!(1, coin_cache.metadata.lock().await.len()); + assert!(coin_cache.metadata.lock().await.contains(&my_coin_type)); + assert!(!coin_cache.metadata.lock().await.contains(&GAS::type_tag())); +} + +async fn get_random_sui( + client: &SuiClient, + sender: SuiAddress, + except: Vec, +) -> ObjectRef { + let coins = client + .read_api() + .get_owned_objects( + sender, + Some(SuiObjectResponseQuery::new_with_options( + SuiObjectDataOptions::new() + .with_type() + .with_owner() + .with_previous_transaction(), + )), + /* cursor */ None, + /* limit */ None, + ) + .await + .unwrap() + .data; + + let coin_resp = coins + .iter() + .filter(|object| { + let obj = object.object().unwrap(); + obj.is_gas_coin() && !except.contains(&obj.object_id) + }) + .choose(&mut OsRng) + .unwrap(); + + let coin = coin_resp.object().unwrap(); + (coin.object_id, coin.version, coin.digest) +} diff --git a/crates/sui-rosetta/src/unit_tests/types_tests.rs b/crates/sui-rosetta/src/unit_tests/types_tests.rs index 72b66b986c309..d55aa6cd1d307 100644 --- a/crates/sui-rosetta/src/unit_tests/types_tests.rs +++ b/crates/sui-rosetta/src/unit_tests/types_tests.rs @@ -1,7 +1,12 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use crate::types::{AccountBalanceRequest, Amount, Currency, CurrencyMetadata}; +use crate::types::{ + AccountBalanceRequest, Amount, ConstructionMetadata, Currency, CurrencyMetadata, +}; +use quick_js::Context; +use serde::{Deserialize, Serialize}; use serde_json::json; +use sui_types::base_types::{ObjectRef, SuiAddress}; #[tokio::test] async fn test_currency_defaults() { @@ -65,3 +70,56 @@ async fn test_currency_defaults() { account_balance_request.currencies.0.clone().pop().unwrap() ); } + +#[tokio::test] +async fn test_metadata_total_coin_value_js_conversion_for_large_balance() { + #[derive(Serialize, Deserialize, Debug)] + pub struct TestConstructionMetadata { + pub sender: SuiAddress, + pub coins: Vec, + pub objects: Vec, + pub total_coin_value: u64, + pub gas_price: u64, + pub budget: u64, + pub currency: Option, + } + + let test_metadata = TestConstructionMetadata { + sender: Default::default(), + coins: vec![], + objects: vec![], + total_coin_value: 65_000_004_233_578_496, + gas_price: 0, + budget: 0, + currency: None, + }; + let test_metadata_json = serde_json::to_string(&test_metadata).unwrap(); + + let prod_metadata = ConstructionMetadata { + sender: Default::default(), + coins: vec![], + objects: vec![], + total_coin_value: 65_000_004_233_578_496, + gas_price: 0, + budget: 0, + currency: None, + }; + let prod_metadata_json = serde_json::to_string(&prod_metadata).unwrap(); + + let context = Context::new().unwrap(); + + let test_total_coin_value = format!( + "JSON.parse({:?}).total_coin_value.toString()", + test_metadata_json + ); + let js_test_total_coin_value = context.eval_as::(&test_total_coin_value).unwrap(); + + let prod_total_coin_value = format!( + "JSON.parse({:?}).total_coin_value.toString()", + prod_metadata_json + ); + let js_prod_total_coin_value = context.eval_as::(&prod_total_coin_value).unwrap(); + + assert_eq!("65000004233578500", js_test_total_coin_value); + assert_eq!("65000004233578496", js_prod_total_coin_value); +} diff --git a/crates/sui-rosetta/tests/custom_coins/test_coin_no_symbol/Move.toml b/crates/sui-rosetta/tests/custom_coins/test_coin_no_symbol/Move.toml new file mode 100644 index 0000000000000..3f5e73de1f5c7 --- /dev/null +++ b/crates/sui-rosetta/tests/custom_coins/test_coin_no_symbol/Move.toml @@ -0,0 +1,9 @@ +[package] +name = "test_coin" +edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move + +[dependencies] +Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "framework/testnet" } + +[addresses] +test_coin = "0x0" \ No newline at end of file diff --git a/crates/sui-rosetta/tests/custom_coins/test_coin_no_symbol/sources/test_coin.move b/crates/sui-rosetta/tests/custom_coins/test_coin_no_symbol/sources/test_coin.move new file mode 100644 index 0000000000000..715857a7245da --- /dev/null +++ b/crates/sui-rosetta/tests/custom_coins/test_coin_no_symbol/sources/test_coin.move @@ -0,0 +1,24 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// Module: test_coin +module test_coin::test_coin { + + use sui::coin; + + public struct TEST_COIN has drop {} + + fun init(witness: TEST_COIN, ctx: &mut TxContext) { + let (treasury, metadata) = coin::create_currency( + witness, + 6, + b"", + b"", + b"", + option::none(), + ctx + ); + transfer::public_freeze_object(metadata); + transfer::public_transfer(treasury, ctx.sender()) + } +} diff --git a/crates/sui-rosetta/tests/custom_coins/test_coin_utils.rs b/crates/sui-rosetta/tests/custom_coins/test_coin_utils.rs index c7e640b268fc1..13ba4aa714af6 100644 --- a/crates/sui-rosetta/tests/custom_coins/test_coin_utils.rs +++ b/crates/sui-rosetta/tests/custom_coins/test_coin_utils.rs @@ -131,8 +131,8 @@ pub async fn init_package( client: &SuiClient, keystore: &Keystore, sender: SuiAddress, + path: &Path, ) -> Result { - let path = Path::new("tests/custom_coins/test_coin"); let path_buf = base::reroot_path(Some(path))?; let move_build_config = MoveBuildConfig::default(); @@ -266,7 +266,14 @@ async fn test_mint() { let keystore = &test_cluster.wallet.config.keystore; let sender = test_cluster.get_address_0(); - let init_ret = init_package(&client, keystore, sender).await.unwrap(); + let init_ret = init_package( + &client, + keystore, + sender, + Path::new("tests/custom_coins/test_coin"), + ) + .await + .unwrap(); let address1 = test_cluster.get_address_1(); let address2 = test_cluster.get_address_2(); diff --git a/crates/sui-rosetta/tests/custom_coins_tests.rs b/crates/sui-rosetta/tests/custom_coins_tests.rs index ca991c40b66b4..77094e35e9d7d 100644 --- a/crates/sui-rosetta/tests/custom_coins_tests.rs +++ b/crates/sui-rosetta/tests/custom_coins_tests.rs @@ -7,15 +7,17 @@ mod rosetta_client; mod test_coin_utils; use serde_json::json; +use std::num::NonZeroUsize; +use std::path::Path; use sui_json_rpc_types::{ SuiExecutionStatus, SuiTransactionBlockEffectsAPI, SuiTransactionBlockResponseOptions, }; use sui_rosetta::operations::Operations; -use sui_rosetta::types::Currencies; use sui_rosetta::types::{ AccountBalanceRequest, AccountBalanceResponse, AccountIdentifier, Currency, CurrencyMetadata, NetworkIdentifier, SuiEnv, }; +use sui_rosetta::types::{Currencies, OperationType}; use sui_rosetta::CoinMetadataCache; use sui_rosetta::SUI; use test_cluster::TestClusterBuilder; @@ -36,7 +38,14 @@ async fn test_custom_coin_balance() { let (rosetta_client, _handle) = start_rosetta_test_server(client.clone()).await; let sender = test_cluster.get_address_0(); - let init_ret = init_package(&client, keystore, sender).await.unwrap(); + let init_ret = init_package( + &client, + keystore, + sender, + Path::new("tests/custom_coins/test_coin"), + ) + .await + .unwrap(); let address1 = test_cluster.get_address_1(); let address2 = test_cluster.get_address_2(); @@ -159,7 +168,14 @@ async fn test_custom_coin_transfer() { let keystore = &test_cluster.wallet.config.keystore; // TEST_COIN setup and mint - let init_ret = init_package(&client, keystore, sender).await.unwrap(); + let init_ret = init_package( + &client, + keystore, + sender, + Path::new("tests/custom_coins/test_coin"), + ) + .await + .unwrap(); let balances_to = vec![(COIN1_BALANCE, sender)]; let coin_type = init_ret.coin_tag.to_canonical_string(true); let _mint_res = mint(&client, keystore, init_ret, balances_to) @@ -222,7 +238,7 @@ async fn test_custom_coin_transfer() { tx.effects.as_ref().unwrap().status() ); println!("Sui TX: {tx:?}"); - let coin_cache = CoinMetadataCache::new(client); + let coin_cache = CoinMetadataCache::new(client, NonZeroUsize::new(2).unwrap()); let ops2 = Operations::try_from_response(tx, &coin_cache) .await .unwrap(); @@ -233,3 +249,55 @@ async fn test_custom_coin_transfer() { serde_json::to_string(&ops2).unwrap() ); } + +#[tokio::test] +async fn test_custom_coin_without_symbol() { + const COIN1_BALANCE: u64 = 100_000_000_000_000_000; + let test_cluster = TestClusterBuilder::new().build().await; + let sender = test_cluster.get_address_0(); + let client = test_cluster.wallet.get_client().await.unwrap(); + let keystore = &test_cluster.wallet.config.keystore; + + // TEST_COIN setup and mint + let init_ret = init_package( + &client, + keystore, + sender, + Path::new("tests/custom_coins/test_coin_no_symbol"), + ) + .await + .unwrap(); + + let balances_to = vec![(COIN1_BALANCE, sender)]; + let mint_res = mint(&client, keystore, init_ret, balances_to) + .await + .unwrap(); + + let tx = client + .read_api() + .get_transaction_with_options( + mint_res.digest, + SuiTransactionBlockResponseOptions::new() + .with_input() + .with_effects() + .with_balance_changes() + .with_events(), + ) + .await + .unwrap(); + + assert_eq!( + &SuiExecutionStatus::Success, + tx.effects.as_ref().unwrap().status() + ); + let coin_cache = CoinMetadataCache::new(client, NonZeroUsize::new(2).unwrap()); + let ops = Operations::try_from_response(tx, &coin_cache) + .await + .unwrap(); + + for op in ops { + if op.type_ == OperationType::SuiBalanceChange { + assert!(!op.amount.unwrap().currency.symbol.is_empty()) + } + } +} diff --git a/crates/sui-rosetta/tests/end_to_end_tests.rs b/crates/sui-rosetta/tests/end_to_end_tests.rs index d18fad95bd297..6150372626237 100644 --- a/crates/sui-rosetta/tests/end_to_end_tests.rs +++ b/crates/sui-rosetta/tests/end_to_end_tests.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use serde_json::json; +use std::num::NonZeroUsize; use std::time::Duration; use rosetta_client::start_rosetta_test_server; @@ -175,8 +176,10 @@ async fn test_stake() { tx.effects.as_ref().unwrap().status() ); - let con_cache = CoinMetadataCache::new(client); - let ops2 = Operations::try_from_response(tx, &con_cache).await.unwrap(); + let coin_cache = CoinMetadataCache::new(client, NonZeroUsize::new(2).unwrap()); + let ops2 = Operations::try_from_response(tx, &coin_cache) + .await + .unwrap(); assert!( ops2.contains(&ops), "Operation mismatch. expecting:{}, got:{}", @@ -236,7 +239,7 @@ async fn test_stake_all() { tx.effects.as_ref().unwrap().status() ); - let coin_cache = CoinMetadataCache::new(client); + let coin_cache = CoinMetadataCache::new(client, NonZeroUsize::new(2).unwrap()); let ops2 = Operations::try_from_response(tx, &coin_cache) .await .unwrap(); @@ -250,8 +253,6 @@ async fn test_stake_all() { println!("{}", serde_json::to_string_pretty(&ops2).unwrap()) } -/* - //TOCHECK #[tokio::test] async fn test_withdraw_stake() { telemetry_subscribers::init_for_testing(); @@ -356,7 +357,7 @@ async fn test_withdraw_stake() { tx.effects.as_ref().unwrap().status() ); println!("Sui TX: {tx:?}"); - let coin_cache = CoinMetadataCache::new(client); + let coin_cache = CoinMetadataCache::new(client, NonZeroUsize::new(2).unwrap()); let ops2 = Operations::try_from_response(tx, &coin_cache) .await .unwrap(); @@ -381,7 +382,6 @@ async fn test_withdraw_stake() { assert_eq!(1, response.balances.len()); assert_eq!(0, response.balances[0].value); } -*/ #[tokio::test] async fn test_pay_sui() { @@ -428,7 +428,7 @@ async fn test_pay_sui() { tx.effects.as_ref().unwrap().status() ); println!("Sui TX: {tx:?}"); - let coin_cache = CoinMetadataCache::new(client); + let coin_cache = CoinMetadataCache::new(client, NonZeroUsize::new(2).unwrap()); let ops2 = Operations::try_from_response(tx, &coin_cache) .await .unwrap(); @@ -452,7 +452,7 @@ async fn test_pay_sui_multiple_times() { let keystore = &test_cluster.wallet.config.keystore; let (rosetta_client, _handle) = start_rosetta_test_server(client.clone()).await; - let coin_cache = CoinMetadataCache::new(client.clone()); + let coin_cache = CoinMetadataCache::new(client.clone(), NonZeroUsize::new(2).unwrap()); for i in 1..20 { println!("Iteration: {}", i); diff --git a/crates/sui-sdk/examples/event_api.rs b/crates/sui-sdk/examples/event_api.rs index df66154ba7afb..10d577310a2a3 100644 --- a/crates/sui-sdk/examples/event_api.rs +++ b/crates/sui-sdk/examples/event_api.rs @@ -27,7 +27,7 @@ async fn main() -> Result<(), anyhow::Error> { let descending = true; let query_events = sui .event_api() - .query_events(EventFilter::All(vec![]), None, Some(5), descending) // query first 5 events in descending order + .query_events(EventFilter::All([]), None, Some(5), descending) // query first 5 events in descending order .await?; println!(" *** Query events *** "); println!("{:?}", query_events); @@ -39,10 +39,7 @@ async fn main() -> Result<(), anyhow::Error> { .await?; println!("WS version {:?}", ws.api_version()); - let mut subscribe = ws - .event_api() - .subscribe_event(EventFilter::All(vec![])) - .await?; + let mut subscribe = ws.event_api().subscribe_event(EventFilter::All([])).await?; loop { println!("{:?}", subscribe.next().await); diff --git a/crates/sui-sdk/src/apis.rs b/crates/sui-sdk/src/apis.rs index a74e139a54836..ed9f6b8bcd0cb 100644 --- a/crates/sui-sdk/src/apis.rs +++ b/crates/sui-sdk/src/apis.rs @@ -1033,7 +1033,7 @@ impl EventApi { /// .await?; /// let mut subscribe_all = sui /// .event_api() - /// .subscribe_event(EventFilter::All(vec![])) + /// .subscribe_event(EventFilter::All([])) /// .await?; /// loop { /// println!("{:?}", subscribe_all.next().await); diff --git a/crates/sui-snapshot/src/reader.rs b/crates/sui-snapshot/src/reader.rs index 8e15b9e4c208b..bdb4cd35f989c 100644 --- a/crates/sui-snapshot/src/reader.rs +++ b/crates/sui-snapshot/src/reader.rs @@ -522,7 +522,7 @@ pub async fn download_bytes( part_num: &u32, max_timeout_secs: Option, ) -> (Bytes, [u8; 32]) { - let max_timeout = Duration::from_secs(max_timeout_secs.unwrap_or(30)); + let max_timeout = Duration::from_secs(max_timeout_secs.unwrap_or(60)); let mut timeout = Duration::from_secs(2); timeout += timeout / 2; timeout = std::cmp::min(max_timeout, timeout); diff --git a/crates/sui-source-validation/src/toolchain.rs b/crates/sui-source-validation/src/toolchain.rs index 54f87f2e1b83a..360ac76958b02 100644 --- a/crates/sui-source-validation/src/toolchain.rs +++ b/crates/sui-source-validation/src/toolchain.rs @@ -40,7 +40,7 @@ use tracing::{debug, info}; pub(crate) const CURRENT_COMPILER_VERSION: &str = env!("CARGO_PKG_VERSION"); const LEGACY_COMPILER_VERSION: &str = CURRENT_COMPILER_VERSION; // TODO: update this when Move 2024 is released -const PRE_TOOLCHAIN_MOVE_LOCK_VERSION: u64 = 0; // Used to detect lockfiles pre-toolchain versioning support +const PRE_TOOLCHAIN_MOVE_LOCK_VERSION: u16 = 0; // Used to detect lockfiles pre-toolchain versioning support const CANONICAL_UNIX_BINARY_NAME: &str = "sui"; const CANONICAL_WIN_BINARY_NAME: &str = "sui.exe"; @@ -249,9 +249,9 @@ fn download_and_compile( let mut dest_binary = dest_version.clone(); dest_binary.extend(["target", "release"]); if platform == "windows-x86_64" { - dest_binary.push(&format!("sui-{platform}.exe")); + dest_binary.push(format!("sui-{platform}.exe")); } else { - dest_binary.push(&format!("sui-{platform}")); + dest_binary.push(format!("sui-{platform}")); } let dest_binary_os = OsStr::new(dest_binary.as_path()); set_executable_permission(dest_binary_os)?; diff --git a/crates/sui-storage/src/http_key_value_store.rs b/crates/sui-storage/src/http_key_value_store.rs index deabb0789081f..5fc9dcc3128ba 100644 --- a/crates/sui-storage/src/http_key_value_store.rs +++ b/crates/sui-storage/src/http_key_value_store.rs @@ -26,7 +26,7 @@ use sui_types::{ }, transaction::Transaction, }; -use tap::TapFallible; +use tap::{TapFallible, TapOptional}; use tracing::{error, info, instrument, trace, warn}; use crate::key_value_store::{TransactionKeyValueStore, TransactionKeyValueStoreTrait}; @@ -86,6 +86,45 @@ pub enum Key { ObjectKey(ObjectID, VersionNumber), } +impl Key { + /// Return a string representation of the key type + pub fn ty(&self) -> &'static str { + match self { + Key::Tx(_) => "tx", + Key::Fx(_) => "fx", + Key::Events(_) => "ev", + Key::CheckpointContents(_) => "cc", + Key::CheckpointSummary(_) => "cs", + Key::CheckpointContentsByDigest(_) => "cc", + Key::CheckpointSummaryByDigest(_) => "cs", + Key::TxToCheckpoint(_) => "tx2c", + Key::ObjectKey(_, _) => "ob", + } + } + + pub fn encode(&self) -> String { + match self { + Key::Tx(digest) => encode_digest(digest), + Key::Fx(digest) => encode_digest(digest), + Key::Events(digest) => encode_digest(digest), + Key::CheckpointContents(seq) => { + encoded_tagged_key(&TaggedKey::CheckpointSequenceNumber(*seq)) + } + Key::CheckpointSummary(seq) => { + encoded_tagged_key(&TaggedKey::CheckpointSequenceNumber(*seq)) + } + Key::CheckpointContentsByDigest(digest) => encode_digest(digest), + Key::CheckpointSummaryByDigest(digest) => encode_digest(digest), + Key::TxToCheckpoint(digest) => encode_digest(digest), + Key::ObjectKey(object_id, version) => encode_object_key(object_id, version), + } + } + + pub fn to_path_elements(&self) -> (String, &'static str) { + (self.encode(), self.ty()) + } +} + #[derive(Clone, Debug)] enum Value { Tx(Box), @@ -96,26 +135,6 @@ enum Value { TxToCheckpoint(CheckpointSequenceNumber), } -pub fn key_to_path_elements(key: &Key) -> SuiResult<(String, &'static str)> { - match key { - Key::Tx(digest) => Ok((encode_digest(digest), "tx")), - Key::Fx(digest) => Ok((encode_digest(digest), "fx")), - Key::Events(digest) => Ok((encode_digest(digest), "ev")), - Key::CheckpointContents(seq) => Ok(( - encoded_tagged_key(&TaggedKey::CheckpointSequenceNumber(*seq)), - "cc", - )), - Key::CheckpointSummary(seq) => Ok(( - encoded_tagged_key(&TaggedKey::CheckpointSequenceNumber(*seq)), - "cs", - )), - Key::CheckpointContentsByDigest(digest) => Ok((encode_digest(digest), "cc")), - Key::CheckpointSummaryByDigest(digest) => Ok((encode_digest(digest), "cs")), - Key::TxToCheckpoint(digest) => Ok((encode_digest(digest), "tx2c")), - Key::ObjectKey(object_id, version) => Ok((encode_object_key(object_id, version), "ob")), - } -} - pub fn path_elements_to_key(digest: &str, type_: &str) -> anyhow::Result { let decoded_digest = base64_url::decode(digest)?; @@ -202,7 +221,7 @@ impl HttpKVStore { } fn get_url(&self, key: &Key) -> SuiResult { - let (digest, item_type) = key_to_path_elements(key)?; + let (digest, item_type) = key.to_path_elements(); let joined = self .base_url .join(&format!("{}/{}", digest, item_type)) @@ -225,14 +244,14 @@ impl HttpKVStore { trace!("found cached data for url: {}, len: {:?}", url, res.len()); self.metrics .key_value_store_num_fetches_success - .with_label_values(&["http_cache", "url"]) + .with_label_values(&["http_cache", key.ty()]) .inc(); return Ok(Some(res)); } self.metrics .key_value_store_num_fetches_not_found - .with_label_values(&["http_cache", "url"]) + .with_label_values(&["http_cache", key.ty()]) .inc(); let resp = self @@ -508,9 +527,22 @@ impl TransactionKeyValueStoreTrait for HttpKVStore { version: SequenceNumber, ) -> SuiResult> { let key = Key::ObjectKey(object_id, version); - self.fetch(key) - .await - .map(|maybe| maybe.and_then(|bytes| deser::<_, Object>(&key, bytes.as_ref()))) + self.fetch(key).await.map(|maybe| { + maybe + .and_then(|bytes| deser::<_, Object>(&key, bytes.as_ref())) + .tap_some(|_| { + self.metrics + .key_value_store_num_fetches_success + .with_label_values(&["http", key.ty()]) + .inc(); + }) + .tap_none(|| { + self.metrics + .key_value_store_num_fetches_not_found + .with_label_values(&["http", key.ty()]) + .inc(); + }) + }) } #[instrument(level = "trace", skip_all)] diff --git a/crates/sui-storage/src/lib.rs b/crates/sui-storage/src/lib.rs index 13a9fa8a6f2e5..fda7b7abb6bc5 100644 --- a/crates/sui-storage/src/lib.rs +++ b/crates/sui-storage/src/lib.rs @@ -2,15 +2,12 @@ // SPDX-License-Identifier: Apache-2.0 #![allow(dead_code)] -pub mod indexes; - use crate::blob::BlobIter; use anyhow::{anyhow, Result}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use bytes::{Buf, Bytes}; use fastcrypto::hash::{HashFunction, Sha3_256}; use futures::StreamExt; -pub use indexes::{IndexStore, IndexStoreTables}; use itertools::Itertools; use num_enum::{IntoPrimitive, TryFromPrimitive}; use serde::de::DeserializeOwned; diff --git a/crates/sui-storage/src/object_store/http/gcs.rs b/crates/sui-storage/src/object_store/http/gcs.rs index 09e8c587004da..372167e372b87 100644 --- a/crates/sui-storage/src/object_store/http/gcs.rs +++ b/crates/sui-storage/src/object_store/http/gcs.rs @@ -22,7 +22,7 @@ struct GoogleCloudStorageClient { impl GoogleCloudStorageClient { pub fn new(bucket: &str) -> Result { - let mut builder = ClientBuilder::new(); + let mut builder = ClientBuilder::new().pool_idle_timeout(None); builder = builder.user_agent(DEFAULT_USER_AGENT); let client = builder.https_only(false).build()?; let bucket_name_encoded = percent_encode(bucket.as_bytes(), NON_ALPHANUMERIC).to_string(); diff --git a/crates/sui-storage/src/object_store/http/s3.rs b/crates/sui-storage/src/object_store/http/s3.rs index 7489e91bdd8ba..1344ea431becf 100644 --- a/crates/sui-storage/src/object_store/http/s3.rs +++ b/crates/sui-storage/src/object_store/http/s3.rs @@ -23,7 +23,9 @@ pub(crate) struct S3Client { impl S3Client { pub fn new(endpoint: &str) -> Result { let mut builder = ClientBuilder::new(); - builder = builder.user_agent(DEFAULT_USER_AGENT); + builder = builder + .user_agent(DEFAULT_USER_AGENT) + .pool_idle_timeout(None); let client = builder.https_only(false).build()?; Ok(Self { diff --git a/crates/sui-storage/src/object_store/mod.rs b/crates/sui-storage/src/object_store/mod.rs index 1ebfa595af66c..e92f4000abbc1 100644 --- a/crates/sui-storage/src/object_store/mod.rs +++ b/crates/sui-storage/src/object_store/mod.rs @@ -36,10 +36,17 @@ as_ref_get_ext_impl!(Box); impl ObjectStoreGetExt for Arc { async fn get_bytes(&self, src: &Path) -> Result { self.get(src) - .await? + .await + .map_err(|e| anyhow!("Failed to get file {} with error: {:?}", src, e))? .bytes() .await - .map_err(|e| anyhow!("Failed to get file: {} with error: {}", src, e.to_string())) + .map_err(|e| { + anyhow!( + "Failed to collect GET result for file {} into bytes with error: {:?}", + src, + e + ) + }) } } diff --git a/crates/sui-storage/tests/key_value_tests.rs b/crates/sui-storage/tests/key_value_tests.rs index b880955deb36e..09e550283c113 100644 --- a/crates/sui-storage/tests/key_value_tests.rs +++ b/crates/sui-storage/tests/key_value_tests.rs @@ -560,7 +560,7 @@ mod simtests { assert_eq!( metrics .key_value_store_num_fetches_success - .get_metric_with_label_values(&["http_cache", "url"]) + .get_metric_with_label_values(&["http_cache", "tx"]) .unwrap() .get(), 1 @@ -575,14 +575,14 @@ mod simtests { fn test_key_to_path_and_back() { let tx = TransactionDigest::random(); let key = Key::Tx(tx); - let path_elts = key_to_path_elements(&key).unwrap(); + let path_elts = key.to_path_elements(); assert_eq!( path_elements_to_key(path_elts.0.as_str(), path_elts.1).unwrap(), key ); let key = Key::Fx(TransactionDigest::random()); - let path_elts = key_to_path_elements(&key).unwrap(); + let path_elts = key.to_path_elements(); assert_eq!( path_elements_to_key(path_elts.0.as_str(), path_elts.1).unwrap(), key @@ -590,21 +590,21 @@ fn test_key_to_path_and_back() { let events = TransactionEventsDigest::random(); let key = Key::Events(events); - let path_elts = key_to_path_elements(&key).unwrap(); + let path_elts = key.to_path_elements(); assert_eq!( path_elements_to_key(path_elts.0.as_str(), path_elts.1).unwrap(), key ); let key = Key::CheckpointSummary(42); - let path_elts = key_to_path_elements(&key).unwrap(); + let path_elts = key.to_path_elements(); assert_eq!( path_elements_to_key(path_elts.0.as_str(), path_elts.1).unwrap(), key ); let key = Key::CheckpointContents(42); - let path_elts = key_to_path_elements(&key).unwrap(); + let path_elts = key.to_path_elements(); assert_eq!( path_elements_to_key(path_elts.0.as_str(), path_elts.1).unwrap(), key @@ -612,7 +612,7 @@ fn test_key_to_path_and_back() { let ckpt_contents = CheckpointContentsDigest::random(); let key = Key::CheckpointContentsByDigest(ckpt_contents); - let path_elts = key_to_path_elements(&key).unwrap(); + let path_elts = key.to_path_elements(); assert_eq!( path_elements_to_key(path_elts.0.as_str(), path_elts.1).unwrap(), key @@ -620,21 +620,21 @@ fn test_key_to_path_and_back() { let ckpt_summary = CheckpointDigest::random(); let key = Key::CheckpointSummaryByDigest(ckpt_summary); - let path_elts = key_to_path_elements(&key).unwrap(); + let path_elts = key.to_path_elements(); assert_eq!( path_elements_to_key(path_elts.0.as_str(), path_elts.1).unwrap(), key ); let key = Key::TxToCheckpoint(tx); - let path_elts = key_to_path_elements(&key).unwrap(); + let path_elts = key.to_path_elements(); assert_eq!( path_elements_to_key(path_elts.0.as_str(), path_elts.1).unwrap(), key ); let key = Key::ObjectKey(ObjectID::random(), SequenceNumber::from_u64(42)); - let path_elts = key_to_path_elements(&key).unwrap(); + let path_elts = key.to_path_elements(); assert_eq!( path_elements_to_key(path_elts.0.as_str(), path_elts.1).unwrap(), key diff --git a/crates/sui-swarm-config/Cargo.toml b/crates/sui-swarm-config/Cargo.toml index 12a367ccbd6c4..3fa547a15b201 100644 --- a/crates/sui-swarm-config/Cargo.toml +++ b/crates/sui-swarm-config/Cargo.toml @@ -30,6 +30,7 @@ sui-macros.workspace = true sui-protocol-config.workspace = true sui-types.workspace = true sui-genesis-builder.workspace = true +sui-rest-api.workspace = true [target.'cfg(msim)'.dependencies] sui-simulator.workspace = true diff --git a/crates/sui-swarm-config/src/network_config_builder.rs b/crates/sui-swarm-config/src/network_config_builder.rs index a1ca29d1391a8..ad8547303b0e3 100644 --- a/crates/sui-swarm-config/src/network_config_builder.rs +++ b/crates/sui-swarm-config/src/network_config_builder.rs @@ -84,6 +84,8 @@ impl ConfigBuilder { rng: Some(OsRng), config_directory: config_directory.as_ref().into(), supported_protocol_versions_config: None, + // FIXME: A network with only 1 validator does not have liveness. + // We need to change this. There are some tests that depend on it though. committee: CommitteeConfig::Size(NonZeroUsize::new(1).unwrap()), genesis_config: None, reference_gas_price: None, diff --git a/crates/sui-swarm-config/src/node_config_builder.rs b/crates/sui-swarm-config/src/node_config_builder.rs index ebdcb01fbcfba..20c45387a7ded 100644 --- a/crates/sui-swarm-config/src/node_config_builder.rs +++ b/crates/sui-swarm-config/src/node_config_builder.rs @@ -227,6 +227,10 @@ impl ValidatorConfigBuilder { transaction_kv_store_read_config: Default::default(), transaction_kv_store_write_config: None, enable_experimental_rest_api: true, + rest: Some(sui_rest_api::Config { + enable_unstable_apis: Some(true), + ..Default::default() + }), jwk_fetch_interval_seconds: self .jwk_fetch_interval .map(|i| i.as_secs()) @@ -278,6 +282,7 @@ pub struct FullnodeConfigBuilder { policy_config: Option, fw_config: Option, data_ingestion_dir: Option, + disable_pruning: bool, } impl FullnodeConfigBuilder { @@ -312,6 +317,11 @@ impl FullnodeConfigBuilder { self } + pub fn with_disable_pruning(mut self, disable_pruning: bool) -> Self { + self.disable_pruning = disable_pruning; + self + } + pub fn with_expensive_safety_check_config( mut self, expensive_safety_check_config: ExpensiveSafetyCheckConfig, @@ -458,6 +468,12 @@ impl FullnodeConfigBuilder { ..Default::default() }; + let mut pruning_config = AuthorityStorePruningConfig::default(); + if self.disable_pruning { + pruning_config.set_num_epochs_to_retain_for_checkpoints(None); + pruning_config.set_num_epochs_to_retain(u64::MAX); + }; + NodeConfig { protocol_key_pair: AuthorityKeyPairWithPath::new(validator_config.key_pair), account_key_pair: KeyPairWithPath::new(validator_config.account_key_pair), @@ -489,7 +505,7 @@ impl FullnodeConfigBuilder { grpc_load_shed: None, grpc_concurrency_limit: None, p2p_config, - authority_store_pruning_config: AuthorityStorePruningConfig::default(), + authority_store_pruning_config: pruning_config, end_of_epoch_broadcast_channel_capacity: default_end_of_epoch_broadcast_channel_capacity(), checkpoint_executor_config, @@ -513,6 +529,10 @@ impl FullnodeConfigBuilder { transaction_kv_store_read_config: Default::default(), transaction_kv_store_write_config: Default::default(), enable_experimental_rest_api: true, + rest: Some(sui_rest_api::Config { + enable_unstable_apis: Some(true), + ..Default::default() + }), // note: not used by fullnodes. jwk_fetch_interval_seconds: 3600, zklogin_oauth_providers: default_zklogin_oauth_providers(), diff --git a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__genesis_config_snapshot_matches.snap b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__genesis_config_snapshot_matches.snap index 9f1b3aa9d6411..74ac933740527 100644 --- a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__genesis_config_snapshot_matches.snap +++ b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__genesis_config_snapshot_matches.snap @@ -6,7 +6,7 @@ ssfn_config_info: ~ validator_config_info: ~ parameters: chain_start_timestamp_ms: 0 - protocol_version: 60 + protocol_version: 65 allow_insertion_of_extra_objects: true epoch_duration_ms: 86400000 stake_subsidy_start_epoch: 0 diff --git a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__network_config_snapshot_matches.snap b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__network_config_snapshot_matches.snap index f0323dd19d1f2..8557a77990c4c 100644 --- a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__network_config_snapshot_matches.snap +++ b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__network_config_snapshot_matches.snap @@ -15,6 +15,8 @@ validator_configs: network-address: "" json-rpc-address: "0.0.0.0:1" enable-experimental-rest-api: true + rest: + enable-unstable-apis: true metrics-address: "0.0.0.0:1" admin-interface-port: 8888 consensus-config: @@ -100,6 +102,8 @@ validator_configs: zklogin-oauth-providers: Mainnet: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 - Facebook @@ -107,9 +111,12 @@ validator_configs: - KarrierOne - Onefc - Playtron + - Threedos - Twitch Testnet: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 - Facebook @@ -117,9 +124,12 @@ validator_configs: - KarrierOne - Onefc - Playtron + - Threedos - Twitch Unknown: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_LPSLCkC3A" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 @@ -137,8 +147,8 @@ validator_configs: - Twitch authority-overload-config: max-txn-age-in-queue: - secs: 1 - nanos: 0 + secs: 0 + nanos: 200000000 overload-monitor-interval: secs: 10 nanos: 0 @@ -153,8 +163,10 @@ validator_configs: safe-transaction-ready-rate: 100 check-system-overload-at-signing: true max-transaction-manager-queue-length: 100000 - max-transaction-manager-per-object-queue-length: 100 - execution-cache: passthrough-cache + max-transaction-manager-per-object-queue-length: 20 + execution-cache: + writeback-cache: + max_cache_size: ~ enable-soft-bundle: true enable-validator-tx-finalizer: true verifier-signing-config: @@ -175,6 +187,8 @@ validator_configs: network-address: "" json-rpc-address: "0.0.0.0:1" enable-experimental-rest-api: true + rest: + enable-unstable-apis: true metrics-address: "0.0.0.0:1" admin-interface-port: 8888 consensus-config: @@ -260,6 +274,8 @@ validator_configs: zklogin-oauth-providers: Mainnet: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 - Facebook @@ -267,9 +283,12 @@ validator_configs: - KarrierOne - Onefc - Playtron + - Threedos - Twitch Testnet: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 - Facebook @@ -277,9 +296,12 @@ validator_configs: - KarrierOne - Onefc - Playtron + - Threedos - Twitch Unknown: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_LPSLCkC3A" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 @@ -297,8 +319,8 @@ validator_configs: - Twitch authority-overload-config: max-txn-age-in-queue: - secs: 1 - nanos: 0 + secs: 0 + nanos: 200000000 overload-monitor-interval: secs: 10 nanos: 0 @@ -313,8 +335,10 @@ validator_configs: safe-transaction-ready-rate: 100 check-system-overload-at-signing: true max-transaction-manager-queue-length: 100000 - max-transaction-manager-per-object-queue-length: 100 - execution-cache: passthrough-cache + max-transaction-manager-per-object-queue-length: 20 + execution-cache: + writeback-cache: + max_cache_size: ~ enable-soft-bundle: true enable-validator-tx-finalizer: true verifier-signing-config: @@ -335,6 +359,8 @@ validator_configs: network-address: "" json-rpc-address: "0.0.0.0:1" enable-experimental-rest-api: true + rest: + enable-unstable-apis: true metrics-address: "0.0.0.0:1" admin-interface-port: 8888 consensus-config: @@ -420,6 +446,8 @@ validator_configs: zklogin-oauth-providers: Mainnet: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 - Facebook @@ -427,9 +455,12 @@ validator_configs: - KarrierOne - Onefc - Playtron + - Threedos - Twitch Testnet: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 - Facebook @@ -437,9 +468,12 @@ validator_configs: - KarrierOne - Onefc - Playtron + - Threedos - Twitch Unknown: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_LPSLCkC3A" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 @@ -457,8 +491,8 @@ validator_configs: - Twitch authority-overload-config: max-txn-age-in-queue: - secs: 1 - nanos: 0 + secs: 0 + nanos: 200000000 overload-monitor-interval: secs: 10 nanos: 0 @@ -473,8 +507,10 @@ validator_configs: safe-transaction-ready-rate: 100 check-system-overload-at-signing: true max-transaction-manager-queue-length: 100000 - max-transaction-manager-per-object-queue-length: 100 - execution-cache: passthrough-cache + max-transaction-manager-per-object-queue-length: 20 + execution-cache: + writeback-cache: + max_cache_size: ~ enable-soft-bundle: true enable-validator-tx-finalizer: true verifier-signing-config: @@ -495,6 +531,8 @@ validator_configs: network-address: "" json-rpc-address: "0.0.0.0:1" enable-experimental-rest-api: true + rest: + enable-unstable-apis: true metrics-address: "0.0.0.0:1" admin-interface-port: 8888 consensus-config: @@ -580,6 +618,8 @@ validator_configs: zklogin-oauth-providers: Mainnet: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 - Facebook @@ -587,9 +627,12 @@ validator_configs: - KarrierOne - Onefc - Playtron + - Threedos - Twitch Testnet: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 - Facebook @@ -597,9 +640,12 @@ validator_configs: - KarrierOne - Onefc - Playtron + - Threedos - Twitch Unknown: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_LPSLCkC3A" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 @@ -617,8 +663,8 @@ validator_configs: - Twitch authority-overload-config: max-txn-age-in-queue: - secs: 1 - nanos: 0 + secs: 0 + nanos: 200000000 overload-monitor-interval: secs: 10 nanos: 0 @@ -633,8 +679,10 @@ validator_configs: safe-transaction-ready-rate: 100 check-system-overload-at-signing: true max-transaction-manager-queue-length: 100000 - max-transaction-manager-per-object-queue-length: 100 - execution-cache: passthrough-cache + max-transaction-manager-per-object-queue-length: 20 + execution-cache: + writeback-cache: + max_cache_size: ~ enable-soft-bundle: true enable-validator-tx-finalizer: true verifier-signing-config: @@ -655,6 +703,8 @@ validator_configs: network-address: "" json-rpc-address: "0.0.0.0:1" enable-experimental-rest-api: true + rest: + enable-unstable-apis: true metrics-address: "0.0.0.0:1" admin-interface-port: 8888 consensus-config: @@ -740,6 +790,8 @@ validator_configs: zklogin-oauth-providers: Mainnet: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 - Facebook @@ -747,9 +799,12 @@ validator_configs: - KarrierOne - Onefc - Playtron + - Threedos - Twitch Testnet: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 - Facebook @@ -757,9 +812,12 @@ validator_configs: - KarrierOne - Onefc - Playtron + - Threedos - Twitch Unknown: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_LPSLCkC3A" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 @@ -777,8 +835,8 @@ validator_configs: - Twitch authority-overload-config: max-txn-age-in-queue: - secs: 1 - nanos: 0 + secs: 0 + nanos: 200000000 overload-monitor-interval: secs: 10 nanos: 0 @@ -793,8 +851,10 @@ validator_configs: safe-transaction-ready-rate: 100 check-system-overload-at-signing: true max-transaction-manager-queue-length: 100000 - max-transaction-manager-per-object-queue-length: 100 - execution-cache: passthrough-cache + max-transaction-manager-per-object-queue-length: 20 + execution-cache: + writeback-cache: + max_cache_size: ~ enable-soft-bundle: true enable-validator-tx-finalizer: true verifier-signing-config: @@ -815,6 +875,8 @@ validator_configs: network-address: "" json-rpc-address: "0.0.0.0:1" enable-experimental-rest-api: true + rest: + enable-unstable-apis: true metrics-address: "0.0.0.0:1" admin-interface-port: 8888 consensus-config: @@ -900,6 +962,8 @@ validator_configs: zklogin-oauth-providers: Mainnet: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 - Facebook @@ -907,9 +971,12 @@ validator_configs: - KarrierOne - Onefc - Playtron + - Threedos - Twitch Testnet: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 - Facebook @@ -917,9 +984,12 @@ validator_configs: - KarrierOne - Onefc - Playtron + - Threedos - Twitch Unknown: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_LPSLCkC3A" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 @@ -937,8 +1007,8 @@ validator_configs: - Twitch authority-overload-config: max-txn-age-in-queue: - secs: 1 - nanos: 0 + secs: 0 + nanos: 200000000 overload-monitor-interval: secs: 10 nanos: 0 @@ -953,8 +1023,10 @@ validator_configs: safe-transaction-ready-rate: 100 check-system-overload-at-signing: true max-transaction-manager-queue-length: 100000 - max-transaction-manager-per-object-queue-length: 100 - execution-cache: passthrough-cache + max-transaction-manager-per-object-queue-length: 20 + execution-cache: + writeback-cache: + max_cache_size: ~ enable-soft-bundle: true enable-validator-tx-finalizer: true verifier-signing-config: @@ -975,6 +1047,8 @@ validator_configs: network-address: "" json-rpc-address: "0.0.0.0:1" enable-experimental-rest-api: true + rest: + enable-unstable-apis: true metrics-address: "0.0.0.0:1" admin-interface-port: 8888 consensus-config: @@ -1060,6 +1134,8 @@ validator_configs: zklogin-oauth-providers: Mainnet: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 - Facebook @@ -1067,9 +1143,12 @@ validator_configs: - KarrierOne - Onefc - Playtron + - Threedos - Twitch Testnet: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 - Facebook @@ -1077,9 +1156,12 @@ validator_configs: - KarrierOne - Onefc - Playtron + - Threedos - Twitch Unknown: - Apple + - Arden + - "AwsTenant-region:eu-west-3-tenant_id:eu-west-3_gGVCx53Es" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_LPSLCkC3A" - "AwsTenant-region:us-east-1-tenant_id:us-east-1_qPsZxYqd8" - Credenza3 @@ -1097,8 +1179,8 @@ validator_configs: - Twitch authority-overload-config: max-txn-age-in-queue: - secs: 1 - nanos: 0 + secs: 0 + nanos: 200000000 overload-monitor-interval: secs: 10 nanos: 0 @@ -1113,8 +1195,10 @@ validator_configs: safe-transaction-ready-rate: 100 check-system-overload-at-signing: true max-transaction-manager-queue-length: 100000 - max-transaction-manager-per-object-queue-length: 100 - execution-cache: passthrough-cache + max-transaction-manager-per-object-queue-length: 20 + execution-cache: + writeback-cache: + max_cache_size: ~ enable-soft-bundle: true enable-validator-tx-finalizer: true verifier-signing-config: diff --git a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap index 03e1da1191b18..d36c1fad5d370 100644 --- a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap +++ b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap @@ -3,7 +3,7 @@ source: crates/sui-swarm-config/tests/snapshot_tests.rs expression: genesis.sui_system_object().into_genesis_version_for_tooling() --- epoch: 0 -protocol_version: 60 +protocol_version: 65 system_state_version: 1 validators: total_stake: 20000000000000000 @@ -240,13 +240,13 @@ validators: next_epoch_worker_address: ~ extra_fields: id: - id: "0x0cdb59bd57ec62ffd1886d2c74b0f133859ef8cafabbb06490d7ccaad72704b3" + id: "0x834dab6f0617450d0dcfc6dd58e2a918d439fe50d2f914ac6e60dbbc38328ad3" size: 0 voting_power: 10000 - operation_cap_id: "0x882402178f68f1bb8fa326d71c102852501253ec847532084649406bd27cafd2" + operation_cap_id: "0x72b130e2d852f3468d46e67179268cf2b1a000855a549b0dcab002426836a768" gas_price: 1000 staking_pool: - id: "0x098aeb5c5b84ab5376c27bcc662d5fbeeff135fdd1166656d81e5c9f600842a1" + id: "0xdc1b1962050243cbe5efd781c560b88d1c4d43da28ddc1b3f1b558210ca24009" activation_epoch: 0 deactivation_epoch: ~ sui_balance: 20000000000000000 @@ -254,14 +254,14 @@ validators: value: 0 pool_token_balance: 20000000000000000 exchange_rates: - id: "0x6538c7f4a3f65ed77ec21e5f7b391fd5ff58ea13631ecfffb16f1c8ccc72806b" + id: "0xb972b09a2f5914997289ba4ebbff641d7f0a3faae622ee29997c1f6713fe7e78" size: 1 pending_stake: 0 pending_total_sui_withdraw: 0 pending_pool_token_withdraw: 0 extra_fields: id: - id: "0xc14811e3e18d269abc6e3302c6b484aa3d48523241238a3b7a0e15eb45c12172" + id: "0xc6dec0733287765e9f8600169f776566db59a0f6cb1a854de1865db22cda913d" size: 0 commission_rate: 200 next_epoch_stake: 20000000000000000 @@ -269,27 +269,27 @@ validators: next_epoch_commission_rate: 200 extra_fields: id: - id: "0x36bccda14a517940ca6410ad0a7b48f64d6fad6101c4d26c25cbf4937a533aac" + id: "0xe6c77a880c82d4f3e1b8a5d503f3a8e88881de8c7b722e66569ff35f8f505d29" size: 0 pending_active_validators: contents: - id: "0xf6a36c6124d6ed74d8d9db39cb96b4cf36688563a420d340bed6015dadab6932" + id: "0xb84831d86c7697202c857ede215fb5739e4c68e1aee6051efb99496833578d22" size: 0 pending_removals: [] staking_pool_mappings: - id: "0xd8f16068f1d4e8bdaae0e34b63e955243c11affb4c75f5ce4bc0bba9538fa899" + id: "0xb892dd544e8efe2b3c9c89be5689a297ca4ef59804308a81b11c1d89b90f6e18" size: 1 inactive_validators: - id: "0xf8e063e7121cfe05350887be73b7d2b2bfdfd4e2f51583565f06c46b0fe14b99" + id: "0xe285cf22b5d3c56a32961398e8f64a9f4282eb94782aef9080d9a6954e85c7d5" size: 0 validator_candidates: - id: "0x10a6ecdb1d3718003efbbc43256d7912150d201b0c51a0e52ce0298658d48a39" + id: "0x207f4b15b8cd26b0af90e308b677c2589bd914280198b2e8e8528a37f7240c35" size: 0 at_risk_validators: contents: [] extra_fields: id: - id: "0x15d36de0cdc373e3f14de7f2b38ab83e19d89fd7c67a9d17e289835b6cc29227" + id: "0x41921a36773858d7ea5e092810acf3e1ecbd5927a34ec4f460a2988390a57969" size: 0 storage_fund: total_object_storage_rebates: @@ -306,7 +306,7 @@ parameters: validator_low_stake_grace_period: 7 extra_fields: id: - id: "0x16556a79e5b11a4e143d3746b02755461fac9bf51bbbf1bcd741ee23dd7056a0" + id: "0xe96139872f584b831f86b074cf24c6158f23dd472df821c8b75a5777463d1c3d" size: 0 reference_gas_price: 1000 validator_report_records: @@ -320,7 +320,7 @@ stake_subsidy: stake_subsidy_decrease_rate: 1000 extra_fields: id: - id: "0xeac63720f1d2653149631374110574691bf087d4111dab217ac5de88225294b9" + id: "0xe1172cf766a6e4d4fb8d0a228d794e097462e114626bdedce942087b1c029965" size: 0 safe_mode: false safe_mode_storage_rewards: @@ -332,5 +332,5 @@ safe_mode_non_refundable_storage_fee: 0 epoch_start_timestamp_ms: 10 extra_fields: id: - id: "0xe61333df374e35d3b01680fb57e860b33b45d3d699bc6dc2a61b975f882c4720" + id: "0x531d74b5c7080de67c235dd165095164784ab991a92932bc878c60eaf4fa2a3d" size: 0 diff --git a/crates/sui-swarm/src/memory/swarm.rs b/crates/sui-swarm/src/memory/swarm.rs index 5c0df441d2a03..e3cd089495a3c 100644 --- a/crates/sui-swarm/src/memory/swarm.rs +++ b/crates/sui-swarm/src/memory/swarm.rs @@ -58,6 +58,7 @@ pub struct SwarmBuilder { max_submit_position: Option, submit_delay_step_override_millis: Option, state_accumulator_v2_enabled_config: StateAccumulatorV2EnabledConfig, + disable_fullnode_pruning: bool, } impl SwarmBuilder { @@ -86,6 +87,7 @@ impl SwarmBuilder { max_submit_position: None, submit_delay_step_override_millis: None, state_accumulator_v2_enabled_config: StateAccumulatorV2EnabledConfig::Global(true), + disable_fullnode_pruning: false, } } } @@ -116,6 +118,7 @@ impl SwarmBuilder { max_submit_position: self.max_submit_position, submit_delay_step_override_millis: self.submit_delay_step_override_millis, state_accumulator_v2_enabled_config: self.state_accumulator_v2_enabled_config, + disable_fullnode_pruning: self.disable_fullnode_pruning, } } @@ -289,6 +292,11 @@ impl SwarmBuilder { self } + pub fn with_disable_fullnode_pruning(mut self) -> Self { + self.disable_fullnode_pruning = true; + self + } + pub fn with_submit_delay_step_override_millis( mut self, submit_delay_step_override_millis: u64, @@ -375,7 +383,8 @@ impl SwarmBuilder { .with_run_with_range(self.fullnode_run_with_range) .with_policy_config(self.fullnode_policy_config) .with_data_ingestion_dir(ingest_data) - .with_fw_config(self.fullnode_fw_config); + .with_fw_config(self.fullnode_fw_config) + .with_disable_pruning(self.disable_fullnode_pruning); if let Some(spvc) = &self.fullnode_supported_protocol_versions_config { let supported_versions = match spvc { diff --git a/crates/sui-tls/Cargo.toml b/crates/sui-tls/Cargo.toml index 4f1eaea244784..4a03d9c81dc30 100644 --- a/crates/sui-tls/Cargo.toml +++ b/crates/sui-tls/Cargo.toml @@ -9,6 +9,7 @@ publish = false [dependencies] anyhow.workspace = true +arc-swap.workspace = true ed25519.workspace = true pkcs8.workspace = true rcgen.workspace = true diff --git a/crates/sui-tls/src/lib.rs b/crates/sui-tls/src/lib.rs index 91a6cb666b267..7b3b9c23f5796 100644 --- a/crates/sui-tls/src/lib.rs +++ b/crates/sui-tls/src/lib.rs @@ -10,14 +10,16 @@ pub const SUI_VALIDATOR_SERVER_NAME: &str = "sui"; pub use acceptor::{TlsAcceptor, TlsConnectionInfo}; pub use certgen::SelfSignedCertificate; pub use verifier::{ - public_key_from_certificate, AllowAll, Allower, ClientCertVerifier, HashSetAllow, - ServerCertVerifier, ValidatorAllowlist, + public_key_from_certificate, AllowAll, AllowPublicKeys, Allower, ClientCertVerifier, + ServerCertVerifier, }; pub use rustls; #[cfg(test)] mod tests { + use std::collections::BTreeSet; + use super::*; use fastcrypto::ed25519::Ed25519KeyPair; use fastcrypto::traits::KeyPair; @@ -100,23 +102,16 @@ mod tests { let allowed = Ed25519KeyPair::generate(&mut rng); let disallowed = Ed25519KeyPair::generate(&mut rng); - let allowed_public_key = allowed.public().to_owned(); + let allowed_public_keys = BTreeSet::from([allowed.public().to_owned()]); let allowed_cert = SelfSignedCertificate::new(allowed.private(), SUI_VALIDATOR_SERVER_NAME); let disallowed_cert = SelfSignedCertificate::new(disallowed.private(), SUI_VALIDATOR_SERVER_NAME); - let mut allowlist = HashSetAllow::new(); + let allowlist = AllowPublicKeys::new(allowed_public_keys); let verifier = ClientCertVerifier::new(allowlist.clone(), SUI_VALIDATOR_SERVER_NAME.to_string()); - // Add our public key to the allower - allowlist - .inner_mut() - .write() - .unwrap() - .insert(allowed_public_key); - // The allowed cert passes validation verifier .verify_client_cert(&allowed_cert.rustls_certificate(), &[], UnixTime::now()) @@ -132,7 +127,7 @@ mod tests { ); // After removing the allowed public key from the set it now fails validation - allowlist.inner_mut().write().unwrap().clear(); + allowlist.update(BTreeSet::new()); let err = verifier .verify_client_cert(&allowed_cert.rustls_certificate(), &[], UnixTime::now()) .unwrap_err(); @@ -149,17 +144,10 @@ mod tests { let public_key = keypair.public().to_owned(); let cert = SelfSignedCertificate::new(keypair.private(), "not-sui"); - let mut allowlist = HashSetAllow::new(); + let allowlist = AllowPublicKeys::new(BTreeSet::from([public_key.clone()])); let client_verifier = ClientCertVerifier::new(allowlist.clone(), SUI_VALIDATOR_SERVER_NAME.to_string()); - // Add our public key to the allower - allowlist - .inner_mut() - .write() - .unwrap() - .insert(public_key.clone()); - // Allowed public key but the server-name in the cert is not the required "sui" let err = client_verifier .verify_client_cert(&cert.rustls_certificate(), &[], UnixTime::now()) @@ -210,7 +198,7 @@ mod tests { .build() .unwrap(); - let mut allowlist = HashSetAllow::new(); + let allowlist = AllowPublicKeys::new(BTreeSet::new()); let tls_config = ClientCertVerifier::new(allowlist.clone(), SUI_VALIDATOR_SERVER_NAME.to_string()) .rustls_server_config( @@ -240,11 +228,7 @@ mod tests { client.get(&server_url).send().await.unwrap_err(); // Insert the client's public key into the allowlist and verify the request is successful - allowlist - .inner_mut() - .write() - .unwrap() - .insert(client_public_key.clone()); + allowlist.update(BTreeSet::from([client_public_key.clone()])); let res = client.get(&server_url).send().await.unwrap(); let body = res.text().await.unwrap(); diff --git a/crates/sui-tls/src/verifier.rs b/crates/sui-tls/src/verifier.rs index b2dff221ffd7c..562cc34d48973 100644 --- a/crates/sui-tls/src/verifier.rs +++ b/crates/sui-tls/src/verifier.rs @@ -1,6 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use arc_swap::ArcSwap; use fastcrypto::ed25519::Ed25519PublicKey; use fastcrypto::traits::ToFromBytes; use rustls::crypto::WebPkiSupportedAlgorithms; @@ -10,10 +11,8 @@ use rustls::pki_types::ServerName; use rustls::pki_types::SignatureVerificationAlgorithm; use rustls::pki_types::TrustAnchor; use rustls::pki_types::UnixTime; -use std::{ - collections::HashSet, - sync::{Arc, RwLock}, -}; +use std::collections::BTreeSet; +use std::sync::Arc; static SUPPORTED_SIG_ALGS: &[&dyn SignatureVerificationAlgorithm] = &[webpki::ring::ED25519]; @@ -22,13 +21,12 @@ static SUPPORTED_ALGORITHMS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorith mapping: &[(rustls::SignatureScheme::ED25519, SUPPORTED_SIG_ALGS)], }; -pub type ValidatorAllowlist = Arc>>; - /// The Allower trait provides an interface for callers to inject decsions whether /// to allow a cert to be verified or not. This does not prform actual cert validation /// it only acts as a gatekeeper to decide if we should even try. For example, we may want /// to filter our actions to well known public keys. pub trait Allower: std::fmt::Debug + Send + Sync { + // TODO: change allower interface to use raw key bytes. fn allowed(&self, key: &Ed25519PublicKey) -> bool; } @@ -42,32 +40,28 @@ impl Allower for AllowAll { } } -/// HashSetAllow restricts keys to those that are found in the member set. non-members will not be -/// allowed. +/// AllowPublicKeys restricts keys to those that are found in the member set. non-members will +/// not be allowed. #[derive(Debug, Clone, Default)] -pub struct HashSetAllow { - inner: ValidatorAllowlist, +pub struct AllowPublicKeys { + inner: Arc>>, } -impl HashSetAllow { - pub fn new() -> Self { - let inner = Arc::new(RwLock::new(HashSet::new())); - Self { inner } - } - /// Get a reference to the inner service - pub fn inner(&self) -> &ValidatorAllowlist { - &self.inner +impl AllowPublicKeys { + pub fn new(allowed: BTreeSet) -> Self { + Self { + inner: Arc::new(ArcSwap::from_pointee(allowed)), + } } - /// Get a mutable reference to the inner service - pub fn inner_mut(&mut self) -> &mut ValidatorAllowlist { - &mut self.inner + pub fn update(&self, new_allowed: BTreeSet) { + self.inner.store(Arc::new(new_allowed)); } } -impl Allower for HashSetAllow { +impl Allower for AllowPublicKeys { fn allowed(&self, key: &Ed25519PublicKey) -> bool { - self.inner.read().unwrap().contains(key) + self.inner.load().contains(key) } } diff --git a/crates/sui-tool/src/db_tool/db_dump.rs b/crates/sui-tool/src/db_tool/db_dump.rs index 2e95e756f625e..4149cd7be0e31 100644 --- a/crates/sui-tool/src/db_tool/db_dump.rs +++ b/crates/sui-tool/src/db_tool/db_dump.rs @@ -20,9 +20,9 @@ use sui_core::authority::authority_store_tables::AuthorityPerpetualTables; use sui_core::authority::authority_store_types::{StoreData, StoreObject}; use sui_core::checkpoints::CheckpointStore; use sui_core::epoch::committee_store::CommitteeStoreTables; +use sui_core::jsonrpc_index::IndexStoreTables; use sui_core::rest_index::RestIndexStore; use sui_storage::mutex_table::RwLockTable; -use sui_storage::IndexStoreTables; use sui_types::base_types::{EpochId, ObjectID}; use tracing::info; use typed_store::rocks::{default_db_options, MetricConf}; diff --git a/crates/sui-tool/src/db_tool/index_search.rs b/crates/sui-tool/src/db_tool/index_search.rs index 5790b56878068..3d0adfdf0ea38 100644 --- a/crates/sui-tool/src/db_tool/index_search.rs +++ b/crates/sui-tool/src/db_tool/index_search.rs @@ -11,7 +11,7 @@ use typed_store::traits::Map; use crate::get_db_entries; use move_core_types::language_storage::ModuleId; use std::fmt::Debug; -use sui_storage::IndexStoreTables; +use sui_core::jsonrpc_index::IndexStoreTables; use sui_types::{ base_types::{ObjectID, SuiAddress, TxSequenceNumber}, Identifier, TypeTag, @@ -128,14 +128,6 @@ pub fn search_index( termination ) } - "loaded_child_object_versions" => { - get_db_entries!( - db_read_only_handle.loaded_child_object_versions, - TransactionDigest::from_str, - start, - termination - ) - } "event_by_event_module" => { get_db_entries!( db_read_only_handle.event_by_event_module, diff --git a/crates/sui-transaction-checks/src/deny.rs b/crates/sui-transaction-checks/src/deny.rs index fd10c19489c07..d0b2b9247f7c2 100644 --- a/crates/sui-transaction-checks/src/deny.rs +++ b/crates/sui-transaction-checks/src/deny.rs @@ -81,7 +81,7 @@ fn check_disabled_features( deny_if_true!( filter_config.zklogin_disabled_providers().contains( &OIDCProvider::from_iss(z.get_iss()) - .map_err(|_| SuiError::UnexpectedMessage)? + .map_err(|_| SuiError::UnexpectedMessage(z.get_iss().to_string()))? .to_string() ), "zkLogin OAuth provider is temporarily disabled" diff --git a/crates/sui-transactional-test-runner/src/test_adapter.rs b/crates/sui-transactional-test-runner/src/test_adapter.rs index b8eae5e926842..6ee27e331e740 100644 --- a/crates/sui-transactional-test-runner/src/test_adapter.rs +++ b/crates/sui-transactional-test-runner/src/test_adapter.rs @@ -50,7 +50,7 @@ use sui_core::authority::test_authority_builder::TestAuthorityBuilder; use sui_core::authority::AuthorityState; use sui_framework::DEFAULT_FRAMEWORK_PATH; use sui_graphql_rpc::test_infra::cluster::ExecutorCluster; -use sui_graphql_rpc::test_infra::cluster::{serve_executor, SnapshotLagConfig}; +use sui_graphql_rpc::test_infra::cluster::{serve_executor, RetentionConfig, SnapshotLagConfig}; use sui_json_rpc_api::QUERY_MAX_RESULT_LIMIT; use sui_json_rpc_types::{DevInspectResults, SuiExecutionStatus, SuiTransactionBlockEffectsAPI}; use sui_protocol_config::{Chain, ProtocolConfig}; @@ -330,6 +330,11 @@ impl<'a> MoveTestAdapter<'a> for SuiTestAdapter { }, cluster, ) = if is_simulator { + // TODO: (wlmyng) as of right now, we can't test per-table overrides until the pruner is + // updated + let retention_config = + epochs_to_keep.map(RetentionConfig::new_with_default_retention_only_for_testing); + init_sim_executor( rng, account_names, @@ -338,7 +343,7 @@ impl<'a> MoveTestAdapter<'a> for SuiTestAdapter { custom_validator_account, reference_gas_price, snapshot_config, - epochs_to_keep, + retention_config, ) .await } else { @@ -521,7 +526,6 @@ impl<'a> MoveTestAdapter<'a> for SuiTestAdapter { let empty = SerializedReturnValues { mutable_reference_outputs: vec![], return_values: vec![], - call_traces: vec![], }; Ok((output, empty)) } @@ -580,7 +584,7 @@ impl<'a> MoveTestAdapter<'a> for SuiTestAdapter { .await; cluster - .wait_for_objects_snapshot_catchup(Duration::from_secs(60)) + .wait_for_objects_snapshot_catchup(Duration::from_secs(180)) .await; if let Some(wait_for_checkpoint_pruned) = wait_for_checkpoint_pruned { @@ -2117,7 +2121,7 @@ async fn init_sim_executor( custom_validator_account: bool, reference_gas_price: Option, snapshot_config: SnapshotLagConfig, - epochs_to_keep: Option, + retention_config: Option, ) -> ( Box, AccountSetup, @@ -2189,7 +2193,7 @@ async fn init_sim_executor( let cluster = serve_executor( Arc::new(read_replica), Some(snapshot_config), - epochs_to_keep, + retention_config, data_ingestion_path, ) .await; diff --git a/crates/sui-types/Cargo.toml b/crates/sui-types/Cargo.toml index 9acc4e64a055b..3150c7c4238b7 100644 --- a/crates/sui-types/Cargo.toml +++ b/crates/sui-types/Cargo.toml @@ -76,7 +76,7 @@ proptest-derive.workspace = true better_any.workspace = true lru.workspace = true -sui-sdk2.workspace = true +sui-sdk-types.workspace = true [dev-dependencies] bincode.workspace = true @@ -103,7 +103,6 @@ default = [] test-utils = [] gas-profiler = [ "move-vm-profiler/gas-profiler", - "move-vm-types/gas-profiler", "move-vm-test-utils/gas-profiler", ] fuzzing = ["move-core-types/fuzzing"] diff --git a/crates/sui-types/src/digests.rs b/crates/sui-types/src/digests.rs index 52444720d158d..0b19e5772253d 100644 --- a/crates/sui-types/src/digests.rs +++ b/crates/sui-types/src/digests.rs @@ -4,7 +4,7 @@ use std::{env, fmt}; use crate::{error::SuiError, sui_serde::Readable}; -use fastcrypto::encoding::{Base58, Encoding}; +use fastcrypto::encoding::{Base58, Encoding, Hex}; use once_cell::sync::{Lazy, OnceCell}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -157,6 +157,9 @@ impl fmt::UpperHex for Digest { )] pub struct ChainIdentifier(CheckpointDigest); +pub const MAINNET_CHAIN_IDENTIFIER_BASE58: &str = "4btiuiMPvEENsttpZC7CZ53DruC3MAgfznDbASZ7DR6S"; +pub const TESTNET_CHAIN_IDENTIFIER_BASE58: &str = "69WiPg3DAQiwdxfncX6wYQ2siKwAe6L9BZthQea3JNMD"; + pub static MAINNET_CHAIN_IDENTIFIER: OnceCell = OnceCell::new(); pub static TESTNET_CHAIN_IDENTIFIER: OnceCell = OnceCell::new(); @@ -179,6 +182,24 @@ static SUI_PROTOCOL_CONFIG_CHAIN_OVERRIDE: Lazy> = Lazy::new(|| { }); impl ChainIdentifier { + /// take a short 4 byte identifier and convert it into a ChainIdentifier + /// short ids come from the JSON RPC getChainIdentifier and are encoded in hex + pub fn from_chain_short_id(short_id: &String) -> Option { + if Hex::from_bytes(&Base58::decode(MAINNET_CHAIN_IDENTIFIER_BASE58).ok()?) + .encoded_with_format() + .starts_with(&format!("0x{}", short_id)) + { + Some(get_mainnet_chain_identifier()) + } else if Hex::from_bytes(&Base58::decode(TESTNET_CHAIN_IDENTIFIER_BASE58).ok()?) + .encoded_with_format() + .starts_with(&format!("0x{}", short_id)) + { + Some(get_testnet_chain_identifier()) + } else { + None + } + } + pub fn chain(&self) -> Chain { let mainnet_id = get_mainnet_chain_identifier(); let testnet_id = get_testnet_chain_identifier(); @@ -206,7 +227,7 @@ impl ChainIdentifier { pub fn get_mainnet_chain_identifier() -> ChainIdentifier { let digest = MAINNET_CHAIN_IDENTIFIER.get_or_init(|| { let digest = CheckpointDigest::new( - Base58::decode("4btiuiMPvEENsttpZC7CZ53DruC3MAgfznDbASZ7DR6S") + Base58::decode(MAINNET_CHAIN_IDENTIFIER_BASE58) .expect("mainnet genesis checkpoint digest literal is invalid") .try_into() .expect("Mainnet genesis checkpoint digest literal has incorrect length"), @@ -219,7 +240,7 @@ pub fn get_mainnet_chain_identifier() -> ChainIdentifier { pub fn get_testnet_chain_identifier() -> ChainIdentifier { let digest = TESTNET_CHAIN_IDENTIFIER.get_or_init(|| { let digest = CheckpointDigest::new( - Base58::decode("69WiPg3DAQiwdxfncX6wYQ2siKwAe6L9BZthQea3JNMD") + Base58::decode(TESTNET_CHAIN_IDENTIFIER_BASE58) .expect("testnet genesis checkpoint digest literal is invalid") .try_into() .expect("Testnet genesis checkpoint digest literal has incorrect length"), @@ -1043,3 +1064,32 @@ impl fmt::Debug for ConsensusCommitDigest { .finish() } } + +mod test { + #[allow(unused_imports)] + use crate::digests::ChainIdentifier; + // check that the chain id returns mainnet + #[test] + fn test_chain_id_mainnet() { + let chain_id = ChainIdentifier::from_chain_short_id(&String::from("35834a8a")); + assert_eq!( + chain_id.unwrap().chain(), + sui_protocol_config::Chain::Mainnet + ); + } + + #[test] + fn test_chain_id_testnet() { + let chain_id = ChainIdentifier::from_chain_short_id(&String::from("4c78adac")); + assert_eq!( + chain_id.unwrap().chain(), + sui_protocol_config::Chain::Testnet + ); + } + + #[test] + fn test_chain_id_unknown() { + let chain_id = ChainIdentifier::from_chain_short_id(&String::from("unknown")); + assert_eq!(chain_id, None); + } +} diff --git a/crates/sui-types/src/dynamic_field.rs b/crates/sui-types/src/dynamic_field.rs index 3f6068e9fa22e..2e412b46c37c7 100644 --- a/crates/sui-types/src/dynamic_field.rs +++ b/crates/sui-types/src/dynamic_field.rs @@ -27,6 +27,8 @@ use shared_crypto::intent::HashingIntentScope; use std::fmt; use std::fmt::{Display, Formatter}; +pub mod visitor; + const DYNAMIC_FIELD_MODULE_NAME: &IdentStr = ident_str!("dynamic_field"); const DYNAMIC_FIELD_FIELD_STRUCT_NAME: &IdentStr = ident_str!("Field"); @@ -93,7 +95,9 @@ impl Display for DynamicFieldName { } } -#[derive(Clone, Serialize, Deserialize, JsonSchema, Ord, PartialOrd, Eq, PartialEq, Debug)] +#[derive( + Copy, Clone, Serialize, Deserialize, JsonSchema, Ord, PartialOrd, Eq, PartialEq, Debug, +)] pub enum DynamicFieldType { #[serde(rename_all = "camelCase")] DynamicField, diff --git a/crates/sui-types/src/dynamic_field/visitor.rs b/crates/sui-types/src/dynamic_field/visitor.rs new file mode 100644 index 0000000000000..e8bfe6388aabf --- /dev/null +++ b/crates/sui-types/src/dynamic_field/visitor.rs @@ -0,0 +1,531 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use move_core_types::{ + account_address::AccountAddress, + annotated_value as A, + annotated_visitor::{self, StructDriver, ValueDriver, VariantDriver, VecDriver, Visitor}, + language_storage::TypeTag, + u256::U256, +}; + +use crate::{base_types::ObjectID, id::UID}; + +use super::{DynamicFieldInfo, DynamicFieldType}; + +/// Visitor to deserialize the outer structure of a `0x2::dynamic_field::Field` while leaving its +/// name and value untouched. +pub struct FieldVisitor; + +#[derive(Debug, Clone)] +pub struct Field<'b, 'l> { + pub id: ObjectID, + pub kind: DynamicFieldType, + pub name_layout: &'l A::MoveTypeLayout, + pub name_bytes: &'b [u8], + pub value_layout: &'l A::MoveTypeLayout, + pub value_bytes: &'b [u8], +} + +pub enum ValueMetadata { + DynamicField(TypeTag), + DynamicObjectField(ObjectID), +} + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("Not a dynamic field")] + NotADynamicField, + + #[error("Not a dynamic object field")] + NotADynamicObjectField, + + #[error("{0}")] + Visitor(#[from] annotated_visitor::Error), +} + +impl FieldVisitor { + /// Deserialize the top-level structure from a dynamic field's `0x2::dynamic_field::Field` + /// without having to fully deserialize its name or value. + pub fn deserialize<'b, 'l>( + bytes: &'b [u8], + layout: &'l A::MoveTypeLayout, + ) -> anyhow::Result> { + A::MoveValue::visit_deserialize(bytes, layout, &mut FieldVisitor) + } +} + +impl<'b, 'l> Field<'b, 'l> { + /// If this field is a dynamic field, returns its value's type. If it is a dynamic object + /// field, it returns the ID of the object the value points to (which must be fetched to + /// extract its type). + pub fn value_metadata(&self) -> Result { + match self.kind { + DynamicFieldType::DynamicField => Ok(ValueMetadata::DynamicField(TypeTag::from( + self.value_layout, + ))), + + DynamicFieldType::DynamicObject => { + let id: ObjectID = + bcs::from_bytes(self.value_bytes).map_err(|_| Error::NotADynamicObjectField)?; + Ok(ValueMetadata::DynamicObjectField(id)) + } + } + } +} + +impl<'b, 'l> Visitor<'b, 'l> for FieldVisitor { + type Value = Field<'b, 'l>; + type Error = Error; + + fn visit_struct( + &mut self, + driver: &mut StructDriver<'_, 'b, 'l>, + ) -> Result { + if !DynamicFieldInfo::is_dynamic_field(&driver.struct_layout().type_) { + return Err(Error::NotADynamicField); + } + + // Set-up optionals to fill while visiting fields -- all of them must be filled by the end + // to successfully return a `Field`. + let mut id = None; + let mut name_parts = None; + let mut value_parts = None; + + while let Some(A::MoveFieldLayout { name, layout }) = driver.peek_field() { + match name.as_str() { + "id" => { + let lo = driver.position(); + driver.skip_field()?; + let hi = driver.position(); + + if !matches!(layout, A::MoveTypeLayout::Struct(s) if s.as_ref() == &UID::layout()) + { + return Err(Error::NotADynamicField); + } + + // HACK: Bypassing `id`'s layout to deserialize its bytes as a Rust type. + let bytes = &driver.bytes()[lo..hi]; + id = Some(ObjectID::from_bytes(bytes).map_err(|_| Error::NotADynamicField)?); + } + + "name" => { + let lo = driver.position(); + driver.skip_field()?; + let hi = driver.position(); + + let (kind, layout) = extract_name_layout(layout)?; + name_parts = Some((&driver.bytes()[lo..hi], layout, kind)); + } + + "value" => { + let lo = driver.position(); + driver.skip_field()?; + let hi = driver.position(); + value_parts = Some((&driver.bytes()[lo..hi], layout)); + } + + _ => { + return Err(Error::NotADynamicField); + } + } + } + + let (Some(id), Some((name_bytes, name_layout, kind)), Some((value_bytes, value_layout))) = + (id, name_parts, value_parts) + else { + return Err(Error::NotADynamicField); + }; + + Ok(Field { + id, + kind, + name_layout, + name_bytes, + value_layout, + value_bytes, + }) + } + + // === Empty/default casees === + // + // A dynamic field must be a struct, so if the visitor is fed anything else, it complains. + + fn visit_u8(&mut self, _: &ValueDriver<'_, 'b, 'l>, _: u8) -> Result { + Err(Error::NotADynamicField) + } + + fn visit_u16(&mut self, _: &ValueDriver<'_, 'b, 'l>, _: u16) -> Result { + Err(Error::NotADynamicField) + } + + fn visit_u32(&mut self, _: &ValueDriver<'_, 'b, 'l>, _: u32) -> Result { + Err(Error::NotADynamicField) + } + + fn visit_u64(&mut self, _: &ValueDriver<'_, 'b, 'l>, _: u64) -> Result { + Err(Error::NotADynamicField) + } + + fn visit_u128(&mut self, _: &ValueDriver<'_, 'b, 'l>, _: u128) -> Result { + Err(Error::NotADynamicField) + } + + fn visit_u256(&mut self, _: &ValueDriver<'_, 'b, 'l>, _: U256) -> Result { + Err(Error::NotADynamicField) + } + + fn visit_bool(&mut self, _: &ValueDriver<'_, 'b, 'l>, _: bool) -> Result { + Err(Error::NotADynamicField) + } + + fn visit_address( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: AccountAddress, + ) -> Result { + Err(Error::NotADynamicField) + } + + fn visit_signer( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: AccountAddress, + ) -> Result { + Err(Error::NotADynamicField) + } + + fn visit_vector(&mut self, _: &mut VecDriver<'_, 'b, 'l>) -> Result { + Err(Error::NotADynamicField) + } + + fn visit_variant(&mut self, _: &mut VariantDriver<'_, 'b, 'l>) -> Result { + Err(Error::NotADynamicField) + } +} + +/// Extract the type and layout of a dynamic field name, from the layout of its `Field.name`. +fn extract_name_layout( + layout: &A::MoveTypeLayout, +) -> Result<(DynamicFieldType, &A::MoveTypeLayout), Error> { + let A::MoveTypeLayout::Struct(struct_) = layout else { + return Ok((DynamicFieldType::DynamicField, layout)); + }; + + if !DynamicFieldInfo::is_dynamic_object_field_wrapper(&struct_.type_) { + return Ok((DynamicFieldType::DynamicField, layout)); + } + + // Wrapper contains just one field + let [A::MoveFieldLayout { name, layout }] = &struct_.fields[..] else { + return Err(Error::NotADynamicField); + }; + + // ...called `name` + if name.as_str() != "name" { + return Err(Error::NotADynamicField); + } + + Ok((DynamicFieldType::DynamicObject, layout)) +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use move_core_types::{ + account_address::AccountAddress, annotated_value as A, language_storage::TypeTag, + }; + + use crate::{ + base_types::ObjectID, + dynamic_field, + id::UID, + object::bounded_visitor::tests::{enum_, layout_, value_, variant_}, + }; + + use super::*; + + #[test] + fn test_dynamic_field_name() { + for (name, name_layout, name_bcs) in fixtures() { + for (value, value_layout, value_bcs) in fixtures() { + let df = serialized_df("0x264", name.clone(), value.clone()); + let df_layout = df_layout(name_layout.clone(), value_layout.clone()); + let field = FieldVisitor::deserialize(&df, &df_layout) + .unwrap_or_else(|e| panic!("Failed to deserialize {name} => {value}: {e}")); + + assert_eq!(field.id, oid_("0x264"), "{name} => {value}"); + assert_eq!(field.name_bytes, &name_bcs, "{name} => {value}"); + assert_eq!(field.value_bytes, &value_bcs, "{name} => {value}"); + + assert_eq!( + field.kind, + DynamicFieldType::DynamicField, + "{name} => {value}", + ); + + assert_eq!( + TypeTag::from(field.name_layout), + TypeTag::from(&name_layout), + "{name} => {value}", + ); + + assert_eq!( + TypeTag::from(field.value_layout), + TypeTag::from(&value_layout), + "{name} => {value}", + ); + } + } + } + + #[test] + fn test_dynamic_object_field_name() { + let addr = A::MoveValue::Address(AccountAddress::ONE); + let id = value_("0x2::object::ID", vec![("bytes", addr)]); + let id_bcs = id.clone().undecorate().simple_serialize().unwrap(); + + for (name, name_layout, name_bcs) in fixtures() { + let df = serialized_df("0x264", name.clone(), id.clone()); + let df_layout = dof_layout(name_layout.clone()); + let field = FieldVisitor::deserialize(&df, &df_layout) + .unwrap_or_else(|e| panic!("Failed to deserialize {name}: {e}")); + + assert_eq!(field.id, oid_("0x264"), "{name}"); + assert_eq!(field.name_bytes, &name_bcs, "{name}"); + assert_eq!(field.value_bytes, &id_bcs, "{name}"); + + assert_eq!(field.kind, DynamicFieldType::DynamicObject, "{name}",); + + assert_eq!( + TypeTag::from(field.name_layout), + TypeTag::from(&name_layout), + "{name}", + ); + + assert_eq!( + TypeTag::from(field.value_layout), + TypeTag::from(&id_layout()), + "{name}", + ); + } + } + + #[test] + fn test_name_from_not_dynamic_field() { + for (value, layout, bytes) in fixtures() { + let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else { + panic!("Expected NotADynamicField error for {value}"); + }; + + assert_eq!( + e.to_string(), + "Not a dynamic field", + "Unexpected error for {value}" + ); + } + } + + /// If the visitor is run over a type that isn't actually a `0x2::dynamic_field::Field`, it + /// will complain. + #[test] + fn test_from_bad_type() { + for (value, layout, bytes) in fixtures() { + let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else { + panic!("Expected NotADynamicField error for {value}"); + }; + + assert_eq!( + e.to_string(), + "Not a dynamic field", + "Unexpected error for {value}" + ); + } + } + + #[test] + fn test_from_dynamic_field_missing_id() { + let bytes = bcs::to_bytes(&(42u8, 43u8)).unwrap(); + let layout = layout_( + "0x2::dynamic_field::Field", + vec![ + ("name", A::MoveTypeLayout::U8), + ("value", A::MoveTypeLayout::U8), + ], + ); + + let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else { + panic!("Expected NotADynamicField error"); + }; + + assert_eq!(e.to_string(), "Not a dynamic field"); + } + + #[test] + fn test_from_dynamic_field_missing_name() { + let bytes = bcs::to_bytes(&(oid_("0x264"), 43u8)).unwrap(); + let layout = layout_( + "0x2::dynamic_field::Field", + vec![("id", id_layout()), ("value", A::MoveTypeLayout::U8)], + ); + + let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else { + panic!("Expected NotADynamicField error"); + }; + + assert_eq!(e.to_string(), "Not a dynamic field"); + } + + #[test] + fn test_from_dynamic_field_missing_value() { + let bytes = bcs::to_bytes(&(oid_("0x264"), 42u8)).unwrap(); + let layout = layout_( + "0x2::dynamic_field::Field", + vec![("id", id_layout()), ("name", A::MoveTypeLayout::U8)], + ); + + let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else { + panic!("Expected NotADynamicField error"); + }; + + assert_eq!(e.to_string(), "Not a dynamic field"); + } + + #[test] + fn test_from_dynamic_field_weird_id() { + let bytes = bcs::to_bytes(&(42u8, 43u8, 44u8)).unwrap(); + let layout = layout_( + "0x2::dynamic_field::Field", + vec![ + ("id", A::MoveTypeLayout::U8), + ("name", A::MoveTypeLayout::U8), + ("value", A::MoveTypeLayout::U8), + ], + ); + + let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else { + panic!("Expected NotADynamicField error"); + }; + + assert_eq!(e.to_string(), "Not a dynamic field"); + } + + /// If the name is wrapped in `0x2::dynamic_object_field::Wrapper`, but the wrapper's structure + /// is somehow incorrect, that will result in an error. + #[test] + fn test_from_dynamic_object_field_bad_wrapper() { + let bytes = bcs::to_bytes(&(oid_("0x264"), 42u8)).unwrap(); + let layout = layout_( + "0x2::dynamic_field::Field<0x2::dynamic_object_field::Wrapper, u8>", + vec![ + ("id", id_layout()), + ( + "name", + layout_( + "0x2::dynamic_object_field::Wrapper", + // In the real type, the field is called "name" + vec![("wrapped", A::MoveTypeLayout::U8)], + ), + ), + ("value", A::MoveTypeLayout::U8), + ], + ); + + let Err(e) = FieldVisitor::deserialize(&bytes, &layout) else { + panic!("Expected NotADynamicField error"); + }; + + assert_eq!(e.to_string(), "Not a dynamic field"); + } + + /// Various Move values to use as dynamic field names and values. + fn fixtures() -> Vec<(A::MoveValue, A::MoveTypeLayout, Vec)> { + use A::MoveTypeLayout as T; + use A::MoveValue as V; + + vec![ + fixture(V::U8(42), T::U8), + fixture(V::Address(AccountAddress::ONE), T::Address), + fixture( + V::Vector(vec![V::U32(43), V::U32(44), V::U32(45)]), + T::Vector(Box::new(T::U32)), + ), + fixture( + value_( + "0x2::object::ID", + vec![("bytes", V::Address(AccountAddress::TWO))], + ), + layout_("0x2::object::ID", vec![("bytes", T::Address)]), + ), + fixture( + variant_( + "0x1::option::Option", + "Some", + 1, + vec![("value", V::U64(46))], + ), + enum_( + "0x1::option::Option", + vec![ + (("None", 0), vec![]), + (("Some", 1), vec![("value", T::U64)]), + ], + ), + ), + ] + } + + fn fixture( + value: A::MoveValue, + layout: A::MoveTypeLayout, + ) -> (A::MoveValue, A::MoveTypeLayout, Vec) { + let bytes = value + .clone() + .undecorate() + .simple_serialize() + .unwrap_or_else(|| panic!("Failed to serialize {}", value.clone())); + + (value, layout, bytes) + } + + fn oid_(rep: &str) -> ObjectID { + ObjectID::from_str(rep).unwrap() + } + + fn serialized_df(id: &str, name: A::MoveValue, value: A::MoveValue) -> Vec { + bcs::to_bytes(&dynamic_field::Field { + id: UID::new(oid_(id)), + name: name.undecorate(), + value: value.undecorate(), + }) + .unwrap() + } + + fn id_layout() -> A::MoveTypeLayout { + let addr = A::MoveTypeLayout::Address; + layout_("0x2::object::ID", vec![("bytes", addr)]) + } + + fn df_layout(name: A::MoveTypeLayout, value: A::MoveTypeLayout) -> A::MoveTypeLayout { + let uid = layout_("0x2::object::UID", vec![("id", id_layout())]); + let field = format!( + "0x2::dynamic_field::Field<{}, {}>", + TypeTag::from(&name).to_canonical_display(/* with_prefix */ true), + TypeTag::from(&value).to_canonical_display(/* with_prefix */ true) + ); + + layout_(&field, vec![("id", uid), ("name", name), ("value", value)]) + } + + fn dof_layout(name: A::MoveTypeLayout) -> A::MoveTypeLayout { + let tag = TypeTag::from(&name); + let wrapper = format!( + "0x2::dynamic_object_field::Wrapper<{}>", + tag.to_canonical_display(/* with_prefix */ true) + ); + + let name = layout_(&wrapper, vec![("name", name)]); + df_layout(name, id_layout()) + } +} diff --git a/crates/sui-types/src/error.rs b/crates/sui-types/src/error.rs index afa128dcf7bcf..7d97c8ffa7509 100644 --- a/crates/sui-types/src/error.rs +++ b/crates/sui-types/src/error.rs @@ -443,8 +443,8 @@ pub enum SuiError { #[error("Invalid DKG message size")] InvalidDkgMessageSize, - #[error("Unexpected message.")] - UnexpectedMessage, + #[error("Unexpected message: {0}")] + UnexpectedMessage(String), // Move module publishing related errors #[error("Failed to verify the Move module, reason: {error:?}.")] @@ -809,6 +809,7 @@ impl SuiError { SuiError::ValidatorHaltedAtEpochEnd => true, SuiError::MissingCommitteeAtEpoch(..) => true, SuiError::WrongEpoch { .. } => true, + SuiError::EpochEnded(..) => true, SuiError::UserInputError { error } => { match error { diff --git a/crates/sui-types/src/event.rs b/crates/sui-types/src/event.rs index da13f4469318a..287522b0d2b11 100755 --- a/crates/sui-types/src/event.rs +++ b/crates/sui-types/src/event.rs @@ -38,8 +38,7 @@ pub struct EventEnvelope { /// Move event's json value pub parsed_json: Value, } -/// Unique ID of a Sui Event, the ID is a combination of tx seq number and event seq number, -/// the ID is local to this particular fullnode and will be different from other fullnode. +/// Unique ID of a Sui Event, the ID is a combination of transaction digest and event seq number. #[serde_as] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Hash)] #[serde(rename_all = "camelCase")] diff --git a/crates/sui-types/src/executable_transaction.rs b/crates/sui-types/src/executable_transaction.rs index 4e4354c1be4a5..964bf9b235947 100644 --- a/crates/sui-types/src/executable_transaction.rs +++ b/crates/sui-types/src/executable_transaction.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::messages_checkpoint::CheckpointSequenceNumber; +use crate::messages_consensus::{AuthorityIndex, Round, TransactionIndex}; use crate::{committee::EpochId, crypto::AuthorityStrongQuorumSignInfo}; use crate::message_envelope::{Envelope, TrustedEnvelope, VerifiedEnvelope}; @@ -23,6 +24,9 @@ pub enum CertificateProof { QuorumExecuted(EpochId), /// Transaction generated by the system, for example Clock update transaction SystemTransaction(EpochId), + /// Validity was proven through consensus. Round, authority and transaction index indicate + /// the position of the transaction in the consensus DAG for debugging. + Consensus(EpochId, Round, AuthorityIndex, TransactionIndex), } impl CertificateProof { @@ -38,11 +42,21 @@ impl CertificateProof { Self::SystemTransaction(epoch) } + pub fn new_from_consensus( + epoch: EpochId, + round: Round, + authority: AuthorityIndex, + transaction_index: TransactionIndex, + ) -> Self { + Self::Consensus(epoch, round, authority, transaction_index) + } + pub fn epoch(&self) -> EpochId { match self { Self::Checkpoint(epoch, _) | Self::QuorumExecuted(epoch) - | Self::SystemTransaction(epoch) => *epoch, + | Self::SystemTransaction(epoch) + | Self::Consensus(epoch, _, _, _) => *epoch, Self::Certified(sig) => sig.epoch, } } @@ -57,13 +71,6 @@ pub type VerifiedExecutableTransaction = VerifiedEnvelope; impl VerifiedExecutableTransaction { - pub fn certificate_sig(&self) -> Option<&AuthorityStrongQuorumSignInfo> { - match self.auth_sig() { - CertificateProof::Certified(sig) => Some(sig), - _ => None, - } - } - pub fn gas_budget(&self) -> u64 { self.data().transaction_data().gas_budget() } diff --git a/crates/sui-types/src/lib.rs b/crates/sui-types/src/lib.rs index d410e42ba35c6..4823253ab985f 100644 --- a/crates/sui-types/src/lib.rs +++ b/crates/sui-types/src/lib.rs @@ -76,7 +76,7 @@ pub mod randomness_state; pub mod signature; pub mod signature_verification; pub mod storage; -pub mod sui_sdk2_conversions; +pub mod sui_sdk_types_conversions; pub mod sui_serde; pub mod sui_system_state; pub mod supported_protocol_versions; diff --git a/crates/sui-types/src/message_envelope.rs b/crates/sui-types/src/message_envelope.rs index be4454367a344..4a15a39ccef97 100644 --- a/crates/sui-types/src/message_envelope.rs +++ b/crates/sui-types/src/message_envelope.rs @@ -10,10 +10,12 @@ use crate::crypto::{ use crate::error::SuiResult; use crate::executable_transaction::CertificateProof; use crate::messages_checkpoint::CheckpointSequenceNumber; +use crate::messages_consensus::{AuthorityIndex, Round, TransactionIndex}; use crate::transaction::SenderSignedData; use fastcrypto::traits::KeyPair; use once_cell::sync::OnceCell; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_name::{DeserializeNameAdapter, SerializeNameAdapter}; use shared_crypto::intent::{Intent, IntentScope}; use std::fmt::{Debug, Display, Formatter}; use std::ops::{Deref, DerefMut}; @@ -30,6 +32,7 @@ pub trait Message { } #[derive(Clone, Debug, Eq, Serialize, Deserialize)] +#[serde(remote = "Envelope")] pub struct Envelope { #[serde(skip)] digest: OnceCell, @@ -38,6 +41,38 @@ pub struct Envelope { auth_signature: S, } +impl<'de, T, S> Deserialize<'de> for Envelope +where + T: Message + Deserialize<'de>, + S: Deserialize<'de>, +{ + fn deserialize(deserializer: D) -> Result + where + D: serde::de::Deserializer<'de>, + { + Envelope::deserialize(DeserializeNameAdapter::new( + deserializer, + std::any::type_name::(), + )) + } +} + +impl Serialize for Envelope +where + T: Message + Serialize, + Sig: Serialize, +{ + fn serialize(&self, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + Envelope::serialize( + self, + SerializeNameAdapter::new(serializer, std::any::type_name::()), + ) + } +} + impl Envelope { pub fn new_from_data_and_sig(data: T, sig: S) -> Self { Self { @@ -418,6 +453,31 @@ impl VerifiedEnvelope { }) } + pub fn new_from_consensus( + transaction: VerifiedEnvelope, + epoch: EpochId, + round: Round, + authority: AuthorityIndex, + transaction_index: TransactionIndex, + ) -> Self { + let inner = transaction.into_inner(); + let Envelope { + digest, + data, + auth_signature: _, + } = inner; + VerifiedEnvelope::new_unchecked(Envelope { + digest, + data, + auth_signature: CertificateProof::new_from_consensus( + epoch, + round, + authority, + transaction_index, + ), + }) + } + pub fn epoch(&self) -> EpochId { self.auth_signature.epoch() } diff --git a/crates/sui-types/src/messages_consensus.rs b/crates/sui-types/src/messages_consensus.rs index 7b82c4a0345e9..29a4cc08dcda5 100644 --- a/crates/sui-types/src/messages_consensus.rs +++ b/crates/sui-types/src/messages_consensus.rs @@ -4,9 +4,7 @@ use crate::base_types::{AuthorityName, ObjectRef, TransactionDigest}; use crate::base_types::{ConciseableName, ObjectID, SequenceNumber}; use crate::digests::ConsensusCommitDigest; -use crate::messages_checkpoint::{ - CheckpointSequenceNumber, CheckpointSignatureMessage, CheckpointTimestamp, -}; +use crate::messages_checkpoint::{CheckpointSequenceNumber, CheckpointSignatureMessage}; use crate::supported_protocol_versions::{ Chain, SupportedProtocolVersions, SupportedProtocolVersionsWithHashes, }; @@ -24,16 +22,29 @@ use std::hash::{Hash, Hasher}; use std::sync::Arc; use std::time::{SystemTime, UNIX_EPOCH}; +/// The index of an authority in the consensus committee. +/// The value should be the same in Sui committee. +pub type AuthorityIndex = u32; + +/// Consensus round number. +pub type Round = u32; + +/// The index of a transaction in a consensus block. +pub type TransactionIndex = u16; + +/// Non-decreasing timestamp produced by consensus in ms. +pub type TimestampMs = u64; + /// Only commit_timestamp_ms is passed to the move call currently. /// However we include epoch and round to make sure each ConsensusCommitPrologue has a unique tx digest. #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] pub struct ConsensusCommitPrologue { /// Epoch of the commit prologue transaction pub epoch: u64, - /// Consensus round of the commit + /// Consensus round of the commit. Using u64 for compatibility. pub round: u64, - /// Unix timestamp from consensus - pub commit_timestamp_ms: CheckpointTimestamp, + /// Unix timestamp from consensus commit. + pub commit_timestamp_ms: TimestampMs, } #[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)] @@ -42,8 +53,8 @@ pub struct ConsensusCommitPrologueV2 { pub epoch: u64, /// Consensus round of the commit pub round: u64, - /// Unix timestamp from consensus - pub commit_timestamp_ms: CheckpointTimestamp, + /// Unix timestamp from consensus commit. + pub commit_timestamp_ms: TimestampMs, /// Digest of consensus output pub consensus_commit_digest: ConsensusCommitDigest, } @@ -64,8 +75,8 @@ pub struct ConsensusCommitPrologueV3 { /// The sub DAG index of the consensus commit. This field will be populated if there /// are multiple consensus commits per round. pub sub_dag_index: Option, - /// Unix timestamp from consensus - pub commit_timestamp_ms: CheckpointTimestamp, + /// Unix timestamp from consensus commit. + pub commit_timestamp_ms: TimestampMs, /// Digest of consensus output pub consensus_commit_digest: ConsensusCommitDigest, /// Stores consensus handler determined shared object version assignments. @@ -393,6 +404,18 @@ impl ConsensusTransaction { } } + pub fn new_user_transaction_message(authority: &AuthorityName, tx: Transaction) -> Self { + let mut hasher = DefaultHasher::new(); + let tx_digest = tx.digest(); + tx_digest.hash(&mut hasher); + authority.hash(&mut hasher); + let tracking_id = hasher.finish().to_le_bytes(); + Self { + tracking_id, + kind: ConsensusTransactionKind::UserTransaction(Box::new(tx)), + } + } + pub fn new_checkpoint_signature_message(data: CheckpointSignatureMessage) -> Self { let mut hasher = DefaultHasher::new(); data.summary.auth_sig().signature.hash(&mut hasher); @@ -537,10 +560,14 @@ impl ConsensusTransaction { } } - pub fn is_user_certificate(&self) -> bool { + pub fn is_certified_transaction(&self) -> bool { matches!(self.kind, ConsensusTransactionKind::CertifiedTransaction(_)) } + pub fn is_user_transaction(&self) -> bool { + matches!(self.kind, ConsensusTransactionKind::UserTransaction(_)) + } + pub fn is_end_of_publish(&self) -> bool { matches!(self.kind, ConsensusTransactionKind::EndOfPublish(_)) } diff --git a/crates/sui-types/src/messages_grpc.rs b/crates/sui-types/src/messages_grpc.rs index ffe8001980e68..1d2ce63d32e51 100644 --- a/crates/sui-types/src/messages_grpc.rs +++ b/crates/sui-types/src/messages_grpc.rs @@ -4,10 +4,11 @@ use crate::base_types::{ObjectID, SequenceNumber, TransactionDigest}; use crate::crypto::{AuthoritySignInfo, AuthorityStrongQuorumSignInfo}; use crate::effects::{ - SignedTransactionEffects, TransactionEvents, VerifiedSignedTransactionEffects, + SignedTransactionEffects, TransactionEffects, TransactionEvents, + VerifiedSignedTransactionEffects, }; use crate::object::Object; -use crate::transaction::{CertifiedTransaction, SenderSignedData, SignedTransaction}; +use crate::transaction::{CertifiedTransaction, SenderSignedData, SignedTransaction, Transaction}; use move_core_types::annotated_value::MoveStructLayout; use serde::{Deserialize, Serialize}; @@ -184,7 +185,7 @@ pub struct SystemStateRequest { /// Response type for version 3 of the handle certifacte validator API. /// -/// The coorisponding version 3 request type allows for a client to request events as well as +/// The corresponding version 3 request type allows for a client to request events as well as /// input/output objects from a transaction's execution. Given Validators operate with very /// aggressive object pruning, the return of input/output objects is only done immediately after /// the transaction has been executed locally on the validator and will not be returned for @@ -219,6 +220,43 @@ pub struct HandleCertificateRequestV3 { pub include_auxiliary_data: bool, } +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct HandleTransactionRequestV2 { + pub transaction: Transaction, + + pub include_events: bool, + pub include_input_objects: bool, + pub include_output_objects: bool, + pub include_auxiliary_data: bool, +} + +/// Response type for version 2 of the handle transaction validator API. +/// +/// The corresponding version 2 request type allows for a client to request events as well as +/// input/output objects from a transaction's execution. Given Validators operate with very +/// aggressive object pruning, the return of input/output objects is only done immediately after +/// the transaction has been executed locally on the validator and will not be returned for +/// requests to previously executed transactions. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct HandleTransactionResponseV2 { + pub effects: TransactionEffects, + pub events: Option, + + /// If requested, will included all initial versions of objects modified in this transaction. + /// This includes owned objects included as input into the transaction as well as the assigned + /// versions of shared objects. + // + // TODO: In the future we may want to include shared objects or child objects which were read + // but not modified during exectuion. + pub input_objects: Option>, + + /// If requested, will included all changed objects, including mutated, created and unwrapped + /// objects. In other words, all objects that still exist in the object state after this + /// transaction. + pub output_objects: Option>, + pub auxiliary_data: Option>, +} + impl From for HandleCertificateResponseV2 { fn from(value: HandleCertificateResponseV3) -> Self { Self { diff --git a/crates/sui-types/src/object.rs b/crates/sui-types/src/object.rs index 04146c2699e08..7c3d300aeb39f 100644 --- a/crates/sui-types/src/object.rs +++ b/crates/sui-types/src/object.rs @@ -578,6 +578,7 @@ pub struct ObjectInner { } #[derive(Eq, PartialEq, Debug, Clone, Deserialize, Serialize, Hash)] +#[serde(from = "ObjectInner")] pub struct Object(Arc); impl From for Object { diff --git a/crates/sui-types/src/object/balance_traversal.rs b/crates/sui-types/src/object/balance_traversal.rs index cbbcc49236d74..e6f34128c075c 100644 --- a/crates/sui-types/src/object/balance_traversal.rs +++ b/crates/sui-types/src/object/balance_traversal.rs @@ -4,7 +4,7 @@ use std::collections::BTreeMap; use move_core_types::{ - annotated_visitor::{self, StructDriver, Traversal}, + annotated_visitor::{self, StructDriver, Traversal, ValueDriver}, language_storage::{StructTag, TypeTag}, }; @@ -30,12 +30,12 @@ impl BalanceTraversal { } } -impl Traversal for BalanceTraversal { +impl<'b, 'l> Traversal<'b, 'l> for BalanceTraversal { type Error = annotated_visitor::Error; fn traverse_struct( &mut self, - driver: &mut StructDriver<'_, '_, '_>, + driver: &mut StructDriver<'_, 'b, 'l>, ) -> Result<(), Self::Error> { let Some(coin_type) = is_balance(&driver.struct_layout().type_) else { // Not a balance, search recursively for balances among fields. @@ -50,9 +50,13 @@ impl Traversal for BalanceTraversal { } } -impl Traversal for Accumulator { +impl<'b, 'l> Traversal<'b, 'l> for Accumulator { type Error = annotated_visitor::Error; - fn traverse_u64(&mut self, value: u64) -> Result<(), Self::Error> { + fn traverse_u64( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u64, + ) -> Result<(), Self::Error> { self.total += value; Ok(()) } diff --git a/crates/sui-types/src/object/bounded_visitor.rs b/crates/sui-types/src/object/bounded_visitor.rs index 54efcbccf7407..bdef4ff0cbe03 100644 --- a/crates/sui-types/src/object/bounded_visitor.rs +++ b/crates/sui-types/src/object/bounded_visitor.rs @@ -5,7 +5,7 @@ use anyhow::bail; use move_core_types::{ account_address::AccountAddress, annotated_value as A, - annotated_visitor::{self, StructDriver, VecDriver, Visitor}, + annotated_visitor::{self, StructDriver, ValueDriver, VecDriver, Visitor}, language_storage::TypeTag, u256::U256, }; @@ -146,49 +146,85 @@ impl BoundedVisitor { } } -impl Visitor for BoundedVisitor { +impl<'b, 'l> Visitor<'b, 'l> for BoundedVisitor { type Value = A::MoveValue; type Error = Error; - fn visit_u8(&mut self, value: u8) -> Result { + fn visit_u8( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u8, + ) -> Result { Ok(A::MoveValue::U8(value)) } - fn visit_u16(&mut self, value: u16) -> Result { + fn visit_u16( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u16, + ) -> Result { Ok(A::MoveValue::U16(value)) } - fn visit_u32(&mut self, value: u32) -> Result { + fn visit_u32( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u32, + ) -> Result { Ok(A::MoveValue::U32(value)) } - fn visit_u64(&mut self, value: u64) -> Result { + fn visit_u64( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u64, + ) -> Result { Ok(A::MoveValue::U64(value)) } - fn visit_u128(&mut self, value: u128) -> Result { + fn visit_u128( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u128, + ) -> Result { Ok(A::MoveValue::U128(value)) } - fn visit_u256(&mut self, value: U256) -> Result { + fn visit_u256( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: U256, + ) -> Result { Ok(A::MoveValue::U256(value)) } - fn visit_bool(&mut self, value: bool) -> Result { + fn visit_bool( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: bool, + ) -> Result { Ok(A::MoveValue::Bool(value)) } - fn visit_address(&mut self, value: AccountAddress) -> Result { + fn visit_address( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result { Ok(A::MoveValue::Address(value)) } - fn visit_signer(&mut self, value: AccountAddress) -> Result { + fn visit_signer( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result { Ok(A::MoveValue::Signer(value)) } fn visit_vector( &mut self, - driver: &mut VecDriver<'_, '_, '_>, + driver: &mut VecDriver<'_, 'b, 'l>, ) -> Result { let mut elems = vec![]; while let Some(elem) = driver.next_element(self)? { @@ -200,7 +236,7 @@ impl Visitor for BoundedVisitor { fn visit_struct( &mut self, - driver: &mut StructDriver<'_, '_, '_>, + driver: &mut StructDriver<'_, 'b, 'l>, ) -> Result { let tag = driver.struct_layout().type_.clone().into(); @@ -226,7 +262,7 @@ impl Visitor for BoundedVisitor { fn visit_variant( &mut self, - driver: &mut annotated_visitor::VariantDriver<'_, '_, '_>, + driver: &mut annotated_visitor::VariantDriver<'_, 'b, 'l>, ) -> Result { let type_ = driver.enum_layout().type_.clone().into(); @@ -262,7 +298,7 @@ impl Default for BoundedVisitor { } #[cfg(test)] -mod tests { +pub(crate) mod tests { use std::str::FromStr; use super::*; @@ -450,23 +486,30 @@ mod tests { expect.assert_eq(&err.to_string()); } + type Variant<'s> = (&'s str, u16); + type FieldLayout<'s> = (&'s str, A::MoveTypeLayout); + + fn ident_(name: &str) -> Identifier { + Identifier::new(name).unwrap() + } + /// Create a struct value for test purposes. - fn value_(rep: &str, fields: Vec<(&str, A::MoveValue)>) -> A::MoveValue { + pub(crate) fn value_(rep: &str, fields: Vec<(&str, A::MoveValue)>) -> A::MoveValue { let type_ = StructTag::from_str(rep).unwrap(); let fields = fields .into_iter() - .map(|(name, value)| (Identifier::new(name).unwrap(), value)) + .map(|(name, value)| (ident_(name), value)) .collect(); A::MoveValue::Struct(A::MoveStruct::new(type_, fields)) } /// Create a struct layout for test purposes. - fn layout_(rep: &str, fields: Vec<(&str, A::MoveTypeLayout)>) -> A::MoveTypeLayout { + pub(crate) fn layout_(rep: &str, fields: Vec>) -> A::MoveTypeLayout { let type_ = StructTag::from_str(rep).unwrap(); let fields = fields .into_iter() - .map(|(name, layout)| A::MoveFieldLayout::new(Identifier::new(name).unwrap(), layout)) + .map(|(name, layout)| A::MoveFieldLayout::new(ident_(name), layout)) .collect(); A::MoveTypeLayout::Struct(Box::new(A::MoveStructLayout { @@ -475,6 +518,47 @@ mod tests { })) } + /// Create a variant value for test purposes. + pub(crate) fn variant_( + rep: &str, + name: &str, + tag: u16, + fields: Vec<(&str, A::MoveValue)>, + ) -> A::MoveValue { + let type_ = StructTag::from_str(rep).unwrap(); + let fields = fields + .into_iter() + .map(|(name, value)| (ident_(name), value)) + .collect(); + + A::MoveValue::Variant(A::MoveVariant { + type_, + variant_name: ident_(name), + tag, + fields, + }) + } + + /// Create an enum layout for test purposes. + pub(crate) fn enum_( + rep: &str, + variants: Vec<(Variant<'_>, Vec>)>, + ) -> A::MoveTypeLayout { + let type_ = StructTag::from_str(rep).unwrap(); + let variants = variants + .into_iter() + .map(|((name, tag), fields)| { + let fields = fields + .into_iter() + .map(|(name, layout)| A::MoveFieldLayout::new(ident_(name), layout)) + .collect(); + ((ident_(name), tag), fields) + }) + .collect(); + + A::MoveTypeLayout::Enum(Box::new(A::MoveEnumLayout { type_, variants })) + } + /// BCS encode Move value. fn serialize(value: A::MoveValue) -> Vec { value.clone().undecorate().simple_serialize().unwrap() diff --git a/crates/sui-types/src/sui_sdk2_conversions.rs b/crates/sui-types/src/sui_sdk_types_conversions.rs similarity index 99% rename from crates/sui-types/src/sui_sdk2_conversions.rs rename to crates/sui-types/src/sui_sdk_types_conversions.rs index 10130e867ca8f..23fc8bc91adee 100644 --- a/crates/sui-types/src/sui_sdk2_conversions.rs +++ b/crates/sui-types/src/sui_sdk_types_conversions.rs @@ -9,7 +9,7 @@ //! directly to avoid going through the BCS machinery. use fastcrypto::traits::ToFromBytes; -use sui_sdk2::types::*; +use sui_sdk_types::types::*; use tap::Pipe; #[derive(Debug)] diff --git a/crates/sui-types/src/traffic_control.rs b/crates/sui-types/src/traffic_control.rs index 95e5ee534887b..6a70ea3ad28ab 100644 --- a/crates/sui-types/src/traffic_control.rs +++ b/crates/sui-types/src/traffic_control.rs @@ -251,6 +251,11 @@ pub struct PolicyConfig { pub spam_sample_rate: Weight, #[serde(default = "default_dry_run")] pub dry_run: bool, + /// List of String which should all parse to type IPAddr. + /// If set, only requests from provided IPs will be allowed, + /// and any blocklist related configuration will be ignored. + #[serde(default)] + pub allow_list: Option>, } impl Default for PolicyConfig { @@ -264,6 +269,7 @@ impl Default for PolicyConfig { channel_capacity: 100, spam_sample_rate: default_spam_sample_rate(), dry_run: default_dry_run(), + allow_list: None, } } } diff --git a/crates/sui-types/src/transaction.rs b/crates/sui-types/src/transaction.rs index 9a3b4f937b335..6ac464a07c69a 100644 --- a/crates/sui-types/src/transaction.rs +++ b/crates/sui-types/src/transaction.rs @@ -2864,7 +2864,7 @@ pub struct ObjectReadResult { pub object: ObjectReadResultKind, } -#[derive(Clone, Debug)] +#[derive(Clone)] pub enum ObjectReadResultKind { Object(Object), // The version of the object that the transaction intended to read, and the digest of the tx @@ -2874,6 +2874,22 @@ pub enum ObjectReadResultKind { CancelledTransactionSharedObject(SequenceNumber), } +impl std::fmt::Debug for ObjectReadResultKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + ObjectReadResultKind::Object(obj) => { + write!(f, "Object({:?})", obj.compute_object_reference()) + } + ObjectReadResultKind::DeletedSharedObject(seq, digest) => { + write!(f, "DeletedSharedObject({}, {:?})", seq, digest) + } + ObjectReadResultKind::CancelledTransactionSharedObject(seq) => { + write!(f, "CancelledTransactionSharedObject({})", seq) + } + } + } +} + impl From for ObjectReadResultKind { fn from(object: Object) -> Self { Self::Object(object) @@ -3019,6 +3035,12 @@ pub struct InputObjects { objects: Vec, } +impl std::fmt::Debug for InputObjects { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_list().entries(self.objects.iter()).finish() + } +} + // An InputObjects new-type that has been verified by sui-transaction-checks, and can be // safely passed to execution. pub struct CheckedInputObjects(InputObjects); diff --git a/crates/sui-types/src/transaction_executor.rs b/crates/sui-types/src/transaction_executor.rs index 8fd2ebc742a28..308c3258dafe7 100644 --- a/crates/sui-types/src/transaction_executor.rs +++ b/crates/sui-types/src/transaction_executor.rs @@ -1,9 +1,17 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 +use std::collections::BTreeMap; + +use crate::base_types::ObjectID; +use crate::effects::TransactionEffects; +use crate::effects::TransactionEvents; +use crate::error::SuiError; +use crate::object::Object; use crate::quorum_driver_types::ExecuteTransactionRequestV3; use crate::quorum_driver_types::ExecuteTransactionResponseV3; use crate::quorum_driver_types::QuorumDriverError; +use crate::transaction::TransactionData; /// Trait to define the interface for how the REST service interacts with a a QuorumDriver or a /// simulated transaction executor. @@ -14,4 +22,17 @@ pub trait TransactionExecutor: Send + Sync { request: ExecuteTransactionRequestV3, client_addr: Option, ) -> Result; + + fn simulate_transaction( + &self, + transaction: TransactionData, + ) -> Result; +} + +pub struct SimulateTransactionResult { + pub effects: TransactionEffects, + pub events: Option, + pub input_objects: BTreeMap, + pub output_objects: BTreeMap, + pub mock_gas_id: Option, } diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__constant_name_change.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__constant_name_change.snap index b86118f72d405..e4c5575f7fdce 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__constant_name_change.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__constant_name_change.snap @@ -3,37 +3,25 @@ source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__constant_value_changed.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__constant_value_changed.snap index ce6f70ed769a9..0f15dec74c2f3 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__constant_value_changed.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__constant_value_changed.snap @@ -3,37 +3,25 @@ source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__friend_entry_changed.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__friend_entry_changed.snap index d51e97b52ad06..388c974ae41d5 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__friend_entry_changed.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__friend_entry_changed.snap @@ -3,25 +3,7 @@ source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: false - upgrade->base: true -0000000000000000000000000000000000000000000000000000000000000000::friend_module: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: true - upgrade->base: true -0000000000000000000000000000000000000000000000000000000000000000::friend_module: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true @@ -30,7 +12,7 @@ Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_la upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: true @@ -39,7 +21,7 @@ Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_la upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: true @@ -48,7 +30,7 @@ Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_la upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__friend_function_change.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__friend_function_change.snap index bfcc468e0ee46..e13049fda2aa6 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__friend_function_change.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__friend_function_change.snap @@ -3,25 +3,16 @@ source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: false - upgrade->base: false -0000000000000000000000000000000000000000000000000000000000000000::friend_module: base->upgrade: true upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: false - upgrade->base: false 0000000000000000000000000000000000000000000000000000000000000000::friend_module: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true @@ -30,7 +21,7 @@ Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_la upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true @@ -39,16 +30,7 @@ Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_la upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: false - upgrade->base: false -0000000000000000000000000000000000000000000000000000000000000000::friend_module: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_equality_check.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_equality_check.snap index e63e0028d0b00..3534c1c5410a4 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_equality_check.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_equality_check.snap @@ -3,37 +3,25 @@ source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::ascii: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::ascii: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_equality_check_alpha_rename.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_equality_check_alpha_rename.snap index e63e0028d0b00..af0b45fe55e07 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_equality_check_alpha_rename.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_equality_check_alpha_rename.snap @@ -1,39 +1,28 @@ --- source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs +assertion_line: 119 expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::ascii: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::ascii: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_equality_check_local_shuffle.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_equality_check_local_shuffle.snap index 062f7c1443a57..79ff641479f6f 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_equality_check_local_shuffle.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_equality_check_local_shuffle.snap @@ -3,37 +3,25 @@ source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::ascii: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::ascii: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_inclusion_check.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_inclusion_check.snap index 3e910d83564fc..f9c0fa5f6dc7d 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_inclusion_check.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_inclusion_check.snap @@ -1,39 +1,28 @@ --- source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs +assertion_line: 119 expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::ascii: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::ascii: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_invalid_equality_inclusion_check.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_invalid_equality_inclusion_check.snap index 062f7c1443a57..79ff641479f6f 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_invalid_equality_inclusion_check.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__large_package_invalid_equality_inclusion_check.snap @@ -3,37 +3,25 @@ source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::ascii: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::ascii: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::ascii: base->upgrade: true upgrade->base: true diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__private_entry_and_friend_changes.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__private_entry_and_friend_changes.snap index c6681aa5ba95a..bfb2725c8427f 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__private_entry_and_friend_changes.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__private_entry_and_friend_changes.snap @@ -3,25 +3,7 @@ source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: false - upgrade->base: false -0000000000000000000000000000000000000000000000000000000000000000::friend_module: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: false - upgrade->base: false -0000000000000000000000000000000000000000000000000000000000000000::friend_module: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true @@ -30,7 +12,7 @@ Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_la upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: true @@ -39,16 +21,16 @@ Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_la upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false - upgrade->base: false + upgrade->base: true 0000000000000000000000000000000000000000000000000000000000000000::friend_module: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__private_entry_fun_entry_removed.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__private_entry_fun_entry_removed.snap index d51e97b52ad06..388c974ae41d5 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__private_entry_fun_entry_removed.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__private_entry_fun_entry_removed.snap @@ -3,25 +3,7 @@ source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: false - upgrade->base: true -0000000000000000000000000000000000000000000000000000000000000000::friend_module: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: true - upgrade->base: true -0000000000000000000000000000000000000000000000000000000000000000::friend_module: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true @@ -30,7 +12,7 @@ Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_la upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: true @@ -39,7 +21,7 @@ Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_la upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: true @@ -48,7 +30,7 @@ Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_la upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__public_fun_param_alpha_rename.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__public_fun_param_alpha_rename.snap index b86118f72d405..e4c5575f7fdce 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__public_fun_param_alpha_rename.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__public_fun_param_alpha_rename.snap @@ -3,37 +3,25 @@ source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__public_fun_param_permute.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__public_fun_param_permute.snap index ce6f70ed769a9..0f15dec74c2f3 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__public_fun_param_permute.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__public_fun_param_permute.snap @@ -3,37 +3,25 @@ source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: true - upgrade->base: true -==== -==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__public_fun_rename.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__public_fun_rename.snap index 09bbea5822c3a..ff77031427299 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__public_fun_rename.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__public_fun_rename.snap @@ -3,40 +3,28 @@ source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: false - upgrade->base: false -==== -==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: true - upgrade->base: true ======== Equal 0000000000000000000000000000000000000000000000000000000000000000::base_module: diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_field_name_change.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_field_name_change.snap index 09bbea5822c3a..0b4ea50756164 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_field_name_change.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_field_name_change.snap @@ -1,39 +1,28 @@ --- source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs +assertion_line: 119 expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: false - upgrade->base: false -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: false - upgrade->base: false -==== -==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_field_reorder.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_field_reorder.snap index 09bbea5822c3a..0b903a6a27ef1 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_field_reorder.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_field_reorder.snap @@ -3,37 +3,25 @@ source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: false - upgrade->base: false -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: false - upgrade->base: false -==== -==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_field_reorder_no_name_change.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_field_reorder_no_name_change.snap index 09bbea5822c3a..0b903a6a27ef1 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_field_reorder_no_name_change.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_field_reorder_no_name_change.snap @@ -3,37 +3,25 @@ source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: false - upgrade->base: false -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: false - upgrade->base: false -==== -==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_layout_change.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_layout_change.snap index 09bbea5822c3a..0b903a6a27ef1 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_layout_change.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_layout_change.snap @@ -3,37 +3,25 @@ source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: false - upgrade->base: false -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: false - upgrade->base: false -==== -==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: true upgrade->base: true diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_name_change.snap b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_name_change.snap index 09bbea5822c3a..50a9c5c431e77 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_name_change.snap +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/snapshots/tests__struct_name_change.snap @@ -1,42 +1,31 @@ --- source: crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs +assertion_line: 119 expression: results --- ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: true, check_private_entry_linking: true, disallowed_new_abilities: [Key, ] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false ==== ==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } +Compatibility { check_datatype_layout: false, check_private_entry_linking: false, disallowed_new_abilities: [] } 0000000000000000000000000000000000000000000000000000000000000000::base_module: base->upgrade: false upgrade->base: false -==== -==== -Compatibility { check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: [Copy, Drop, Store, Key, ], disallow_change_datatype_type_params: true, disallow_new_variants: true } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: false - upgrade->base: false -==== -==== -Compatibility { check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: [], disallow_change_datatype_type_params: false, disallow_new_variants: false } -0000000000000000000000000000000000000000000000000000000000000000::base_module: - base->upgrade: true - upgrade->base: true ======== Equal 0000000000000000000000000000000000000000000000000000000000000000::base_module: diff --git a/crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs b/crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs index 4f491e8f3cc09..ce1f104d25942 100644 --- a/crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs +++ b/crates/sui-upgrade-compatibility-transactional-tests/tests/tests.rs @@ -5,7 +5,6 @@ use std::path::{Path, PathBuf}; use move_binary_format::{ compatibility::{Compatibility, InclusionCheck}, - file_format::AbilitySet, normalized, CompiledModule, }; use sui_move_build::{BuildConfig, SuiPackageHooks}; @@ -52,47 +51,12 @@ fn check_all_compatibilities( assert_eq!(base.len(), upgraded.len()); let compatibility_types = [ + // Full compat skip check private entry linking + Compatibility::upgrade_check(), + // Full compat but allow any new abilities Compatibility::full_check(), - // Full compat but allow private entry functions to change - Compatibility { - check_datatype_and_pub_function_linking: true, - check_datatype_layout: true, - check_friend_linking: true, - check_private_entry_linking: false, - disallowed_new_abilities: AbilitySet::ALL, - disallow_change_datatype_type_params: true, - disallow_new_variants: true, - }, - // Full compat but allow private entry functions and friends to change - Compatibility { - check_datatype_and_pub_function_linking: true, - check_datatype_layout: true, - check_friend_linking: false, - check_private_entry_linking: false, - disallowed_new_abilities: AbilitySet::ALL, - disallow_change_datatype_type_params: true, - disallow_new_variants: true, - }, - // Full compat but allow friends to change - Compatibility { - check_datatype_and_pub_function_linking: true, - check_datatype_layout: true, - check_friend_linking: false, - check_private_entry_linking: true, - disallowed_new_abilities: AbilitySet::ALL, - disallow_change_datatype_type_params: true, - disallow_new_variants: true, - }, - // Full compat but allow new enum variants to be added - Compatibility { - check_datatype_and_pub_function_linking: true, - check_datatype_layout: true, - check_friend_linking: true, - check_private_entry_linking: true, - disallowed_new_abilities: AbilitySet::ALL, - disallow_change_datatype_type_params: true, - disallow_new_variants: true, - }, + // Full compat only disallow new key ability + Compatibility::framework_upgrade_check(), Compatibility::no_check(), ]; diff --git a/crates/sui/src/client_commands.rs b/crates/sui/src/client_commands.rs index 00f9939ce77d8..9f97b95c7b0f0 100644 --- a/crates/sui/src/client_commands.rs +++ b/crates/sui/src/client_commands.rs @@ -1645,8 +1645,7 @@ impl SuiClientCommands { SuiClientCommandResult::NoOutput } }; - let client = context.get_client().await?; - Ok(ret.prerender_clever_errors(client.read_api()).await) + Ok(ret.prerender_clever_errors(context).await) } pub fn switch_env(config: &mut SuiClientConfig, env: &str) -> Result<(), anyhow::Error> { @@ -2297,13 +2296,16 @@ impl SuiClientCommandResult { } } - pub async fn prerender_clever_errors(mut self, read_api: &ReadApi) -> Self { + pub async fn prerender_clever_errors(mut self, context: &mut WalletContext) -> Self { match &mut self { SuiClientCommandResult::DryRun(DryRunTransactionBlockResponse { effects, .. }) | SuiClientCommandResult::TransactionBlock(SuiTransactionBlockResponse { effects: Some(effects), .. - }) => prerender_clever_errors(effects, read_api).await, + }) => { + let client = context.get_client().await.expect("Cannot connect to RPC"); + prerender_clever_errors(effects, client.read_api()).await + } SuiClientCommandResult::TransactionBlock(SuiTransactionBlockResponse { effects: None, @@ -2686,7 +2688,7 @@ fn format_balance( /// Helper function to reduce code duplication for executing dry run pub async fn execute_dry_run( - client: &SuiClient, + context: &mut WalletContext, signer: SuiAddress, kind: TransactionKind, gas_budget: Option, @@ -2694,9 +2696,10 @@ pub async fn execute_dry_run( gas_payment: Option>, sponsor: Option, ) -> Result { + let client = context.get_client().await?; let gas_budget = match gas_budget { Some(gas_budget) => gas_budget, - None => max_gas_budget(client).await?, + None => max_gas_budget(&client).await?, }; let dry_run_tx_data = client .transaction_builder() @@ -2710,7 +2713,7 @@ pub async fn execute_dry_run( .map_err(|e| anyhow!("Dry run failed: {e}"))?; debug!("Finished executing dry run"); let resp = SuiClientCommandResult::DryRun(response) - .prerender_clever_errors(client.read_api()) + .prerender_clever_errors(context) .await; Ok(resp) } @@ -2726,15 +2729,16 @@ pub async fn execute_dry_run( /// This gas estimate is computed exactly as in the TypeScript SDK /// pub async fn estimate_gas_budget( - client: &SuiClient, + context: &mut WalletContext, signer: SuiAddress, kind: TransactionKind, gas_price: u64, gas_payment: Option>, sponsor: Option, ) -> Result { + let client = context.get_client().await?; let Ok(SuiClientCommandResult::DryRun(dry_run)) = - execute_dry_run(client, signer, kind, None, gas_price, gas_payment, sponsor).await + execute_dry_run(context, signer, kind, None, gas_price, gas_payment, sponsor).await else { bail!("Could not automatically determine the gas budget. Please supply one using the --gas-budget flag.") }; @@ -2808,7 +2812,7 @@ pub(crate) async fn dry_run_or_execute_or_serialize( let client = context.get_client().await?; if dry_run { return execute_dry_run( - &client, + context, signer, tx_kind, gas_budget, @@ -2824,7 +2828,7 @@ pub(crate) async fn dry_run_or_execute_or_serialize( None => { debug!("Estimating gas budget"); let budget = estimate_gas_budget( - &client, + context, signer, tx_kind.clone(), gas_price, diff --git a/crates/sui/src/keytool.rs b/crates/sui/src/keytool.rs index 88f70f8718f91..08e4aabefee40 100644 --- a/crates/sui/src/keytool.rs +++ b/crates/sui/src/keytool.rs @@ -1114,6 +1114,22 @@ impl KeyToolCommand { "https://api.ambrus.studio/callback", &jwt_randomness, )?; + let url_14 = get_oidc_url( + OIDCProvider::Arden, + &eph_pk_bytes, + max_epoch, + "2e3i87cb-bf24-4399-ab98-48343d457124", + "https://www.sui.io", + &jwt_randomness, + )?; + let url_15 = get_oidc_url( + OIDCProvider::AwsTenant(("eu-west-3".to_string(), "trace".to_string())), + &eph_pk_bytes, + max_epoch, + "trace-dev", + "https://trace.fan", + &jwt_randomness, + )?; // This is only for CLI testing. If frontend apps will be built, no need to add anything here. println!("Visit URL (Google): {url}"); println!("Visit URL (Twitch): {url_2}"); @@ -1129,6 +1145,8 @@ impl KeyToolCommand { println!("Visit URL (KarrierOne): {url_11}"); println!("Visit URL (Credenza3): {url_12}"); println!("Visit URL (AWS - Ambrus): {url_13}"); + println!("Visit URL (Arden): {url_14}"); + println!("Visit URL (AWS - Trace): {url_15}"); println!("Finish login and paste the entire URL here (e.g. https://sui.io/#id_token=...):"); diff --git a/crates/sui/src/sui_commands.rs b/crates/sui/src/sui_commands.rs index 70aacecad733a..392ba66fa7549 100644 --- a/crates/sui/src/sui_commands.rs +++ b/crates/sui/src/sui_commands.rs @@ -33,7 +33,9 @@ use sui_config::{ SUI_BENCHMARK_GENESIS_GAS_KEYSTORE_FILENAME, SUI_GENESIS_FILENAME, SUI_KEYSTORE_FILENAME, }; use sui_faucet::{create_wallet_context, start_faucet, AppState, FaucetConfig, SimpleFaucet}; -use sui_indexer::test_utils::{start_test_indexer, ReaderWriterConfig}; +use sui_indexer::test_utils::{ + start_indexer_jsonrpc_for_testing, start_indexer_writer_for_testing, +}; use sui_graphql_rpc::{ config::{ConnectionConfig, ServiceConfig}, @@ -427,8 +429,8 @@ impl SuiCommand { } => { let config_path = config.unwrap_or(sui_config_dir()?.join(SUI_CLIENT_CONFIG)); prompt_if_no_config(&config_path, accept_defaults).await?; - let mut context = WalletContext::new(&config_path, None, None)?; if let Some(cmd) = cmd { + let mut context = WalletContext::new(&config_path, None, None)?; cmd.execute(&mut context).await?.print(!json); } else { // Print help @@ -631,7 +633,7 @@ async fn start( swarm_builder = swarm_builder.with_epoch_duration_ms(epoch_duration_ms); } else { if config.is_none() && !sui_config_dir()?.join(SUI_NETWORK_CONFIG).exists() { - genesis(None, None, None, false, epoch_duration_ms, None, false).await?; + genesis(None, None, None, false, epoch_duration_ms, None, false).await.map_err(|_| anyhow!("Cannot run genesis with non-empty Sui config directory: {}.\n\nIf you are trying to run a local network without persisting the data (so a new genesis that is randomly generated and will not be saved once the network is shut down), use --force-regenesis flag.\nIf you are trying to persist the network data and start from a new genesis, use sui genesis --help to see how to generate a new genesis.", sui_config_dir().unwrap().display()))?; } // Load the config of the Sui authority. @@ -704,20 +706,20 @@ async fn start( .map_err(|_| anyhow!("Invalid indexer host and port"))?; info!("Starting the indexer service at {indexer_address}"); // Start in reader mode - start_test_indexer( + start_indexer_jsonrpc_for_testing( pg_address.clone(), fullnode_url.clone(), - ReaderWriterConfig::reader_mode(indexer_address.to_string()), - data_ingestion_path.clone(), + indexer_address.to_string(), + None, ) .await; info!("Indexer started in reader mode"); - // Start in writer mode - start_test_indexer( + start_indexer_writer_for_testing( pg_address.clone(), - fullnode_url.clone(), - ReaderWriterConfig::writer_mode(None, None), - data_ingestion_path.clone(), + None, + None, + Some(data_ingestion_path.clone()), + None, ) .await; info!("Indexer started in writer mode"); @@ -727,14 +729,13 @@ async fn start( let graphql_address = parse_host_port(input, DEFAULT_GRAPHQL_PORT) .map_err(|_| anyhow!("Invalid graphql host and port"))?; tracing::info!("Starting the GraphQL service at {graphql_address}"); - let graphql_connection_config = ConnectionConfig::new( - Some(graphql_address.port()), - Some(graphql_address.ip().to_string()), - Some(pg_address), - None, - None, - None, - ); + let graphql_connection_config = ConnectionConfig { + port: graphql_address.port(), + host: graphql_address.ip().to_string(), + db_url: pg_address, + ..Default::default() + }; + start_graphql_server_with_fn_rpc( graphql_connection_config, Some(fullnode_url.clone()), diff --git a/crates/sui/src/unit_tests/fixtures/upgrade_errors/all_v1/Move.toml b/crates/sui/src/unit_tests/fixtures/upgrade_errors/all_v1/Move.toml new file mode 100644 index 0000000000000..70f14c105d35c --- /dev/null +++ b/crates/sui/src/unit_tests/fixtures/upgrade_errors/all_v1/Move.toml @@ -0,0 +1,6 @@ +[package] +name = "upgrades" +edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move + +[addresses] +upgrades = "0x0" \ No newline at end of file diff --git a/crates/sui/src/unit_tests/fixtures/upgrade_errors/all_v1/sources/UpgradeErrors.move b/crates/sui/src/unit_tests/fixtures/upgrade_errors/all_v1/sources/UpgradeErrors.move new file mode 100644 index 0000000000000..f09e4424df3d1 --- /dev/null +++ b/crates/sui/src/unit_tests/fixtures/upgrade_errors/all_v1/sources/UpgradeErrors.move @@ -0,0 +1,95 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// Module: UpgradeErrors + +#[allow(unused_field)] +module upgrades::upgrades { + // struct missing + public struct StructToBeRemoved { + b: u64 + } + + // struct ability mismatch (add) + public struct StructAbilityMismatchAdd {} + + // struct ability mismatch (remove) + public struct StructAbilityMismatchRemove has copy {} + + // struct ability mismatch (change) + public struct StructAbilityMismatchChange has copy {} + + // struct type param mismatch + public struct StructTypeParamMismatch { a: S } + + // struct field mismatch (add) + public struct StructFieldMismatchAdd { + a: u64, + b: u64 + } + + // struct field mismatch (remove) + public struct StructFieldMismatchRemove { + a: u64, + b: u64 + } + + // struct field mismatch (change) + public struct StructFieldMismatchChange { + a: u64, + b: u64 + } + + // enum missing + public enum EnumToBeRemoved { + A, + B + } + + // enum ability mismatch (add) + public enum EnumAbilityMismatchAdd { + A, + } + + // enum ability mismatch (remove) + public enum EnumAbilityMismatchRemove has copy { + A, + } + + // enum ability mismatch (change) + public enum EnumAbilityMismatchChange has copy { + A, + } + + // enum new variant + public enum EnumNewVariant { + A, + B, + C + } + + // enum variant missing + public enum EnumVariantMissing { + A, + B, + } + + // function missing public + public fun function_to_have_public_removed() {} + + // function missing friend + public(package) fun function_to_have_friend_removed() {} + + // function missing entry + + + // function signature mismatch (add) + public fun function_add_arg() {} + + // function signature mismatch (remove) + public fun function_remove_arg(a: u64) {} + + // function signature mismatch (change) + public fun function_change_arg(a: u64) {} +} + diff --git a/crates/sui/src/unit_tests/fixtures/upgrade_errors/all_v2/Move.toml b/crates/sui/src/unit_tests/fixtures/upgrade_errors/all_v2/Move.toml new file mode 100644 index 0000000000000..70f14c105d35c --- /dev/null +++ b/crates/sui/src/unit_tests/fixtures/upgrade_errors/all_v2/Move.toml @@ -0,0 +1,6 @@ +[package] +name = "upgrades" +edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move + +[addresses] +upgrades = "0x0" \ No newline at end of file diff --git a/crates/sui/src/unit_tests/fixtures/upgrade_errors/all_v2/sources/UpgradeErrors.move b/crates/sui/src/unit_tests/fixtures/upgrade_errors/all_v2/sources/UpgradeErrors.move new file mode 100644 index 0000000000000..788cd34148cd6 --- /dev/null +++ b/crates/sui/src/unit_tests/fixtures/upgrade_errors/all_v2/sources/UpgradeErrors.move @@ -0,0 +1,89 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +/// Module: UpgradeErrors + +#[allow(unused_field)] +module upgrades::upgrades { + // struct missing + // public struct StructToBeRemoved {} + + // struct ability mismatch (add) + public struct StructAbilityMismatchAdd has copy {} // added the copy ability where none existed + + // struct field mismatch (remove) + public struct StructAbilityMismatchRemove {} // removed the copy ability + + // struct field mismatch (change) + public struct StructAbilityMismatchChange has drop {} // changed from drop to copy + + // struct type param mismatch + public struct StructTypeParamMismatch { a: T } // changed S to T + + // struct field mismatch (add) + public struct StructFieldMismatchAdd { + a: u64, + b: u64, + c: u64, // added + } + + // struct field mismatch (remove) + public struct StructFieldMismatchRemove { + a: u64, + // removed b: u64 + } + + // struct field mismatch (change) + public struct StructFieldMismatchChange { + a: u64, + b: u8 // changed b from u64 to u8 + } + + // enum missing + // public enum EnumToBeRemoved {} + + // enum ability mismatch (add) + public enum EnumAbilityMismatchAdd has copy { + A, + } + + // enum ability mismatch (remove) + public enum EnumAbilityMismatchRemove { + A, + } + + // enum ability mismatch (change) + public enum EnumAbilityMismatchChange has drop { + A, + } + + // enum new variant + public enum EnumNewVariant { + A, + B, + C, + D // new variant + } + + // enum variant missing + public enum EnumVariantMissing { + A, + // remove B, + } + + // function missing public + fun function_to_have_public_removed() {} + + // function missing friend + fun function_to_have_friend_removed() {} + + // function missing entry + + // function signature mismatch (add) + public fun function_add_arg(a: u64) {} + + // function signature mismatch (remove) + public fun function_remove_arg() {} + + // function signature mismatch (change) + public fun function_change_arg(a: u8) {} // now has u8 instead of u64 +} diff --git a/crates/sui/src/unit_tests/fixtures/upgrade_errors/entry_linking_v1/Move.toml b/crates/sui/src/unit_tests/fixtures/upgrade_errors/entry_linking_v1/Move.toml new file mode 100644 index 0000000000000..39cc2752ab46d --- /dev/null +++ b/crates/sui/src/unit_tests/fixtures/upgrade_errors/entry_linking_v1/Move.toml @@ -0,0 +1,6 @@ +[package] +name = "upgrades" +edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move + +[addresses] +upgrades = "0x0" diff --git a/crates/sui/src/unit_tests/fixtures/upgrade_errors/entry_linking_v1/sources/UpgradeErrors.move b/crates/sui/src/unit_tests/fixtures/upgrade_errors/entry_linking_v1/sources/UpgradeErrors.move new file mode 100644 index 0000000000000..626e013bc3218 --- /dev/null +++ b/crates/sui/src/unit_tests/fixtures/upgrade_errors/entry_linking_v1/sources/UpgradeErrors.move @@ -0,0 +1,10 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// Module: UpgradeErrors + +#[allow(unused_field)] +module upgrades::upgrades { + entry fun entry_to_be_removed() {} +} + diff --git a/crates/sui/src/unit_tests/fixtures/upgrade_errors/entry_linking_v2/Move.toml b/crates/sui/src/unit_tests/fixtures/upgrade_errors/entry_linking_v2/Move.toml new file mode 100644 index 0000000000000..39cc2752ab46d --- /dev/null +++ b/crates/sui/src/unit_tests/fixtures/upgrade_errors/entry_linking_v2/Move.toml @@ -0,0 +1,6 @@ +[package] +name = "upgrades" +edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move + +[addresses] +upgrades = "0x0" diff --git a/crates/sui/src/unit_tests/fixtures/upgrade_errors/entry_linking_v2/sources/UpgradeErrors.move b/crates/sui/src/unit_tests/fixtures/upgrade_errors/entry_linking_v2/sources/UpgradeErrors.move new file mode 100644 index 0000000000000..690e45c404cbf --- /dev/null +++ b/crates/sui/src/unit_tests/fixtures/upgrade_errors/entry_linking_v2/sources/UpgradeErrors.move @@ -0,0 +1,9 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// Module: UpgradeErrors + +#[allow(unused_field)] +module upgrades::upgrades { + fun entry_to_be_removed() {} +} diff --git a/crates/sui/src/unit_tests/fixtures/upgrade_errors/friend_linking_v1/Move.toml b/crates/sui/src/unit_tests/fixtures/upgrade_errors/friend_linking_v1/Move.toml new file mode 100644 index 0000000000000..39cc2752ab46d --- /dev/null +++ b/crates/sui/src/unit_tests/fixtures/upgrade_errors/friend_linking_v1/Move.toml @@ -0,0 +1,6 @@ +[package] +name = "upgrades" +edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move + +[addresses] +upgrades = "0x0" diff --git a/crates/sui/src/unit_tests/fixtures/upgrade_errors/friend_linking_v1/sources/UpgradeErrors.move b/crates/sui/src/unit_tests/fixtures/upgrade_errors/friend_linking_v1/sources/UpgradeErrors.move new file mode 100644 index 0000000000000..77beb53e4df95 --- /dev/null +++ b/crates/sui/src/unit_tests/fixtures/upgrade_errors/friend_linking_v1/sources/UpgradeErrors.move @@ -0,0 +1,15 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// Module: UpgradeErrors + +#[allow(unused_field)] +module upgrades::upgrades { + fun call_friend() { + upgrades::upgrades_friend::friend_to_be_dropped(); + } +} + +module upgrades::upgrades_friend { + public(package) fun friend_to_be_dropped() {} +} \ No newline at end of file diff --git a/crates/sui/src/unit_tests/fixtures/upgrade_errors/friend_linking_v2/Move.toml b/crates/sui/src/unit_tests/fixtures/upgrade_errors/friend_linking_v2/Move.toml new file mode 100644 index 0000000000000..39cc2752ab46d --- /dev/null +++ b/crates/sui/src/unit_tests/fixtures/upgrade_errors/friend_linking_v2/Move.toml @@ -0,0 +1,6 @@ +[package] +name = "upgrades" +edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move + +[addresses] +upgrades = "0x0" diff --git a/crates/sui/src/unit_tests/fixtures/upgrade_errors/friend_linking_v2/sources/UpgradeErrors.move b/crates/sui/src/unit_tests/fixtures/upgrade_errors/friend_linking_v2/sources/UpgradeErrors.move new file mode 100644 index 0000000000000..f412c97e05f3b --- /dev/null +++ b/crates/sui/src/unit_tests/fixtures/upgrade_errors/friend_linking_v2/sources/UpgradeErrors.move @@ -0,0 +1,12 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// Module: UpgradeErrors + +#[allow(unused_field)] +module upgrades::upgrades { + fun call_friend() {} +} + + +module upgrades::upgrades_friend {} diff --git a/crates/sui/src/unit_tests/fixtures/upgrade_errors/struct_missing_v1/Move.toml b/crates/sui/src/unit_tests/fixtures/upgrade_errors/struct_missing_v1/Move.toml new file mode 100644 index 0000000000000..39cc2752ab46d --- /dev/null +++ b/crates/sui/src/unit_tests/fixtures/upgrade_errors/struct_missing_v1/Move.toml @@ -0,0 +1,6 @@ +[package] +name = "upgrades" +edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move + +[addresses] +upgrades = "0x0" diff --git a/crates/sui/src/unit_tests/fixtures/upgrade_errors/struct_missing_v1/sources/UpgradeErrors.move b/crates/sui/src/unit_tests/fixtures/upgrade_errors/struct_missing_v1/sources/UpgradeErrors.move new file mode 100644 index 0000000000000..70db96559ad50 --- /dev/null +++ b/crates/sui/src/unit_tests/fixtures/upgrade_errors/struct_missing_v1/sources/UpgradeErrors.move @@ -0,0 +1,13 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// Module: UpgradeErrors + +#[allow(unused_field)] +module upgrades::upgrades { + // struct missing + public struct StructToBeRemoved { + b: u64 + } +} + diff --git a/crates/sui/src/unit_tests/fixtures/upgrade_errors/struct_missing_v2/Move.toml b/crates/sui/src/unit_tests/fixtures/upgrade_errors/struct_missing_v2/Move.toml new file mode 100644 index 0000000000000..70f14c105d35c --- /dev/null +++ b/crates/sui/src/unit_tests/fixtures/upgrade_errors/struct_missing_v2/Move.toml @@ -0,0 +1,6 @@ +[package] +name = "upgrades" +edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move + +[addresses] +upgrades = "0x0" \ No newline at end of file diff --git a/crates/sui/src/unit_tests/fixtures/upgrade_errors/struct_missing_v2/sources/UpgradeErrors.move b/crates/sui/src/unit_tests/fixtures/upgrade_errors/struct_missing_v2/sources/UpgradeErrors.move new file mode 100644 index 0000000000000..542e2edca5b60 --- /dev/null +++ b/crates/sui/src/unit_tests/fixtures/upgrade_errors/struct_missing_v2/sources/UpgradeErrors.move @@ -0,0 +1,11 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// Module: UpgradeErrors + +#[allow(unused_field)] +module upgrades::upgrades { + // struct missing + // public struct StructToBeRemoved {} +} + diff --git a/crates/sui/src/unit_tests/snapshots/sui__upgrade_compatibility__upgrade_compatibility_tests__all_fail.snap b/crates/sui/src/unit_tests/snapshots/sui__upgrade_compatibility__upgrade_compatibility_tests__all_fail.snap new file mode 100644 index 0000000000000..f75df286256ed --- /dev/null +++ b/crates/sui/src/unit_tests/snapshots/sui__upgrade_compatibility__upgrade_compatibility_tests__all_fail.snap @@ -0,0 +1,23 @@ +--- +source: crates/sui/src/unit_tests/upgrade_compatibility_tests.rs +expression: err.to_string() +--- +Upgrade compatibility check failed with the following errors: +- StructAbilityMismatch { name: Identifier("StructAbilityMismatchAdd"), old_struct: Struct { abilities: [], type_parameters: [], fields: [Field { name: Identifier("dummy_field"), type_: Bool }] }, new_struct: Struct { abilities: [Copy, ], type_parameters: [], fields: [Field { name: Identifier("dummy_field"), type_: Bool }] } } +- StructAbilityMismatch { name: Identifier("StructAbilityMismatchChange"), old_struct: Struct { abilities: [Copy, ], type_parameters: [], fields: [Field { name: Identifier("dummy_field"), type_: Bool }] }, new_struct: Struct { abilities: [Drop, ], type_parameters: [], fields: [Field { name: Identifier("dummy_field"), type_: Bool }] } } +- StructAbilityMismatch { name: Identifier("StructAbilityMismatchRemove"), old_struct: Struct { abilities: [Copy, ], type_parameters: [], fields: [Field { name: Identifier("dummy_field"), type_: Bool }] }, new_struct: Struct { abilities: [], type_parameters: [], fields: [Field { name: Identifier("dummy_field"), type_: Bool }] } } +- StructFieldMismatch { name: Identifier("StructFieldMismatchAdd"), old_struct: Struct { abilities: [], type_parameters: [], fields: [Field { name: Identifier("a"), type_: U64 }, Field { name: Identifier("b"), type_: U64 }] }, new_struct: Struct { abilities: [], type_parameters: [], fields: [Field { name: Identifier("a"), type_: U64 }, Field { name: Identifier("b"), type_: U64 }, Field { name: Identifier("c"), type_: U64 }] } } +- StructFieldMismatch { name: Identifier("StructFieldMismatchChange"), old_struct: Struct { abilities: [], type_parameters: [], fields: [Field { name: Identifier("a"), type_: U64 }, Field { name: Identifier("b"), type_: U64 }] }, new_struct: Struct { abilities: [], type_parameters: [], fields: [Field { name: Identifier("a"), type_: U64 }, Field { name: Identifier("b"), type_: U8 }] } } +- StructFieldMismatch { name: Identifier("StructFieldMismatchRemove"), old_struct: Struct { abilities: [], type_parameters: [], fields: [Field { name: Identifier("a"), type_: U64 }, Field { name: Identifier("b"), type_: U64 }] }, new_struct: Struct { abilities: [], type_parameters: [], fields: [Field { name: Identifier("a"), type_: U64 }] } } +- StructMissing { name: Identifier("StructToBeRemoved"), old_struct: Struct { abilities: [], type_parameters: [], fields: [Field { name: Identifier("b"), type_: U64 }] } } +- StructTypeParamMismatch { name: Identifier("StructTypeParamMismatch"), old_struct: Struct { abilities: [], type_parameters: [DatatypeTyParameter { constraints: [], is_phantom: false }, DatatypeTyParameter { constraints: [], is_phantom: false }], fields: [Field { name: Identifier("a"), type_: TypeParameter(0) }] }, new_struct: Struct { abilities: [], type_parameters: [DatatypeTyParameter { constraints: [], is_phantom: false }], fields: [Field { name: Identifier("a"), type_: TypeParameter(0) }] } } +- EnumAbilityMismatch { name: Identifier("EnumAbilityMismatchAdd"), old_enum: Enum { abilities: [], type_parameters: [], variants: [Variant { name: Identifier("A"), fields: [] }] }, new_enum: Enum { abilities: [Copy, ], type_parameters: [], variants: [Variant { name: Identifier("A"), fields: [] }] } } +- EnumAbilityMismatch { name: Identifier("EnumAbilityMismatchChange"), old_enum: Enum { abilities: [Copy, ], type_parameters: [], variants: [Variant { name: Identifier("A"), fields: [] }] }, new_enum: Enum { abilities: [Drop, ], type_parameters: [], variants: [Variant { name: Identifier("A"), fields: [] }] } } +- EnumAbilityMismatch { name: Identifier("EnumAbilityMismatchRemove"), old_enum: Enum { abilities: [Copy, ], type_parameters: [], variants: [Variant { name: Identifier("A"), fields: [] }] }, new_enum: Enum { abilities: [], type_parameters: [], variants: [Variant { name: Identifier("A"), fields: [] }] } } +- EnumNewVariant { name: Identifier("EnumNewVariant"), old_enum: Enum { abilities: [], type_parameters: [], variants: [Variant { name: Identifier("A"), fields: [] }, Variant { name: Identifier("B"), fields: [] }, Variant { name: Identifier("C"), fields: [] }] }, new_enum: Enum { abilities: [], type_parameters: [], variants: [Variant { name: Identifier("A"), fields: [] }, Variant { name: Identifier("B"), fields: [] }, Variant { name: Identifier("C"), fields: [] }, Variant { name: Identifier("D"), fields: [] }] } } +- EnumMissing { name: Identifier("EnumToBeRemoved"), old_enum: Enum { abilities: [], type_parameters: [], variants: [Variant { name: Identifier("A"), fields: [] }, Variant { name: Identifier("B"), fields: [] }] } } +- EnumVariantMissing { name: Identifier("EnumVariantMissing"), old_enum: Enum { abilities: [], type_parameters: [], variants: [Variant { name: Identifier("A"), fields: [] }, Variant { name: Identifier("B"), fields: [] }] }, tag: 1 } +- FunctionSignatureMismatch { name: Identifier("function_add_arg"), old_function: Function { visibility: Public, is_entry: false, type_parameters: [], parameters: [], return_: [], code: [Ret] }, new_function: Function { visibility: Public, is_entry: false, type_parameters: [], parameters: [U64], return_: [], code: [Ret] } } +- FunctionSignatureMismatch { name: Identifier("function_change_arg"), old_function: Function { visibility: Public, is_entry: false, type_parameters: [], parameters: [U64], return_: [], code: [Ret] }, new_function: Function { visibility: Public, is_entry: false, type_parameters: [], parameters: [U8], return_: [], code: [Ret] } } +- FunctionSignatureMismatch { name: Identifier("function_remove_arg"), old_function: Function { visibility: Public, is_entry: false, type_parameters: [], parameters: [U64], return_: [], code: [Ret] }, new_function: Function { visibility: Public, is_entry: false, type_parameters: [], parameters: [], return_: [], code: [Ret] } } +- FunctionLostPublicVisibility { name: Identifier("function_to_have_public_removed"), old_function: Function { visibility: Public, is_entry: false, type_parameters: [], parameters: [], return_: [], code: [Ret] } } diff --git a/crates/sui/src/unit_tests/snapshots/sui__upgrade_compatibility__upgrade_compatibility_tests__struct_missing.snap b/crates/sui/src/unit_tests/snapshots/sui__upgrade_compatibility__upgrade_compatibility_tests__struct_missing.snap new file mode 100644 index 0000000000000..faa586a1414ad --- /dev/null +++ b/crates/sui/src/unit_tests/snapshots/sui__upgrade_compatibility__upgrade_compatibility_tests__struct_missing.snap @@ -0,0 +1,6 @@ +--- +source: crates/sui/src/unit_tests/upgrade_compatibility_tests.rs +expression: err.to_string() +--- +Upgrade compatibility check failed with the following errors: +- StructMissing { name: Identifier("StructToBeRemoved"), old_struct: Struct { abilities: [], type_parameters: [], fields: [Field { name: Identifier("b"), type_: U64 }] } } diff --git a/crates/sui/src/unit_tests/upgrade_compatibility_tests.rs b/crates/sui/src/unit_tests/upgrade_compatibility_tests.rs new file mode 100644 index 0000000000000..1f0e38eae9995 --- /dev/null +++ b/crates/sui/src/unit_tests/upgrade_compatibility_tests.rs @@ -0,0 +1,65 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::upgrade_compatibility::compare_packages; +use insta::assert_snapshot; +use move_binary_format::CompiledModule; +use std::path::PathBuf; +use sui_move_build::BuildConfig; + +#[test] +fn test_all_fail() { + let (pkg_v1, pkg_v2) = get_packages("all"); + + let result = compare_packages(pkg_v1, pkg_v2); + assert!(result.is_err()); + let err = result.unwrap_err(); + + assert_snapshot!(err.to_string()); +} + +#[test] +fn test_struct_missing() { + let (pkg_v1, pkg_v2) = get_packages("struct_missing"); + let result = compare_packages(pkg_v1, pkg_v2); + + assert!(result.is_err()); + let err = result.unwrap_err(); + assert_snapshot!(err.to_string()); +} + +#[test] +fn test_friend_link_ok() { + let (pkg_v1, pkg_v2) = get_packages("friend_linking"); + // upgrade compatibility ignores friend linking + assert!(compare_packages(pkg_v1, pkg_v2).is_ok()); +} + +#[test] +fn test_entry_linking_ok() { + let (pkg_v1, pkg_v2) = get_packages("entry_linking"); + // upgrade compatibility ignores entry linking + assert!(compare_packages(pkg_v1, pkg_v2).is_ok()); +} + +fn get_packages(name: &str) -> (Vec, Vec) { + let mut path: PathBuf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("src/unit_tests/fixtures/upgrade_errors/"); + path.push(format!("{}_v1", name)); + + let pkg_v1 = BuildConfig::new_for_testing() + .build(&path) + .unwrap() + .into_modules(); + + let mut path: PathBuf = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push("src/unit_tests/fixtures/upgrade_errors/"); + path.push(format!("{}_v2", name)); + + let pkg_v2 = BuildConfig::new_for_testing() + .build(&path) + .unwrap() + .into_modules(); + + (pkg_v1, pkg_v2) +} diff --git a/crates/sui/src/upgrade_compatibility.rs b/crates/sui/src/upgrade_compatibility.rs new file mode 100644 index 0000000000000..bad3fca2c1a5d --- /dev/null +++ b/crates/sui/src/upgrade_compatibility.rs @@ -0,0 +1,413 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[path = "unit_tests/upgrade_compatibility_tests.rs"] +#[cfg(test)] +mod upgrade_compatibility_tests; + +use anyhow::{anyhow, Context, Error}; +use std::collections::HashMap; + +use move_binary_format::{ + compatibility::Compatibility, + compatibility_mode::CompatibilityMode, + file_format::Visibility, + normalized::{Enum, Function, Module, Struct}, + CompiledModule, +}; +use move_core_types::{ + account_address::AccountAddress, + identifier::{IdentStr, Identifier}, +}; +use sui_json_rpc_types::{SuiObjectDataOptions, SuiRawData}; +use sui_protocol_config::ProtocolConfig; +use sui_sdk::SuiClient; +use sui_types::{base_types::ObjectID, execution_config_utils::to_binary_config}; + +/// Errors that can occur during upgrade compatibility checks. +/// one-to-one related to the underlying trait functions see: [`CompatibilityMode`] +#[derive(Debug)] +pub(crate) enum UpgradeCompatibilityModeError { + StructMissing { + name: Identifier, + old_struct: Struct, + }, + StructAbilityMismatch { + name: Identifier, + old_struct: Struct, + new_struct: Struct, + }, + StructTypeParamMismatch { + name: Identifier, + old_struct: Struct, + new_struct: Struct, + }, + StructFieldMismatch { + name: Identifier, + old_struct: Struct, + new_struct: Struct, + }, + EnumMissing { + name: Identifier, + old_enum: Enum, + }, + EnumAbilityMismatch { + name: Identifier, + old_enum: Enum, + new_enum: Enum, + }, + EnumTypeParamMismatch { + name: Identifier, + old_enum: Enum, + new_enum: Enum, + }, + EnumNewVariant { + name: Identifier, + old_enum: Enum, + new_enum: Enum, + }, + EnumVariantMissing { + name: Identifier, + old_enum: Enum, + tag: usize, + }, + EnumVariantMismatch { + name: Identifier, + old_enum: Enum, + new_enum: Enum, + tag: usize, + }, + FunctionMissingPublic { + name: Identifier, + old_function: Function, + }, + FunctionMissingEntry { + name: Identifier, + old_function: Function, + }, + FunctionSignatureMismatch { + name: Identifier, + old_function: Function, + new_function: Function, + }, + FunctionLostPublicVisibility { + name: Identifier, + old_function: Function, + }, + FunctionEntryCompatibility { + name: Identifier, + old_function: Function, + new_function: Function, + }, +} + +impl UpgradeCompatibilityModeError { + fn breaks_compatibility(&self, compatability: &Compatibility) -> bool { + match self { + UpgradeCompatibilityModeError::StructAbilityMismatch { .. } + | UpgradeCompatibilityModeError::StructTypeParamMismatch { .. } + | UpgradeCompatibilityModeError::EnumAbilityMismatch { .. } + | UpgradeCompatibilityModeError::EnumTypeParamMismatch { .. } + | UpgradeCompatibilityModeError::FunctionMissingPublic { .. } + | UpgradeCompatibilityModeError::FunctionLostPublicVisibility { .. } => true, + + UpgradeCompatibilityModeError::StructFieldMismatch { .. } + | UpgradeCompatibilityModeError::EnumVariantMissing { .. } + | UpgradeCompatibilityModeError::EnumVariantMismatch { .. } => { + compatability.check_datatype_layout + } + + UpgradeCompatibilityModeError::StructMissing { .. } + | UpgradeCompatibilityModeError::EnumMissing { .. } => true, + + UpgradeCompatibilityModeError::FunctionSignatureMismatch { old_function, .. } => { + if old_function.visibility == Visibility::Public { + return true; + } + if old_function.is_entry { + compatability.check_private_entry_linking + } else { + false + } + } + + UpgradeCompatibilityModeError::FunctionMissingEntry { .. } + | UpgradeCompatibilityModeError::FunctionEntryCompatibility { .. } => { + compatability.check_private_entry_linking + } + UpgradeCompatibilityModeError::EnumNewVariant { .. } => { + compatability.check_datatype_layout + } + } + } +} + +/// A compatibility mode that collects errors as a vector of enums which describe the error causes +#[derive(Default)] +pub(crate) struct CliCompatibilityMode { + errors: Vec, +} + +impl CompatibilityMode for CliCompatibilityMode { + type Error = anyhow::Error; + // ignored, address is not populated pre-tx + fn module_id_mismatch( + &mut self, + _old_addr: &AccountAddress, + _old_name: &IdentStr, + _new_addr: &AccountAddress, + _new_name: &IdentStr, + ) { + } + + fn struct_missing(&mut self, name: &Identifier, old_struct: &Struct) { + self.errors + .push(UpgradeCompatibilityModeError::StructMissing { + name: name.clone(), + old_struct: old_struct.clone(), + }); + } + + fn struct_ability_mismatch( + &mut self, + name: &Identifier, + old_struct: &Struct, + new_struct: &Struct, + ) { + self.errors + .push(UpgradeCompatibilityModeError::StructAbilityMismatch { + name: name.clone(), + old_struct: old_struct.clone(), + new_struct: new_struct.clone(), + }); + } + + fn struct_type_param_mismatch( + &mut self, + name: &Identifier, + old_struct: &Struct, + new_struct: &Struct, + ) { + self.errors + .push(UpgradeCompatibilityModeError::StructTypeParamMismatch { + name: name.clone(), + old_struct: old_struct.clone(), + new_struct: new_struct.clone(), + }); + } + + fn struct_field_mismatch( + &mut self, + name: &Identifier, + old_struct: &Struct, + new_struct: &Struct, + ) { + self.errors + .push(UpgradeCompatibilityModeError::StructFieldMismatch { + name: name.clone(), + old_struct: old_struct.clone(), + new_struct: new_struct.clone(), + }); + } + + fn enum_missing(&mut self, name: &Identifier, old_enum: &Enum) { + self.errors + .push(UpgradeCompatibilityModeError::EnumMissing { + name: name.clone(), + old_enum: old_enum.clone(), + }); + } + + fn enum_ability_mismatch(&mut self, name: &Identifier, old_enum: &Enum, new_enum: &Enum) { + self.errors + .push(UpgradeCompatibilityModeError::EnumAbilityMismatch { + name: name.clone(), + old_enum: old_enum.clone(), + new_enum: new_enum.clone(), + }); + } + + fn enum_type_param_mismatch(&mut self, name: &Identifier, old_enum: &Enum, new_enum: &Enum) { + self.errors + .push(UpgradeCompatibilityModeError::EnumTypeParamMismatch { + name: name.clone(), + old_enum: old_enum.clone(), + new_enum: new_enum.clone(), + }); + } + + fn enum_new_variant(&mut self, name: &Identifier, old_enum: &Enum, new_enum: &Enum) { + self.errors + .push(UpgradeCompatibilityModeError::EnumNewVariant { + name: name.clone(), + old_enum: old_enum.clone(), + new_enum: new_enum.clone(), + }); + } + + fn enum_variant_missing(&mut self, name: &Identifier, old_enum: &Enum, tag: usize) { + self.errors + .push(UpgradeCompatibilityModeError::EnumVariantMissing { + name: name.clone(), + old_enum: old_enum.clone(), + tag, + }); + } + + fn enum_variant_mismatch( + &mut self, + name: &Identifier, + old_enum: &Enum, + new_enum: &Enum, + variant_idx: usize, + ) { + self.errors + .push(UpgradeCompatibilityModeError::EnumVariantMismatch { + name: name.clone(), + old_enum: old_enum.clone(), + new_enum: new_enum.clone(), + tag: variant_idx, + }); + } + + fn function_missing_public(&mut self, name: &Identifier, old_function: &Function) { + self.errors + .push(UpgradeCompatibilityModeError::FunctionMissingPublic { + name: name.clone(), + old_function: old_function.clone(), + }); + } + + fn function_missing_entry(&mut self, name: &Identifier, old_function: &Function) { + self.errors + .push(UpgradeCompatibilityModeError::FunctionMissingEntry { + name: name.clone(), + old_function: old_function.clone(), + }); + } + + fn function_signature_mismatch( + &mut self, + name: &Identifier, + old_function: &Function, + new_function: &Function, + ) { + self.errors + .push(UpgradeCompatibilityModeError::FunctionSignatureMismatch { + name: name.clone(), + old_function: old_function.clone(), + new_function: new_function.clone(), + }); + } + + fn function_lost_public_visibility(&mut self, name: &Identifier, old_function: &Function) { + self.errors.push( + UpgradeCompatibilityModeError::FunctionLostPublicVisibility { + name: name.clone(), + old_function: old_function.clone(), + }, + ); + } + + fn function_entry_compatibility( + &mut self, + name: &Identifier, + old_function: &Function, + new_function: &Function, + ) { + self.errors + .push(UpgradeCompatibilityModeError::FunctionEntryCompatibility { + name: name.clone(), + old_function: old_function.clone(), + new_function: new_function.clone(), + }); + } + + fn finish(&self, compatability: &Compatibility) -> Result<(), Self::Error> { + let errors: Vec = self + .errors + .iter() + .filter(|e| e.breaks_compatibility(compatability)) + .map(|e| format!("- {:?}", e)) + .collect(); + + if !errors.is_empty() { + return Err(anyhow!( + "Upgrade compatibility check failed with the following errors:\n{}", + errors.join("\n") + )); + } + Ok(()) + } +} + +/// Check the upgrade compatibility of a new package with an existing on-chain package. +pub(crate) async fn check_compatibility( + client: &SuiClient, + package_id: ObjectID, + compiled_modules: &[Vec], + protocol_config: ProtocolConfig, +) -> Result<(), Error> { + let new_modules = compiled_modules + .iter() + .map(|b| CompiledModule::deserialize_with_config(b, &to_binary_config(&protocol_config))) + .collect::, _>>() + .context("Unable to to deserialize compiled module")?; + + let existing_obj_read = client + .read_api() + .get_object_with_options(package_id, SuiObjectDataOptions::new().with_bcs()) + .await + .context("Unable to get existing package")?; + + let existing_obj = existing_obj_read + .into_object() + .context("Unable to get existing package")? + .bcs + .ok_or_else(|| anyhow!("Unable to read object"))?; + + let existing_package = match existing_obj { + SuiRawData::Package(pkg) => Ok(pkg), + SuiRawData::MoveObject(_) => Err(anyhow!("Object found when package expected")), + }?; + + let existing_modules = existing_package + .module_map + .iter() + .map(|m| CompiledModule::deserialize_with_config(m.1, &to_binary_config(&protocol_config))) + .collect::, _>>() + .context("Unable to get existing package")?; + + compare_packages(existing_modules, new_modules) +} + +fn compare_packages( + existing_modules: Vec, + new_modules: Vec, +) -> Result<(), Error> { + // create a map from the new modules + let new_modules_map: HashMap = new_modules + .iter() + .map(|m| (m.self_id().name().to_owned(), m.clone())) + .collect(); + + // for each existing find the new one run compatibility check + for existing_module in existing_modules { + let name = existing_module.self_id().name().to_owned(); + + // find the new module with the same name + match new_modules_map.get(&name) { + Some(new_module) => { + Compatibility::upgrade_check().check_with_mode::( + &Module::new(&existing_module), + &Module::new(new_module), + )?; + } + None => { + Err(anyhow!("Module {} is missing from the package", name))?; + } + } + } + + Ok(()) +} diff --git a/crates/sui/tests/cli_tests.rs b/crates/sui/tests/cli_tests.rs index c02a35dead329..7073cd7d76ed2 100644 --- a/crates/sui/tests/cli_tests.rs +++ b/crates/sui/tests/cli_tests.rs @@ -3762,7 +3762,7 @@ async fn test_gas_estimation() -> Result<(), anyhow::Error> { let sender = context.active_address().unwrap(); let tx_builder = client.transaction_builder(); let tx_kind = tx_builder.transfer_sui_tx_kind(address2, Some(amount)); - let gas_estimate = estimate_gas_budget(&client, sender, tx_kind, rgp, None, None).await; + let gas_estimate = estimate_gas_budget(context, sender, tx_kind, rgp, None, None).await; assert!(gas_estimate.is_ok()); let transfer_sui_cmd = SuiClientCommands::TransferSui { diff --git a/crates/suins-indexer/Cargo.toml b/crates/suins-indexer/Cargo.toml index 0737d1ae4e1ce..c08d63938f84a 100644 --- a/crates/suins-indexer/Cargo.toml +++ b/crates/suins-indexer/Cargo.toml @@ -34,6 +34,11 @@ sui-json-rpc.workspace = true dotenvy = "0.15" move-core-types.workspace = true mysten-service.workspace = true +rustls.workspace = true +webpki-roots = "0.26.3" +tokio-postgres-rustls = "0.12.0" +tokio-postgres = "0.7.12" +futures-util = "0.3.30" [dev-dependencies] rand.workspace = true diff --git a/crates/suins-indexer/README.md b/crates/suins-indexer/README.md new file mode 100644 index 0000000000000..fcce744fdf949 --- /dev/null +++ b/crates/suins-indexer/README.md @@ -0,0 +1,12 @@ +# SuiNS Indexer + +This indexer is used to cache the on-chain state of the SuiNS registry to a database, +in order to unlock more composite queries (e.g. query all subnames for a given name). + +## Setting up locally + +Copy `.env.sample` to `.env` and fill the variables (for DB connection). +This sample environment setup works with Mainnet types. + +- `BACKFILL_PROGRESS_FILE_PATH`: Expects a file in the format `{ "suins_indexing": }` +- `CHECKPOINTS_DIR`: Make sure an empty directory exists on that path. diff --git a/crates/suins-indexer/src/lib.rs b/crates/suins-indexer/src/lib.rs index 0fa2b89d72afb..e7bfa49c84951 100644 --- a/crates/suins-indexer/src/lib.rs +++ b/crates/suins-indexer/src/lib.rs @@ -8,9 +8,14 @@ pub mod schema; use dotenvy::dotenv; use std::env; +use diesel::{ConnectionError, ConnectionResult}; use diesel_async::pooled_connection::bb8::Pool; use diesel_async::pooled_connection::AsyncDieselConnectionManager; +use diesel_async::pooled_connection::ManagerConfig; use diesel_async::AsyncPgConnection; +use futures_util::future::BoxFuture; +use futures_util::FutureExt; +use std::time::Duration; pub type PgConnectionPool = diesel_async::pooled_connection::bb8::Pool; @@ -18,16 +23,91 @@ pub type PgPoolConnection<'a> = diesel_async::pooled_connection::bb8::PooledConnection<'a, AsyncPgConnection>; pub async fn get_connection_pool() -> PgConnectionPool { - //Pool { dotenv().ok(); let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); - let manager = - AsyncDieselConnectionManager::::new(database_url); + let mut config = ManagerConfig::default(); + config.custom_setup = Box::new(establish_connection); + + let manager = AsyncDieselConnectionManager::::new_with_config( + database_url, + config, + ); Pool::builder() + .connection_timeout(Duration::from_secs(30)) .build(manager) .await .expect("Could not build Postgres DB connection pool") } + +fn establish_connection(config: &str) -> BoxFuture> { + let fut = async { + // We first set up the way we want rustls to work. + let rustls_config = rustls::ClientConfig::builder() + .dangerous() + .with_custom_certificate_verifier(std::sync::Arc::new(SkipServerCertCheck)) + .with_no_client_auth(); + let tls = tokio_postgres_rustls::MakeRustlsConnect::new(rustls_config); + let (client, conn) = tokio_postgres::connect(config, tls) + .await + .map_err(|e| ConnectionError::BadConnection(e.to_string()))?; + tokio::spawn(async move { + if let Err(e) = conn.await { + eprintln!("Database connection: {e}"); + } + }); + AsyncPgConnection::try_from(client).await + }; + fut.boxed() +} + +fn root_certs() -> rustls::RootCertStore { + rustls::RootCertStore { + roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(), + } +} + +/// Skip performing any verification of a server's cert in order to temporarily match the default +/// behavior of libpq +#[derive(Debug)] +struct SkipServerCertCheck; + +impl rustls::client::danger::ServerCertVerifier for SkipServerCertCheck { + fn verify_server_cert( + &self, + _end_entity: &rustls::pki_types::CertificateDer<'_>, + _intermediates: &[rustls::pki_types::CertificateDer<'_>], + _server_name: &rustls::pki_types::ServerName<'_>, + _ocsp_response: &[u8], + _now: rustls::pki_types::UnixTime, + ) -> Result { + Ok(rustls::client::danger::ServerCertVerified::assertion()) + } + + fn verify_tls12_signature( + &self, + _message: &[u8], + _cert: &rustls::pki_types::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn verify_tls13_signature( + &self, + _message: &[u8], + _cert: &rustls::pki_types::CertificateDer<'_>, + _dss: &rustls::DigitallySignedStruct, + ) -> Result { + Ok(rustls::client::danger::HandshakeSignatureValid::assertion()) + } + + fn supported_verify_schemes(&self) -> Vec { + rustls::client::WebPkiServerVerifier::builder(std::sync::Arc::new(root_certs())) + .build() + .unwrap() + .supported_verify_schemes() + } +} diff --git a/crates/suins-indexer/src/main.rs b/crates/suins-indexer/src/main.rs index a42c0e327811f..0f3db957205e2 100644 --- a/crates/suins-indexer/src/main.rs +++ b/crates/suins-indexer/src/main.rs @@ -107,6 +107,7 @@ impl SuinsIndexerWorker { #[async_trait] impl Worker for SuinsIndexerWorker { + type Result = (); async fn process_checkpoint(&self, checkpoint: &CheckpointData) -> Result<()> { let checkpoint_seq_number = checkpoint.checkpoint_summary.sequence_number; let (updates, removals) = self.indexer.process_checkpoint(checkpoint); diff --git a/crates/suiop-cli/Cargo.toml b/crates/suiop-cli/Cargo.toml index 43eb0695c4d46..0c39aa9b6e3a3 100644 --- a/crates/suiop-cli/Cargo.toml +++ b/crates/suiop-cli/Cargo.toml @@ -24,6 +24,7 @@ base64.workspace = true chrono.workspace = true clap.workspace = true colored.workspace = true +crossterm = { workspace = true, features = ["event-stream"] } dirs.workspace = true docker-api.workspace = true field_names.workspace = true @@ -52,7 +53,10 @@ tracing-subscriber.workspace = true tracing.workspace = true once_cell.workspace = true futures.workspace = true +thiserror.workspace = true strsim = "0.11.1" +futures-timer = "3.0.3" + [dev-dependencies] tempfile.workspace = true diff --git a/crates/suiop-cli/src/cli/ci/image.rs b/crates/suiop-cli/src/cli/ci/image.rs index 9486cbfaf4358..fbc100acbb79b 100644 --- a/crates/suiop-cli/src/cli/ci/image.rs +++ b/crates/suiop-cli/src/cli/ci/image.rs @@ -7,8 +7,16 @@ use anyhow::Result; use chrono::{DateTime, Local, Utc}; use clap::{Parser, ValueEnum}; use colored::Colorize; +use crossterm::{ + cursor::MoveTo, + event::{Event, EventStream, KeyCode}, + terminal::{disable_raw_mode, enable_raw_mode, Clear, ClearType}, + ExecutableCommand, +}; +use futures::StreamExt; +use futures::{select, FutureExt}; use serde::{self, Deserialize, Serialize}; -use std::{fmt::Display, str::FromStr}; +use std::{fmt::Display, str::FromStr, time::Duration}; use tabled::{settings::Style, Table, Tabled}; use tracing::debug; @@ -90,7 +98,7 @@ pub enum ImageAction { /// Optional repo region, default to "us-central1" #[arg(long)] repo_region: Option, - /// Optional image name, default to "app", only used if multiple images are built within one repo + /// Optional image tags, default to "" #[arg(long)] image_tag: Option, /// Optional image name, default to "app", only used if multiple images are built within one repo @@ -117,12 +125,20 @@ pub enum ImageAction { /// Optional build args to pass to the docker build command #[arg(long)] build_args: Vec, + /// Optional flag to force build even if build pod already exists + #[arg(short = 'f', long)] + force: bool, + /// Optional flag to target the image, used for multi-stage builds + #[arg(short = 't', long)] + image_target: Option, }, #[command(name = "query")] Query { #[arg(short, long)] repo_name: String, #[arg(short, long)] + watch: bool, + #[arg(short, long)] limit: Option, }, #[command(name = "status")] @@ -160,6 +176,8 @@ struct RequestBuildRequest { memory: String, disk: String, build_args: Vec, + force: bool, + image_target: Option, } #[derive(serde::Serialize)] @@ -167,7 +185,6 @@ struct QueryBuildsRequest { repo_name: String, limit: u32, } - #[derive(serde::Serialize)] struct ImageStatusRequest { repo_name: String, @@ -280,6 +297,24 @@ struct ImageListResponse { pub images: Vec, } +async fn get_status_table(resp: reqwest::Response) -> Result { + let json_resp = resp.json::().await?; + let job_statuses = json_resp.pods.into_iter().map(|pod| { + // Parse the string into a NaiveDateTime + let start_time = utc_to_local_time(pod.start_time); + let end_time = utc_to_local_time(pod.end_time.unwrap_or("".to_string())); + + BuildInfo { + name: pod.name, + status: pod.status, + start_time, + end_time, + } + }); + let mut tabled = Table::new(job_statuses); + Ok(tabled.with(Style::rounded()).to_owned()) +} + async fn send_image_request(token: &str, action: &ImageAction) -> Result<()> { let req = generate_image_request(token, action); @@ -303,6 +338,8 @@ async fn send_image_request(token: &str, action: &ImageAction) -> Result<()> { memory: _, disk: _, build_args: _, + force: _, + image_target, } => { let ref_type = ref_type.clone().unwrap_or(RefType::Branch); let ref_val = ref_val.clone().unwrap_or("main".to_string()); @@ -313,6 +350,9 @@ async fn send_image_request(token: &str, action: &ImageAction) -> Result<()> { if !image_tag.is_empty() { image_info += &format!(":{}", image_tag); } + if !image_target.is_none() { + image_info += &format!("@{}", image_target.as_ref().unwrap()); + } println!( "Requested built image for repo: {}, ref: {}, dockerfile: {}, image: {}", repo_name.green(), @@ -330,27 +370,56 @@ async fn send_image_request(token: &str, action: &ImageAction) -> Result<()> { } ImageAction::Query { repo_name, + watch, limit: _, } => { - println!("Requested query for repo: {}", repo_name.green()); - let json_resp = resp.json::().await?; - let job_statuses = json_resp.pods.into_iter().map(|pod| { - // Parse the string into a NaiveDateTime - let start_time = utc_to_local_time(pod.start_time); - let end_time = utc_to_local_time(pod.end_time.unwrap_or("".to_string())); - - BuildInfo { - name: pod.name, - status: pod.status, - start_time, - end_time, + if !*watch { + println!("Requested query for repo: {}", repo_name.green()); + let status_table = get_status_table(resp).await?.to_string(); + println!("{}", status_table); + } else { + enable_raw_mode()?; + loop { + let mut reader = EventStream::new(); + let mut delay = futures_timer::Delay::new(Duration::from_secs(1)).fuse(); + let mut event = reader.next().fuse(); + + select! { + _ = delay => { + let req = generate_image_request(token, action); + + let resp = req.send().await?; + let status_table = get_status_table(resp).await?.to_string(); + std::io::stdout().execute(Clear(ClearType::All))?.execute(MoveTo(0,0))?; + print!("press 'q' or 'esc' to quit"); + for (i, line )in status_table.lines().enumerate() { + std::io::stdout().execute(MoveTo(0,(i + 1) as u16))?; + println!("{}", line); + } + }, + maybe_event = event => { + println!("checking event"); + match maybe_event { + Some(Ok(event)) => { + if event == Event::Key(KeyCode::Char('q').into()) { + std::io::stdout().execute(Clear(ClearType::All))?.execute(MoveTo(0,0))?; + println!("q pressed, quitting 🫡"); + break + } else if event == Event::Key(KeyCode::Esc.into()) { + std::io::stdout().execute(Clear(ClearType::All))?.execute(MoveTo(0,0))?; + println!("esc pressed, quitting 🫡"); + break; + } + + } + Some(Err(e)) => println!("Error: {:?}\r", e), + None => println!("no event"), + } + } + }; } - }); - let mut tabled = Table::new(job_statuses); - tabled.with(Style::rounded()); - - let tabled_str = tabled.to_string(); - println!("{}", tabled_str); + disable_raw_mode()?; + } } ImageAction::Status { repo_name, @@ -454,6 +523,8 @@ fn generate_image_request(token: &str, action: &ImageAction) -> reqwest::Request memory, disk, build_args, + force, + image_target, } => { let full_url = format!("{}{}", api_server, ENDPOINT); debug!("full_url: {}", full_url); @@ -500,11 +571,17 @@ fn generate_image_request(token: &str, action: &ImageAction) -> reqwest::Request memory, disk, build_args: build_args.clone(), + force: *force, + image_target: image_target.clone(), }; debug!("req body: {:?}", body); req.json(&body).headers(generate_headers_with_auth(token)) } - ImageAction::Query { repo_name, limit } => { + ImageAction::Query { + repo_name, + limit, + watch: _, + } => { let full_url = format!("{}{}", api_server, ENDPOINT); debug!("full_url: {}", full_url); let req = client.get(full_url); diff --git a/crates/suiop-cli/src/cli/ci/mod.rs b/crates/suiop-cli/src/cli/ci/mod.rs index ecbae85e75a19..dc8d6e5ccc5a6 100644 --- a/crates/suiop-cli/src/cli/ci/mod.rs +++ b/crates/suiop-cli/src/cli/ci/mod.rs @@ -21,7 +21,7 @@ pub(crate) enum CIAction { #[clap(aliases = ["k", "key"])] Keys(KeyArgs), #[clap(aliases = ["i"])] - Image(ImageArgs), + Image(Box), } pub async fn ci_cmd(args: &CIArgs) -> Result<()> { diff --git a/crates/suiop-cli/src/cli/incidents/incident.rs b/crates/suiop-cli/src/cli/incidents/incident.rs index 4d6d30056371f..5e7975eb85eab 100644 --- a/crates/suiop-cli/src/cli/incidents/incident.rs +++ b/crates/suiop-cli/src/cli/incidents/incident.rs @@ -9,7 +9,8 @@ use serde::{Deserialize, Serialize}; use super::pd::PagerDutyIncident; use super::pd::Priority; -use crate::cli::slack::{Channel, User}; +use super::user::User; +use crate::cli::slack::Channel; const DATE_FORMAT_IN: &str = "%Y-%m-%dT%H:%M:%SZ"; const DATE_FORMAT_OUT: &str = "%m/%d/%Y %H:%M"; @@ -22,7 +23,7 @@ pub struct Incident { pub created_at: Option, pub resolved_at: Option, pub html_url: String, - /// The slack users responsible for reporting + /// The users responsible for reporting #[serde(skip_deserializing)] pub poc_users: Option>, pub priority: Option, @@ -143,7 +144,11 @@ impl Incident { || "".to_string(), |u| u .iter() - .map(|u| { format!("<@{}>", u.id) }) + .map(|u| { + u.slack_user + .as_ref() + .map_or("".to_owned(), |su| format!("<@{}>", su.id)) + }) .collect::>() .join(", ") ) diff --git a/crates/suiop-cli/src/cli/incidents/mod.rs b/crates/suiop-cli/src/cli/incidents/mod.rs index 657dcd3d624e0..b1d29afc2730a 100644 --- a/crates/suiop-cli/src/cli/incidents/mod.rs +++ b/crates/suiop-cli/src/cli/incidents/mod.rs @@ -3,8 +3,10 @@ mod incident; mod jira; +pub(crate) mod notion; mod pd; mod selection; +mod user; use crate::cli::slack::Slack; use anyhow::Result; diff --git a/crates/suiop-cli/src/cli/incidents/notion.rs b/crates/suiop-cli/src/cli/incidents/notion.rs new file mode 100644 index 0000000000000..96721bbe00250 --- /dev/null +++ b/crates/suiop-cli/src/cli/incidents/notion.rs @@ -0,0 +1,225 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::cli::notion::ids::DatabaseId; +use crate::cli::notion::models::search::DatabaseQuery; +use crate::cli::notion::models::{ListResponse, Page}; +use crate::cli::notion::NotionApi; +use anyhow::{Context, Result}; +use once_cell::sync::Lazy; +use serde::{Deserialize, Serialize}; +use serde_json::json; +use std::env; +use std::str::FromStr; +use tracing::debug; + +use crate::DEBUG_MODE; + +use super::incident::Incident; + +// incident selection db +pub static INCIDENT_DB_ID: Lazy = Lazy::new(|| { + if *DEBUG_MODE { + // incident selection db for testing + DatabaseId::from_str("10e6d9dcb4e980f8ae73c4aa2da176cd").expect("Invalid Database ID") + } else { + // incident selection db for production + DatabaseId::from_str("a8da55dadb524e7db202b4dfd799d9ce").expect("Invalid Database ID") + } +}); + +// incident selection db names +pub static INCIDENT_DB_NAME: Lazy = Lazy::new(|| { + if *DEBUG_MODE { + "Incident Selection (Debug)".to_owned() + } else { + "Incident Selection".to_owned() + } +}); + +/// Macro for debugging Notion database properties. +/// +/// This macro takes two arguments: +/// - `$notion`: A reference to a Notion instance. +/// - `$prop`: The name of the property to debug. +/// +/// It retrieves the specified database, gets the property, and prints debug information +/// based on the property type. Supported property types include: +/// - MultiSelect +/// - People +/// - Date +/// - Title +/// - Checkbox +/// +/// For unsupported property types, it prints an "Unexpected property type" message. +/// +/// # Panics +/// +/// This macro will panic if: +/// - It fails to get the database. +/// - The specified property does not exist in the database. +#[allow(unused_macros)] +macro_rules! debug_prop { + ($notion:expr, $prop:expr) => { + let db = $notion + .client + .get_database(INCIDENT_DB_ID.clone()) + .await + .expect("Failed to get database"); + let prop = db.properties.get($prop).unwrap(); + match prop { + PropertyConfiguration::MultiSelect { + multi_select, + id: _, + } => { + println!("multi select property"); + println!("{:#?}", multi_select.options); + } + PropertyConfiguration::People { id: _ } => { + println!("people property"); + } + PropertyConfiguration::Date { id: _ } => { + println!("date property"); + } + PropertyConfiguration::Title { id: _ } => { + println!("title property"); + } + PropertyConfiguration::Checkbox { id: _ } => { + println!("checkbox property"); + } + _ => { + println!("Unexpected property type {:?}", prop); + } + } + }; +} + +pub struct Notion { + client: NotionApi, + token: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct NotionPerson { + pub object: String, + pub id: String, + pub name: String, + pub avatar_url: Option, + pub person: NotionPersonDetails, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct NotionPersonDetails { + pub email: String, +} +impl Notion { + pub fn new() -> Self { + let token = env::var("NOTION_API_TOKEN") + .expect("Please set the NOTION_API_TOKEN environment variable"); + let client = NotionApi::new(token.clone()).expect("Failed to create Notion API client"); + Self { client, token } + } + + /// Get all incidents from the incident selection database + #[allow(dead_code)] + pub async fn get_incident_selection_incidents(&self) -> Result> { + // Retrieve the db + self.client + .query_database(INCIDENT_DB_ID.clone(), DatabaseQuery::default()) + .await + .map_err(|e| anyhow::anyhow!(e)) + } + + /// Get all people objects from the Notion API + pub async fn get_all_people(&self) -> Result> { + let url = "https://api.notion.com/v1/users"; + let client = reqwest::Client::new(); + + let response = client + .get(url) + .header("Authorization", format!("Bearer {}", self.token)) + .header("Notion-Version", "2022-06-28") + .send() + .await + .map_err(|e| anyhow::anyhow!("Failed to send request: {}", e))?; + + if !response.status().is_success() { + return Err(anyhow::anyhow!( + "Request failed with status: {}", + response.status() + )); + } + + response + .json::() + .await + .map(|v| { + serde_json::from_value::>(v["results"].clone()) + .expect("deserializing people") + }) + .map_err(|e| anyhow::anyhow!("Failed to parse response: {}", e)) + } + + /// Get the shape of the incident selection database to understand the data model + #[allow(dead_code)] + pub async fn get_shape(self) -> Result<()> { + let db = self.client.get_database(INCIDENT_DB_ID.clone()).await?; + println!("{:#?}", db.properties); + Ok(()) + } + + /// Insert a suiop incident into the incident selection database + pub async fn insert_incident(&self, incident: Incident) -> Result<()> { + let url = "https://api.notion.com/v1/pages"; + let body = json!({ + "parent": { "database_id": INCIDENT_DB_ID.to_string() }, + "properties": { + "Name": { + "title": [{ + "text": { + "content":format!("{}: {}", incident.number, incident.title) + } + }] + }, + "link": { + "url": incident.html_url, + }, + "PoC(s)": { + "people": incident.poc_users.unwrap_or_else(|| panic!("no poc users for incident {}", incident.number)).iter().map(|u| { + json!({ + "object": "user", + "id": u.notion_user.as_ref().map(|u| u.id.clone()), + }) + }).collect::>(), + }, + } + }); + + let client = reqwest::ClientBuilder::new() + // .default_headers(headers) + .build() + .expect("failed to build reqwest client"); + let response = client + .post(url) + .header("Authorization", format!("Bearer {}", self.token)) + .header("Content-Type", "application/json") + .header("Notion-Version", "2021-05-13") + .json(&body) + .send() + .await + .context("sending insert db row")?; + + if response.status().is_success() { + debug!( + "inserted incident: {:?}", + response.text().await.context("getting response text")? + ); + Ok(()) + } else { + Err(anyhow::anyhow!( + "Failed to insert incident: {:?}", + response.text().await.context("getting response text")? + )) + } + } +} diff --git a/crates/suiop-cli/src/cli/incidents/selection.rs b/crates/suiop-cli/src/cli/incidents/selection.rs index ebeca0f532504..35a4e2a966a6d 100644 --- a/crates/suiop-cli/src/cli/incidents/selection.rs +++ b/crates/suiop-cli/src/cli/incidents/selection.rs @@ -7,16 +7,18 @@ use std::collections::HashMap; use strsim::normalized_damerau_levenshtein; use tracing::debug; +use crate::cli::incidents::notion::{Notion, INCIDENT_DB_ID, INCIDENT_DB_NAME}; +use crate::cli::incidents::user::User; use crate::cli::lib::utils::day_of_week; -use crate::cli::slack::{Channel, Slack, User}; +use crate::cli::slack::{Channel, Slack}; use crate::DEBUG_MODE; use super::incident::Incident; -fn request_pocs(slack: &Slack) -> Result> { +fn request_pocs(users: Vec) -> Result> { MultiSelect::new( "Please select the users who are POCs for this incident", - slack.users.clone(), + users, ) .with_default(&[]) .prompt() @@ -30,7 +32,6 @@ fn filter_incidents_for_review(incidents: Vec, min_priority: &str) -> .trim_start_matches("P") .parse::() .expect("Parsing priority"); - println!("min_priority_u: {}", min_priority_u); incidents .into_iter() // filter on priority <= min_priority and any slack channel association @@ -46,6 +47,24 @@ fn filter_incidents_for_review(incidents: Vec, min_priority: &str) -> pub async fn review_recent_incidents(incidents: Vec) -> Result<()> { let slack = Slack::new().await; + let notion = Notion::new(); + let combined_users = notion + .get_all_people() + .await? + .into_iter() + .map(|nu| { + let slack_user = slack.users.iter().find(|su| { + su.profile + .as_ref() + .unwrap() + .email + .as_ref() + .unwrap_or(&"".to_owned()) + == &nu.person.email + }); + User::new(slack_user.cloned(), Some(nu)).expect("Failed to convert user from Notion") + }) + .collect::>(); let filtered_incidents = filter_incidents_for_review(incidents, "P2"); let mut group_map = group_by_similar_title(filtered_incidents, 0.9); let mut to_review = vec![]; @@ -74,7 +93,7 @@ pub async fn review_recent_incidents(incidents: Vec) -> Result<()> { .prompt() .expect("Unexpected response"); if ans { - let poc_users = request_pocs(&slack)?; + let poc_users = request_pocs(combined_users.clone())?; incident_group .iter_mut() .for_each(|i| i.poc_users = Some(poc_users.clone())); @@ -90,7 +109,7 @@ pub async fn review_recent_incidents(incidents: Vec) -> Result<()> { .prompt() .expect("Unexpected response"); if ans { - let poc_users = request_pocs(&slack)?; + let poc_users = request_pocs(combined_users.clone())?; incident.poc_users = Some(poc_users.clone()); to_review.push(incident.clone()); } else { @@ -142,17 +161,33 @@ Please comment in the thread to request an adjustment to the list.", } else { "incident-postmortems" }; - let ans = Confirm::new(&format!( + let send_message = Confirm::new(&format!( "Send this message to the #{} channel?", slack_channel )) .with_default(false) .prompt() .expect("Unexpected response"); - if ans { + if send_message { slack.send_message(slack_channel, &message).await?; + debug!("Message sent to #{}", slack_channel); + } + #[allow(clippy::unnecessary_to_owned)] + let insert_into_db = Confirm::new(&format!( + "Insert {} incidents into {:?} Notion database ({:?}) for review?", + to_review.len(), + INCIDENT_DB_NAME.to_string(), + INCIDENT_DB_ID.to_string() + )) + .with_default(false) + .prompt() + .expect("Unexpected response"); + if insert_into_db { + for incident in to_review.iter() { + debug!("Inserting incident into Notion: {}", incident.number); + notion.insert_incident(incident.clone()).await?; + } } - // post to https://slack.com/api/chat.postMessage with message Ok(()) } @@ -239,7 +274,7 @@ mod tests { assert_eq!(groups.len(), 3); assert_eq!(groups.get("Incident 1").unwrap().len(), 2); - assert!(groups.get("Incident 2").is_none()); + assert!(!groups.contains_key("Incident 2")); assert_eq!(groups.get("Another thing entirely").unwrap().len(), 2); assert_eq!( groups diff --git a/crates/suiop-cli/src/cli/incidents/user.rs b/crates/suiop-cli/src/cli/incidents/user.rs new file mode 100644 index 0000000000000..50071f01e7f58 --- /dev/null +++ b/crates/suiop-cli/src/cli/incidents/user.rs @@ -0,0 +1,60 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::fmt::{Display, Formatter}; + +use serde::{Deserialize, Serialize}; + +use crate::cli::slack::SlackUser; + +use super::notion::NotionPerson; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct User { + pub(crate) slack_user: Option, + pub(crate) notion_user: Option, +} + +impl User { + pub fn new(slack_user: Option, notion_user: Option) -> Option { + if slack_user.is_none() && notion_user.is_none() { + None + } else { + Some(User { + slack_user, + notion_user, + }) + } + } +} + +impl Display for User { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + let name = self + .slack_user + .as_ref() + .map(|u| { + format!( + "{} {}", + u.name.clone(), + u.profile + .as_ref() + .map(|p| format!("({})", p.email.as_ref().unwrap_or(&"".to_string()))) + .unwrap_or("".to_string()) + ) + }) + .or_else(|| self.notion_user.as_ref().map(|u| u.name.clone())); + if let Some(name) = name { + write!(f, "{}", name) + } else { + write!( + f, + "{}", + self.notion_user + .as_ref() + .expect("expected notion user") + .name + ) + } + } +} diff --git a/crates/suiop-cli/src/cli/mod.rs b/crates/suiop-cli/src/cli/mod.rs index fceb7ded495be..2111eb17153c3 100644 --- a/crates/suiop-cli/src/cli/mod.rs +++ b/crates/suiop-cli/src/cli/mod.rs @@ -6,6 +6,7 @@ pub mod docker; mod iam; mod incidents; pub mod lib; +mod notion; pub mod pulumi; pub mod service; mod slack; diff --git a/crates/suiop-cli/src/cli/notion/ids.rs b/crates/suiop-cli/src/cli/notion/ids.rs new file mode 100644 index 0000000000000..1634d03279e08 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/ids.rs @@ -0,0 +1,72 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use std::fmt::Display; +use std::fmt::Error; + +pub trait Identifier: Display { + fn value(&self) -> &str; +} +/// Meant to be a helpful trait allowing anything that can be +/// identified by the type specified in `ById`. +pub trait AsIdentifier { + fn as_id(&self) -> &ById; +} + +impl AsIdentifier for T +where + T: Identifier, +{ + fn as_id(&self) -> &T { + self + } +} + +impl AsIdentifier for &T +where + T: Identifier, +{ + fn as_id(&self) -> &T { + self + } +} + +macro_rules! identifer { + ($name:ident) => { + #[derive(serde::Serialize, serde::Deserialize, Debug, Eq, PartialEq, Hash, Clone)] + #[serde(transparent)] + pub struct $name(String); + + impl Identifier for $name { + fn value(&self) -> &str { + &self.0 + } + } + + impl std::fmt::Display for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } + } + + impl std::str::FromStr for $name { + type Err = Error; + + fn from_str(s: &str) -> Result { + Ok($name(s.to_string())) + } + } + }; +} + +identifer!(DatabaseId); +identifer!(PageId); +identifer!(BlockId); +identifer!(UserId); +identifer!(PropertyId); + +impl From for BlockId { + fn from(page_id: PageId) -> Self { + BlockId(page_id.0) + } +} diff --git a/crates/suiop-cli/src/cli/notion/mod.rs b/crates/suiop-cli/src/cli/notion/mod.rs new file mode 100644 index 0000000000000..8f16bf33b5e46 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/mod.rs @@ -0,0 +1,241 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#![allow(dead_code)] +#![allow(clippy::enum_variant_names)] +use ids::{AsIdentifier, PageId}; +use ids::{BlockId, DatabaseId}; +use models::block::Block; +use models::error::ErrorResponse; +use models::search::{DatabaseQuery, SearchRequest}; +use models::PageCreateRequest; +use models::{Database, ListResponse, Object, Page}; +use reqwest::header::{HeaderMap, HeaderValue}; +use reqwest::{header, Client, ClientBuilder, RequestBuilder}; +use tracing::Instrument; + +pub mod ids; +pub mod models; + +#[allow(unused_imports)] +pub use chrono; + +const NOTION_API_VERSION: &str = "2022-02-22"; + +/// An wrapper Error type for all errors produced by the [`NotionApi`](NotionApi) client. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Invalid Notion API Token: {}", source)] + InvalidApiToken { source: header::InvalidHeaderValue }, + + #[error("Unable to build reqwest HTTP client: {}", source)] + ErrorBuildingClient { source: reqwest::Error }, + + #[error("Error sending HTTP request: {}", source)] + RequestFailed { + #[from] + source: reqwest::Error, + }, + + #[error("Error reading response: {}", source)] + ResponseIoError { source: reqwest::Error }, + + #[error("Error parsing json response: {}", source)] + JsonParseError { source: serde_json::Error }, + + #[error("Unexpected API Response")] + UnexpectedResponse { response: Object }, + + #[error("API Error {}({}): {}", .error.code, .error.status, .error.message)] + ApiError { error: ErrorResponse }, +} + +/// An API client for Notion. +/// Create a client by using [new(api_token: String)](Self::new()). +#[derive(Clone)] +pub struct NotionApi { + client: Client, +} + +impl NotionApi { + /// Creates an instance of NotionApi. + /// May fail if the provided api_token is an improper value. + pub fn new(api_token: String) -> Result { + let mut headers = HeaderMap::new(); + headers.insert( + "Notion-Version", + HeaderValue::from_static(NOTION_API_VERSION), + ); + + let mut auth_value = HeaderValue::from_str(&format!("Bearer {}", api_token)) + .map_err(|source| Error::InvalidApiToken { source })?; + auth_value.set_sensitive(true); + headers.insert(header::AUTHORIZATION, auth_value); + + let client = ClientBuilder::new() + .default_headers(headers) + .build() + .map_err(|source| Error::ErrorBuildingClient { source })?; + + Ok(Self { client }) + } + + async fn make_json_request(&self, request: RequestBuilder) -> Result { + let request = request.build()?; + let url = request.url(); + tracing::trace!( + method = request.method().as_str(), + url = url.as_str(), + "Sending request" + ); + let json = self + .client + .execute(request) + .instrument(tracing::trace_span!("Sending request")) + .await + .map_err(|source| Error::RequestFailed { source })? + .text() + .instrument(tracing::trace_span!("Reading response")) + .await + .map_err(|source| Error::ResponseIoError { source })?; + + tracing::debug!("JSON Response: {}", json); + #[cfg(test)] + { + dbg!(serde_json::from_str::(&json) + .map_err(|source| Error::JsonParseError { source })?); + } + let result = + serde_json::from_str(&json).map_err(|source| Error::JsonParseError { source })?; + + match result { + Object::Error { error } => Err(Error::ApiError { error }), + response => Ok(response), + } + } + + /// List all the databases shared with the supplied integration token. + /// > This method is apparently deprecated/"not recommended" and + /// > [search()](Self::search()) should be used instead. + pub async fn list_databases(&self) -> Result, Error> { + let builder = self.client.get("https://api.notion.com/v1/databases"); + + match self.make_json_request(builder).await? { + Object::List { list } => Ok(list.expect_databases()?), + response => Err(Error::UnexpectedResponse { response }), + } + } + + /// Search all pages in notion. + /// `query` can either be a [SearchRequest] or a slightly more convenient + /// [NotionSearch](models::search::NotionSearch) query. + pub async fn search>( + &self, + query: T, + ) -> Result, Error> { + let result = self + .make_json_request( + self.client + .post("https://api.notion.com/v1/search") + .json(&query.into()), + ) + .await?; + + match result { + Object::List { list } => Ok(list), + response => Err(Error::UnexpectedResponse { response }), + } + } + + /// Get a database by [DatabaseId]. + pub async fn get_database>( + &self, + database_id: T, + ) -> Result { + let result = self + .make_json_request(self.client.get(format!( + "https://api.notion.com/v1/databases/{}", + database_id.as_id() + ))) + .await?; + + match result { + Object::Database { database } => Ok(database), + response => Err(Error::UnexpectedResponse { response }), + } + } + + /// Get a page by [PageId]. + pub async fn get_page>(&self, page_id: T) -> Result { + let result = self + .make_json_request(self.client.get(format!( + "https://api.notion.com/v1/pages/{}", + page_id.as_id() + ))) + .await?; + + match result { + Object::Page { page } => Ok(page), + response => Err(Error::UnexpectedResponse { response }), + } + } + + /// Creates a new page and return the created page + pub async fn create_page>(&self, page: T) -> Result { + let result = self + .make_json_request( + self.client + .post("https://api.notion.com/v1/pages") + .json(&page.into()), + ) + .await?; + + match result { + Object::Page { page } => Ok(page), + response => Err(Error::UnexpectedResponse { response }), + } + } + + /// Query a database and return the matching pages. + pub async fn query_database( + &self, + database: D, + query: T, + ) -> Result, Error> + where + T: Into, + D: AsIdentifier, + { + let result = self + .make_json_request( + self.client + .post(format!( + "https://api.notion.com/v1/databases/{database_id}/query", + database_id = database.as_id() + )) + .json(&query.into()), + ) + .await?; + match result { + Object::List { list } => Ok(list.expect_pages()?), + response => Err(Error::UnexpectedResponse { response }), + } + } + + pub async fn get_block_children>( + &self, + block_id: T, + ) -> Result, Error> { + let result = self + .make_json_request(self.client.get(format!( + "https://api.notion.com/v1/blocks/{block_id}/children", + block_id = block_id.as_id() + ))) + .await?; + + match result { + Object::List { list } => Ok(list.expect_blocks()?), + response => Err(Error::UnexpectedResponse { response }), + } + } +} diff --git a/crates/suiop-cli/src/cli/notion/models/block.rs b/crates/suiop-cli/src/cli/notion/models/block.rs new file mode 100644 index 0000000000000..640709503620e --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/block.rs @@ -0,0 +1,615 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +use super::text::{RichText, TextColor}; +use super::users::UserCommon; +use crate::cli::notion::ids::{AsIdentifier, BlockId, DatabaseId, PageId}; + +mod tests; + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct BlockCommon { + pub id: BlockId, + pub created_time: DateTime, + pub last_edited_time: DateTime, + pub has_children: bool, + pub created_by: UserCommon, + pub last_edited_by: UserCommon, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct TextAndChildren { + pub rich_text: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub children: Option>, + pub color: TextColor, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Text { + pub rich_text: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct InternalFileObject { + url: String, + expiry_time: DateTime, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct ExternalFileObject { + url: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum FileOrEmojiObject { + Emoji { emoji: String }, + File { file: InternalFileObject }, + External { external: ExternalFileObject }, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum FileObject { + File { file: InternalFileObject }, + External { external: ExternalFileObject }, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Callout { + pub rich_text: Vec, + pub icon: FileOrEmojiObject, + pub color: TextColor, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct ToDoFields { + pub rich_text: Vec, + pub checked: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub children: Option>, + pub color: TextColor, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct ChildPageFields { + pub title: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct ChildDatabaseFields { + pub title: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct EmbedFields { + pub url: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct BookmarkFields { + pub url: String, + pub caption: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(rename_all = "lowercase")] +pub enum CodeLanguage { + Abap, + Arduino, + Bash, + Basic, + C, + Clojure, + Coffeescript, + #[serde(rename = "c++")] + CPlusPlus, + #[serde(rename = "c#")] + CSharp, + Css, + Dart, + Diff, + Docker, + Elixir, + Elm, + Erlang, + Flow, + Fortran, + #[serde(rename = "f#")] + FSharp, + Gherkin, + Glsl, + Go, + Graphql, + Groovy, + Haskell, + Html, + Java, + Javascript, + Json, + Julia, + Kotlin, + Latex, + Less, + Lisp, + Livescript, + Lua, + Makefile, + Markdown, + Markup, + Matlab, + Mermaid, + Nix, + #[serde(rename = "objective-c")] + ObjectiveC, + Ocaml, + Pascal, + Perl, + Php, + #[serde(rename = "plain text")] + PlainText, + Powershell, + Prolog, + Protobuf, + Python, + R, + Reason, + Ruby, + Rust, + Sass, + Scala, + Scheme, + Scss, + Shell, + Sql, + Swift, + Typescript, + #[serde(rename = "vb.net")] + VbNet, + Verilog, + Vhdl, + #[serde(rename = "visual basic")] + VisualBasic, + Webassembly, + Xml, + Yaml, + #[serde(rename = "java/c/c++/c#")] + JavaCAndCPlusPlusAndCSharp, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct CodeFields { + pub rich_text: Vec, + pub caption: Vec, + pub language: CodeLanguage, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Equation { + pub expression: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct TableOfContents { + pub color: TextColor, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct ColumnListFields { + pub children: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct ColumnFields { + pub children: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct LinkPreviewFields { + pub url: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct TemplateFields { + pub rich_text: Vec, + pub children: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum LinkToPageFields { + PageId { page_id: PageId }, + DatabaseId { database_id: DatabaseId }, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct SyncedFromObject { + pub block_id: BlockId, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct SyncedBlockFields { + pub synced_from: Option, + pub children: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct TableFields { + pub table_width: u64, + pub has_column_header: bool, + pub has_row_header: bool, + pub children: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct TableRowFields { + pub cells: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum Block { + Paragraph { + #[serde(flatten)] + common: BlockCommon, + paragraph: TextAndChildren, + }, + #[serde(rename = "heading_1")] + Heading1 { + #[serde(flatten)] + common: BlockCommon, + heading_1: Text, + }, + #[serde(rename = "heading_2")] + Heading2 { + #[serde(flatten)] + common: BlockCommon, + heading_2: Text, + }, + #[serde(rename = "heading_3")] + Heading3 { + #[serde(flatten)] + common: BlockCommon, + heading_3: Text, + }, + Callout { + #[serde(flatten)] + common: BlockCommon, + callout: Callout, + }, + Quote { + #[serde(flatten)] + common: BlockCommon, + quote: TextAndChildren, + }, + BulletedListItem { + #[serde(flatten)] + common: BlockCommon, + bulleted_list_item: TextAndChildren, + }, + NumberedListItem { + #[serde(flatten)] + common: BlockCommon, + numbered_list_item: TextAndChildren, + }, + ToDo { + #[serde(flatten)] + common: BlockCommon, + to_do: ToDoFields, + }, + Toggle { + #[serde(flatten)] + common: BlockCommon, + toggle: TextAndChildren, + }, + Code { + #[serde(flatten)] + common: BlockCommon, + code: CodeFields, + }, + ChildPage { + #[serde(flatten)] + common: BlockCommon, + child_page: ChildPageFields, + }, + ChildDatabase { + #[serde(flatten)] + common: BlockCommon, + child_page: ChildDatabaseFields, + }, + Embed { + #[serde(flatten)] + common: BlockCommon, + embed: EmbedFields, + }, + Image { + #[serde(flatten)] + common: BlockCommon, + image: FileObject, + }, + Video { + #[serde(flatten)] + common: BlockCommon, + video: FileObject, + }, + File { + #[serde(flatten)] + common: BlockCommon, + file: FileObject, + caption: Text, + }, + Pdf { + #[serde(flatten)] + common: BlockCommon, + pdf: FileObject, + }, + Bookmark { + #[serde(flatten)] + common: BlockCommon, + bookmark: BookmarkFields, + }, + Equation { + #[serde(flatten)] + common: BlockCommon, + equation: Equation, + }, + Divider { + #[serde(flatten)] + common: BlockCommon, + }, + TableOfContents { + #[serde(flatten)] + common: BlockCommon, + table_of_contents: TableOfContents, + }, + Breadcrumb { + #[serde(flatten)] + common: BlockCommon, + }, + ColumnList { + #[serde(flatten)] + common: BlockCommon, + column_list: ColumnListFields, + }, + Column { + #[serde(flatten)] + common: BlockCommon, + column: ColumnFields, + }, + LinkPreview { + #[serde(flatten)] + common: BlockCommon, + link_preview: LinkPreviewFields, + }, + Template { + #[serde(flatten)] + common: BlockCommon, + template: TemplateFields, + }, + LinkToPage { + #[serde(flatten)] + common: BlockCommon, + link_to_page: LinkToPageFields, + }, + Table { + #[serde(flatten)] + common: BlockCommon, + table: TableFields, + }, + SyncedBlock { + #[serde(flatten)] + common: BlockCommon, + synced_block: SyncedBlockFields, + }, + TableRow { + #[serde(flatten)] + common: BlockCommon, + table_row: TableRowFields, + }, + Unsupported { + #[serde(flatten)] + common: BlockCommon, + }, + #[serde(other)] + Unknown, +} + +impl AsIdentifier for Block { + fn as_id(&self) -> &BlockId { + use Block::*; + match self { + Paragraph { common, .. } + | Heading1 { common, .. } + | Heading2 { common, .. } + | Heading3 { common, .. } + | Callout { common, .. } + | Quote { common, .. } + | BulletedListItem { common, .. } + | NumberedListItem { common, .. } + | ToDo { common, .. } + | Toggle { common, .. } + | Code { common, .. } + | ChildPage { common, .. } + | ChildDatabase { common, .. } + | Embed { common, .. } + | Image { common, .. } + | Video { common, .. } + | File { common, .. } + | Pdf { common, .. } + | Bookmark { common, .. } + | Equation { common, .. } + | Divider { common, .. } + | TableOfContents { common, .. } + | Breadcrumb { common, .. } + | ColumnList { common, .. } + | Column { common, .. } + | LinkPreview { common, .. } + | Template { common, .. } + | LinkToPage { common, .. } + | SyncedBlock { common, .. } + | Table { common, .. } + | TableRow { common, .. } + | Unsupported { common, .. } => &common.id, + Unknown => { + panic!("Trying to reference identifier for unknown block!") + } + } + } +} + +impl From for CreateBlock { + fn from(val: Block) -> Self { + match val { + Block::Paragraph { paragraph, .. } => CreateBlock::Paragraph { paragraph }, + Block::Heading1 { heading_1, .. } => CreateBlock::Heading1 { heading_1 }, + Block::Heading2 { heading_2, .. } => CreateBlock::Heading2 { heading_2 }, + Block::Heading3 { heading_3, .. } => CreateBlock::Heading3 { heading_3 }, + Block::Callout { callout, .. } => CreateBlock::Callout { callout }, + Block::Quote { quote, .. } => CreateBlock::Quote { quote }, + Block::BulletedListItem { + bulleted_list_item, .. + } => CreateBlock::BulletedListItem { bulleted_list_item }, + Block::NumberedListItem { + numbered_list_item, .. + } => CreateBlock::NumberedListItem { numbered_list_item }, + Block::ToDo { to_do, .. } => CreateBlock::ToDo { to_do }, + Block::Toggle { toggle, .. } => CreateBlock::Toggle { toggle }, + Block::Code { code, .. } => CreateBlock::Code { code }, + Block::ChildPage { child_page, .. } => CreateBlock::ChildPage { child_page }, + Block::ChildDatabase { child_page, .. } => CreateBlock::ChildDatabase { child_page }, + Block::Embed { embed, .. } => CreateBlock::Embed { embed }, + Block::Image { image, .. } => CreateBlock::Image { image }, + Block::Video { video, .. } => CreateBlock::Video { video }, + Block::File { file, caption, .. } => CreateBlock::File { file, caption }, + Block::Pdf { pdf, .. } => CreateBlock::Pdf { pdf }, + Block::Bookmark { bookmark, .. } => CreateBlock::Bookmark { bookmark }, + Block::Equation { equation, .. } => CreateBlock::Equation { equation }, + Block::Divider { .. } => CreateBlock::Divider {}, + Block::TableOfContents { + table_of_contents, .. + } => CreateBlock::TableOfContents { table_of_contents }, + Block::Breadcrumb { .. } => CreateBlock::Breadcrumb {}, + Block::ColumnList { column_list, .. } => CreateBlock::ColumnList { column_list }, + Block::Column { column, .. } => CreateBlock::Column { column }, + + Block::LinkPreview { link_preview, .. } => CreateBlock::LinkPreview { link_preview }, + Block::Template { template, .. } => CreateBlock::Template { template }, + Block::LinkToPage { link_to_page, .. } => CreateBlock::LinkToPage { link_to_page }, + Block::Table { table, .. } => CreateBlock::Table { table }, + Block::SyncedBlock { synced_block, .. } => CreateBlock::SyncedBlock { synced_block }, + Block::TableRow { table_row, .. } => CreateBlock::TableRow { table_row }, + Block::Unsupported { .. } => CreateBlock::Unsupported, + Block::Unknown => CreateBlock::Unknown, + } + } +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum CreateBlock { + Paragraph { + paragraph: TextAndChildren, + }, + #[serde(rename = "heading_1")] + Heading1 { + heading_1: Text, + }, + #[serde(rename = "heading_2")] + Heading2 { + heading_2: Text, + }, + #[serde(rename = "heading_3")] + Heading3 { + heading_3: Text, + }, + Callout { + callout: Callout, + }, + Quote { + quote: TextAndChildren, + }, + BulletedListItem { + bulleted_list_item: TextAndChildren, + }, + NumberedListItem { + numbered_list_item: TextAndChildren, + }, + ToDo { + to_do: ToDoFields, + }, + Toggle { + toggle: TextAndChildren, + }, + Code { + code: CodeFields, + }, + ChildPage { + child_page: ChildPageFields, + }, + ChildDatabase { + child_page: ChildDatabaseFields, + }, + Embed { + embed: EmbedFields, + }, + Image { + image: FileObject, + }, + Video { + video: FileObject, + }, + File { + file: FileObject, + caption: Text, + }, + Pdf { + pdf: FileObject, + }, + Bookmark { + bookmark: BookmarkFields, + }, + Equation { + equation: Equation, + }, + Divider, + TableOfContents { + table_of_contents: TableOfContents, + }, + Breadcrumb, + ColumnList { + column_list: ColumnListFields, + }, + Column { + column: ColumnFields, + }, + LinkPreview { + link_preview: LinkPreviewFields, + }, + Template { + template: TemplateFields, + }, + LinkToPage { + link_to_page: LinkToPageFields, + }, + Table { + table: TableFields, + }, + SyncedBlock { + synced_block: SyncedBlockFields, + }, + TableRow { + table_row: TableRowFields, + }, + Unsupported, + #[serde(other)] + Unknown, +} diff --git a/crates/suiop-cli/src/cli/notion/models/block/tests.rs b/crates/suiop-cli/src/cli/notion/models/block/tests.rs new file mode 100644 index 0000000000000..14f9b781b478b --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/block/tests.rs @@ -0,0 +1,305 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(test)] +mod test { + use std::str::FromStr; + + use chrono::DateTime; + + use crate::cli::notion::{ + ids::{BlockId, UserId}, + models::{ + block::{ + Block, BlockCommon, Callout, ExternalFileObject, FileOrEmojiObject, + InternalFileObject, Text as TextBlockModel, + }, + text::{Annotations, RichText, RichTextCommon, Text, TextColor}, + users::UserCommon, + Object, + }, + }; + + #[test] + fn heading_1() { + let heading_1: Block = serde_json::from_str(include_str!("tests/heading_1.json")).unwrap(); + assert_eq!( + heading_1, + Block::Heading1 { + common: BlockCommon { + id: BlockId::from_str("9e891834-6a03-475c-a2b8-421e17f0f3aa").unwrap(), + created_time: DateTime::from_str("2022-05-12T21:15:00.000Z").unwrap(), + last_edited_time: DateTime::from_str("2022-05-12T22:10:00.000Z").unwrap(), + has_children: false, + created_by: UserCommon { + id: UserId::from_str("6419f912-5293-4ea8-b2c8-9c3ce44f90e3").unwrap(), + name: None, + avatar_url: None, + }, + last_edited_by: UserCommon { + id: UserId::from_str("6419f912-5293-4ea8-b2c8-9c3ce44f90e3").unwrap(), + name: None, + avatar_url: None, + }, + }, + heading_1: TextBlockModel { + rich_text: vec![ + RichText::Text { + rich_text: RichTextCommon { + plain_text: "This".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(true), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(false), + }), + }, + text: Text { + content: "This".to_string(), + link: None, + }, + }, + RichText::Text { + rich_text: RichTextCommon { + plain_text: " ".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(false), + }), + }, + text: Text { + content: " ".to_string(), + link: None, + }, + }, + RichText::Text { + rich_text: RichTextCommon { + plain_text: "is".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(true), + }), + }, + text: Text { + content: "is".to_string(), + link: None, + }, + }, + RichText::Text { + rich_text: RichTextCommon { + plain_text: " ".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(false), + }), + }, + text: Text { + content: " ".to_string(), + link: None, + }, + }, + RichText::Text { + rich_text: RichTextCommon { + plain_text: "a".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(true), + strikethrough: Some(false), + underline: Some(true), + }), + }, + text: Text { + content: "a".to_string(), + link: None, + }, + }, + RichText::Text { + rich_text: RichTextCommon { + plain_text: " ".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(false), + }), + }, + text: Text { + content: " ".to_string(), + link: None, + }, + }, + RichText::Text { + rich_text: RichTextCommon { + plain_text: "Heading".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(true), + strikethrough: Some(false), + underline: Some(false), + }), + }, + text: Text { + content: "Heading".to_string(), + link: None, + }, + }, + RichText::Text { + rich_text: RichTextCommon { + plain_text: " ".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(false), + }), + }, + text: Text { + content: " ".to_string(), + link: None, + }, + }, + RichText::Text { + rich_text: RichTextCommon { + plain_text: "1".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(true), + underline: Some(false), + }), + }, + text: Text { + content: "1".to_string(), + link: None, + }, + }, + ] + }, + } + ) + } + + #[test] + fn emoji_object() { + let emoji_object: FileOrEmojiObject = + serde_json::from_str(include_str!("tests/emoji_object.json")).unwrap(); + assert_eq!( + emoji_object, + FileOrEmojiObject::Emoji { + emoji: "💡".to_string() + } + ) + } + + #[test] + fn file_object() { + let file_object: FileOrEmojiObject = + serde_json::from_str(include_str!("tests/file_object.json")).unwrap(); + assert_eq!(file_object, FileOrEmojiObject::File { + file: InternalFileObject { + url: "https://s3.us-west-2.amazonaws.com/secure.notion-static.com/2703e742-ace5-428c-a74d-1c587ceddc32/DiRT_Rally.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220513%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220513T201035Z&X-Amz-Expires=3600&X-Amz-Signature=714b49bde0b499fb8f3aae1a88a8cbd374f2b09c1d128e91cac49e85ce0e00fb&X-Amz-SignedHeaders=host&x-id=GetObject".to_string(), + expiry_time: DateTime::from_str("2022-05-13T21:10:35.817Z").unwrap(), + } + }) + } + + #[test] + fn external_file_object() { + let external_file_object: FileOrEmojiObject = + serde_json::from_str(include_str!("tests/external_file_object.json")).unwrap(); + assert_eq!( + external_file_object, + FileOrEmojiObject::External { + external: ExternalFileObject { + url: "https://nerdist.com/wp-content/uploads/2020/07/maxresdefault.jpg" + .to_string(), + } + } + ) + } + + #[test] + fn callout() { + let callout: Object = serde_json::from_str(include_str!("tests/callout.json")).unwrap(); + assert_eq!( + callout, + Object::Block { + block: Block::Callout { + common: BlockCommon { + id: BlockId::from_str("00e8829a-a7b8-4075-884a-8f53be145d2f").unwrap(), + created_time: DateTime::from_str("2022-05-13T20:08:00.000Z").unwrap(), + last_edited_time: DateTime::from_str("2022-05-13T20:08:00.000Z").unwrap(), + has_children: true, + created_by: UserCommon { + id: UserId::from_str("e2507360-468c-4e0f-a928-7bbcbbb45353").unwrap(), + name: None, + avatar_url: None, + }, + last_edited_by: UserCommon { + id: UserId::from_str("e2507360-468c-4e0f-a928-7bbcbbb45353").unwrap(), + name: None, + avatar_url: None, + }, + }, + callout: Callout { + rich_text: vec![RichText::Text { + rich_text: RichTextCommon { + plain_text: "Test callout".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(false), + }), + }, + text: Text { + content: "Test callout".to_string(), + link: None + }, + }], + icon: FileOrEmojiObject::Emoji { + emoji: "💡".to_string() + }, + color: TextColor::Green, + }, + } + } + ) + } +} diff --git a/crates/suiop-cli/src/cli/notion/models/block/tests/callout.json b/crates/suiop-cli/src/cli/notion/models/block/tests/callout.json new file mode 100644 index 0000000000000..e4d884de43b6e --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/block/tests/callout.json @@ -0,0 +1,43 @@ +{ + "object": "block", + "id": "00e8829a-a7b8-4075-884a-8f53be145d2f", + "created_time": "2022-05-13T20:08:00.000Z", + "last_edited_time": "2022-05-13T20:08:00.000Z", + "created_by": { + "object": "user", + "id": "e2507360-468c-4e0f-a928-7bbcbbb45353" + }, + "last_edited_by": { + "object": "user", + "id": "e2507360-468c-4e0f-a928-7bbcbbb45353" + }, + "has_children": true, + "archived": false, + "type": "callout", + "callout": { + "rich_text": [ + { + "type": "text", + "text": { + "content": "Test callout", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Test callout", + "href": null + } + ], + "icon": { + "type": "emoji", + "emoji": "💡" + }, + "color": "green" + } +} \ No newline at end of file diff --git a/crates/suiop-cli/src/cli/notion/models/block/tests/emoji_object.json b/crates/suiop-cli/src/cli/notion/models/block/tests/emoji_object.json new file mode 100644 index 0000000000000..1fb3b56e1d669 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/block/tests/emoji_object.json @@ -0,0 +1,4 @@ +{ + "type": "emoji", + "emoji": "💡" +} \ No newline at end of file diff --git a/crates/suiop-cli/src/cli/notion/models/block/tests/external_file_object.json b/crates/suiop-cli/src/cli/notion/models/block/tests/external_file_object.json new file mode 100644 index 0000000000000..b5d4b852d3643 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/block/tests/external_file_object.json @@ -0,0 +1,6 @@ +{ + "type": "external", + "external": { + "url": "https://nerdist.com/wp-content/uploads/2020/07/maxresdefault.jpg" + } +} \ No newline at end of file diff --git a/crates/suiop-cli/src/cli/notion/models/block/tests/file_object.json b/crates/suiop-cli/src/cli/notion/models/block/tests/file_object.json new file mode 100644 index 0000000000000..650cf9b261e87 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/block/tests/file_object.json @@ -0,0 +1,7 @@ +{ + "type": "file", + "file": { + "url": "https://s3.us-west-2.amazonaws.com/secure.notion-static.com/2703e742-ace5-428c-a74d-1c587ceddc32/DiRT_Rally.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45EIPT3X45%2F20220513%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20220513T201035Z&X-Amz-Expires=3600&X-Amz-Signature=714b49bde0b499fb8f3aae1a88a8cbd374f2b09c1d128e91cac49e85ce0e00fb&X-Amz-SignedHeaders=host&x-id=GetObject", + "expiry_time": "2022-05-13T21:10:35.817Z" + } +} \ No newline at end of file diff --git a/crates/suiop-cli/src/cli/notion/models/block/tests/heading_1.json b/crates/suiop-cli/src/cli/notion/models/block/tests/heading_1.json new file mode 100644 index 0000000000000..ab2d7e1d494a0 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/block/tests/heading_1.json @@ -0,0 +1,175 @@ +{ + "object": "block", + "id": "9e891834-6a03-475c-a2b8-421e17f0f3aa", + "created_time": "2022-05-12T21:15:00.000Z", + "last_edited_time": "2022-05-12T22:10:00.000Z", + "created_by": { + "object": "user", + "id": "6419f912-5293-4ea8-b2c8-9c3ce44f90e3" + }, + "last_edited_by": { + "object": "user", + "id": "6419f912-5293-4ea8-b2c8-9c3ce44f90e3" + }, + "has_children": false, + "archived": false, + "type": "heading_1", + "heading_1": { + "rich_text": [ + { + "type": "text", + "text": { + "content": "This", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": true, + "color": "default" + }, + "plain_text": "This", + "href": null + }, + { + "type": "text", + "text": { + "content": " ", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": " ", + "href": null + }, + { + "type": "text", + "text": { + "content": "is", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": true, + "code": false, + "color": "default" + }, + "plain_text": "is", + "href": null + }, + { + "type": "text", + "text": { + "content": " ", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": " ", + "href": null + }, + { + "type": "text", + "text": { + "content": "a", + "link": null + }, + "annotations": { + "bold": false, + "italic": true, + "strikethrough": false, + "underline": true, + "code": false, + "color": "default" + }, + "plain_text": "a", + "href": null + }, + { + "type": "text", + "text": { + "content": " ", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": " ", + "href": null + }, + { + "type": "text", + "text": { + "content": "Heading", + "link": null + }, + "annotations": { + "bold": false, + "italic": true, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Heading", + "href": null + }, + { + "type": "text", + "text": { + "content": " ", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": " ", + "href": null + }, + { + "type": "text", + "text": { + "content": "1", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": true, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "1", + "href": null + } + ], + "color": "default" + } +} \ No newline at end of file diff --git a/crates/suiop-cli/src/cli/notion/models/error.rs b/crates/suiop-cli/src/cli/notion/models/error.rs new file mode 100644 index 0000000000000..3bbc6ed4f57f2 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/error.rs @@ -0,0 +1,74 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use serde::{Deserialize, Serialize}; +use std::fmt::{Display, Formatter}; + +#[derive(Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Hash)] +#[serde(transparent)] +pub struct StatusCode(u16); + +#[allow(dead_code)] +impl StatusCode { + pub fn code(&self) -> u16 { + self.0 + } +} + +impl Display for StatusCode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + +/// +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] +pub struct ErrorResponse { + pub status: StatusCode, + pub code: ErrorCode, + pub message: String, +} + +/// +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] +#[serde(rename_all = "snake_case")] +pub enum ErrorCode { + InvalidJson, + InvalidRequestUrl, + InvalidRequest, + ValidationError, + MissionVersion, + Unauthorized, + RestrictedResource, + ObjectNotFound, + ConflictError, + RateLimited, + InternalServerError, + ServiceUnavailable, + #[serde(other)] // serde issue #912 + Unknown, +} + +impl Display for ErrorCode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self) + } +} + +#[cfg(test)] +mod tests { + use crate::cli::notion::models::error::{ErrorCode, ErrorResponse}; + + #[test] + fn deserialize_error() { + let error: ErrorResponse = serde_json::from_str(include_str!("tests/error.json")).unwrap(); + assert_eq!(error.code, ErrorCode::ValidationError) + } + + #[test] + fn deserialize_unknown_error() { + let error: ErrorResponse = + serde_json::from_str(include_str!("tests/unknown_error.json")).unwrap(); + assert_eq!(error.code, ErrorCode::Unknown) + } +} diff --git a/crates/suiop-cli/src/cli/notion/models/mod.rs b/crates/suiop-cli/src/cli/notion/models/mod.rs new file mode 100644 index 0000000000000..0b23e26239946 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/mod.rs @@ -0,0 +1,276 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#![allow(dead_code)] +pub mod block; +pub mod error; +pub mod paging; +pub mod properties; +pub mod search; +#[cfg(test)] +mod tests; +pub mod text; +pub mod users; + +use super::Error; +use block::ExternalFileObject; +use properties::{PropertyConfiguration, PropertyValue}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use text::RichText; + +use super::ids::{AsIdentifier, DatabaseId, PageId}; +use block::{Block, CreateBlock, FileObject}; +pub use chrono::{DateTime, Utc}; +use error::ErrorResponse; +use paging::PagingCursor; +pub use serde_json::value::Number; +use users::User; + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone)] +#[serde(rename_all = "snake_case")] +enum ObjectType { + Database, + List, +} + +/// Represents a Notion Database +/// See +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Database { + /// Unique identifier for the database. + pub id: DatabaseId, + /// Date and time when this database was created. + pub created_time: DateTime, + /// Date and time when this database was updated. + pub last_edited_time: DateTime, + /// Name of the database as it appears in Notion. + pub title: Vec, + /// Schema of properties for the database as they appear in Notion. + // + // key string + // The name of the property as it appears in Notion. + // + // value object + // A Property object. + pub icon: Option, + pub properties: HashMap, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum IconObject { + File { + #[serde(flatten)] + file: FileObject, + }, + External { + external: ExternalFileObject, + }, + Emoji { + emoji: String, + }, +} + +impl AsIdentifier for Database { + fn as_id(&self) -> &DatabaseId { + &self.id + } +} + +impl Database { + pub fn title_plain_text(&self) -> String { + self.title + .iter() + .flat_map(|rich_text| rich_text.plain_text().chars()) + .collect() + } +} + +/// +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone)] +pub struct ListResponse { + pub results: Vec, + pub next_cursor: Option, + pub has_more: bool, +} + +impl ListResponse { + pub fn results(&self) -> &[T] { + &self.results + } +} + +impl ListResponse { + pub fn only_databases(self) -> ListResponse { + let databases = self + .results + .into_iter() + .filter_map(|object| match object { + Object::Database { database } => Some(database), + _ => None, + }) + .collect(); + + ListResponse { + results: databases, + has_more: self.has_more, + next_cursor: self.next_cursor, + } + } + + pub(crate) fn expect_databases(self) -> Result, super::Error> { + let databases: Result, _> = self + .results + .into_iter() + .map(|object| match object { + Object::Database { database } => Ok(database), + response => Err(Error::UnexpectedResponse { response }), + }) + .collect(); + + Ok(ListResponse { + results: databases?, + has_more: self.has_more, + next_cursor: self.next_cursor, + }) + } + + pub(crate) fn expect_pages(self) -> Result, super::Error> { + let items: Result, _> = self + .results + .into_iter() + .map(|object| match object { + Object::Page { page } => Ok(page), + response => Err(Error::UnexpectedResponse { response }), + }) + .collect(); + + Ok(ListResponse { + results: items?, + has_more: self.has_more, + next_cursor: self.next_cursor, + }) + } + + pub(crate) fn expect_blocks(self) -> Result, super::Error> { + let items: Result, _> = self + .results + .into_iter() + .map(|object| match object { + Object::Block { block } => Ok(block), + response => Err(Error::UnexpectedResponse { response }), + }) + .collect(); + + Ok(ListResponse { + results: items?, + has_more: self.has_more, + next_cursor: self.next_cursor, + }) + } +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum Parent { + #[serde(rename = "database_id")] + Database { + database_id: DatabaseId, + }, + #[serde(rename = "page_id")] + Page { + page_id: PageId, + }, + Workspace, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Properties { + #[serde(flatten)] + pub properties: HashMap, +} + +impl Properties { + pub fn title(&self) -> Option { + self.properties.values().find_map(|p| match p { + PropertyValue::Title { title, .. } => { + Some(title.iter().map(|t| t.plain_text()).collect()) + } + _ => None, + }) + } +} + +#[derive(Serialize, Debug, Eq, PartialEq)] +pub struct PageCreateRequest { + pub parent: Parent, + pub properties: Properties, + #[serde(skip_serializing_if = "Option::is_none")] + pub children: Option>, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Page { + pub id: PageId, + /// Date and time when this page was created. + pub created_time: DateTime, + /// Date and time when this page was updated. + pub last_edited_time: DateTime, + /// The archived status of the page. + pub archived: bool, + pub properties: Properties, + pub icon: Option, + pub parent: Parent, +} + +#[allow(dead_code)] +impl Page { + pub fn title(&self) -> Option { + self.properties.title() + } +} + +impl AsIdentifier for Page { + fn as_id(&self) -> &PageId { + &self.id + } +} + +#[derive(Eq, Serialize, Deserialize, Clone, Debug, PartialEq)] +#[serde(tag = "object")] +#[serde(rename_all = "snake_case")] +pub enum Object { + Block { + #[serde(flatten)] + block: Block, + }, + Database { + #[serde(flatten)] + database: Database, + }, + Page { + #[serde(flatten)] + page: Page, + }, + List { + #[serde(flatten)] + list: ListResponse, + }, + User { + #[serde(flatten)] + user: User, + }, + Error { + #[serde(flatten)] + error: ErrorResponse, + }, +} + +impl Object { + pub fn is_database(&self) -> bool { + matches!(self, Object::Database { .. }) + } +} diff --git a/crates/suiop-cli/src/cli/notion/models/paging.rs b/crates/suiop-cli/src/cli/notion/models/paging.rs new file mode 100644 index 0000000000000..c518a73ef49c5 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/paging.rs @@ -0,0 +1,21 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#![allow(dead_code)] +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone)] +#[serde(transparent)] +pub struct PagingCursor(String); + +#[derive(Serialize, Debug, Eq, PartialEq, Default, Clone)] +pub struct Paging { + #[serde(skip_serializing_if = "Option::is_none")] + pub start_cursor: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub page_size: Option, +} + +pub trait Pageable { + fn start_from(self, starting_point: Option) -> Self; +} diff --git a/crates/suiop-cli/src/cli/notion/models/properties.rs b/crates/suiop-cli/src/cli/notion/models/properties.rs new file mode 100644 index 0000000000000..ef04fdf0e0ae2 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/properties.rs @@ -0,0 +1,520 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use super::text::RichText; +use super::users::User; + +use super::super::ids::{DatabaseId, PageId, PropertyId}; +use super::{DateTime, Number, Utc}; +use chrono::NaiveDate; +use serde::{Deserialize, Serialize}; + +pub mod formulas; + +#[cfg(test)] +mod tests; + +/// How the number is displayed in Notion. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Copy, Clone, Hash)] +#[serde(rename_all = "snake_case")] +pub enum NumberFormat { + Number, + NumberWithCommas, + Percent, + Dollar, + Euro, + Pound, + Yen, + Ruble, + Rupee, + Won, + Yuan, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Clone)] +pub struct NumberDetails { + pub format: NumberFormat, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone)] +#[serde(transparent)] +pub struct SelectOptionId(String); + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Copy, Clone)] +#[serde(rename_all = "lowercase")] +pub enum Color { + Default, + Gray, + Brown, + Orange, + Yellow, + Green, + Blue, + Purple, + Pink, + Red, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct SelectOption { + pub name: String, + pub id: SelectOptionId, + pub color: Color, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Select { + /// Sorted list of options available for this property. + pub options: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct StatusGroupOption { + pub name: String, + pub id: SelectOptionId, + pub color: Color, + pub option_ids: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Status { + /// Sorted list of options available for this property. + pub options: Vec, + /// Sorted list of groups available for this property. + pub groups: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Formula { + /// Formula to evaluate for this property + pub expression: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Relation { + /// The database this relation refers to. + /// New linked pages must belong to this database in order to be valid. + pub database_id: DatabaseId, + /// By default, relations are formed as two synced properties across databases: + /// if you make a change to one property, it updates the synced property at the same time. + /// `synced_property_name` refers to the name of the property in the related database. + pub synced_property_name: Option, + /// By default, relations are formed as two synced properties across databases: + /// if you make a change to one property, it updates the synced property at the same time. + /// `synced_property_id` refers to the id of the property in the related database. + /// This is usually a short string of random letters and symbols. + pub synced_property_id: Option, +} + +/// The function used to roll up the values of the relation property. +/// +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Copy, Clone)] +#[serde(rename_all = "snake_case")] +pub enum RollupFunction { + Average, + Checked, + Count, + CountPerGroup, + CountValues, + DateRange, + EarliestDate, + Empty, + LatestDate, + Max, + Median, + Min, + NotEmpty, + PercentChecked, + PercentEmpty, + PercentNotEmpty, + PercentPerGroup, + PercentUnchecked, + Range, + ShowOriginal, + ShowUnique, + Sum, + Unchecked, + Unique, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Rollup { + /// The name of the relation property this property is responsible for rolling up. + pub relation_property_name: String, + /// The id of the relation property this property is responsible for rolling up. + pub relation_property_id: PropertyId, + /// The name of the property of the pages in the related database + /// that is used as an input to `function`. + pub rollup_property_name: String, + /// The id of the property of the pages in the related database + /// that is used as an input to `function`. + pub rollup_property_id: String, + /// The function that is evaluated for every page in the relation of the rollup. + pub function: RollupFunction, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum PropertyConfiguration { + /// Represents the special Title property required on every database. + /// See + Title { + id: PropertyId, + }, + /// Represents a Text property + /// + #[serde(rename = "rich_text")] + Text { + id: PropertyId, + }, + /// Represents a Number Property + /// See + Number { + id: PropertyId, + /// How the number is displayed in Notion. + number: NumberDetails, + }, + /// Represents a Select Property + /// See + Select { + id: PropertyId, + select: Select, + }, + /// Represents a Status property + Status { + id: PropertyId, + status: Status, + }, + /// Represents a Multi-select Property + /// See + MultiSelect { + id: PropertyId, + multi_select: Select, + }, + /// Represents a Date Property + /// See + Date { + id: PropertyId, + }, + /// Represents a People Property + /// See + People { + id: PropertyId, + }, + /// Represents a File Property + /// See + // Todo: File a bug with notion + // Documentation issue: docs claim type name is `file` but it is in fact `files` + Files { + id: PropertyId, + }, + /// Represents a Checkbox Property + /// See + Checkbox { + id: PropertyId, + }, + /// Represents a URL Property + /// See + Url { + id: PropertyId, + }, + /// Represents a Email Property + /// See + Email { + id: PropertyId, + }, + /// Represents a Phone number Property + /// See + PhoneNumber { + id: PropertyId, + }, + /// See + Formula { + id: PropertyId, + formula: Formula, + }, + /// See + Relation { + id: PropertyId, + relation: Relation, + }, + /// See + Rollup { + id: PropertyId, + rollup: Rollup, + }, + /// See + CreatedTime { + id: PropertyId, + }, + /// See + CreatedBy { + id: PropertyId, + }, + /// See + LastEditedTime { + id: PropertyId, + }, + /// See + LastEditBy { + id: PropertyId, + }, + UniqueId { + id: PropertyId, + }, + Button { + id: PropertyId, + }, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct SelectedValue { + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + pub color: Color, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(untagged)] +pub enum DateOrDateTime { + Date(NaiveDate), + DateTime(DateTime), +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct DateValue { + pub start: DateOrDateTime, + pub end: Option, + pub time_zone: Option, +} + +/// Formula property value objects represent the result of evaluating a formula +/// described in the database's properties. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum FormulaResultValue { + String { string: Option }, + Number { number: Option }, + Boolean { boolean: Option }, + Date { date: Option }, +} + +/// Relation property value objects contain an array of page references within the relation property. +/// A page reference is an object with an id property, +/// with a string value (UUIDv4) corresponding to a page ID in another database. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct RelationValue { + pub id: PageId, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum RollupValue { + Number { number: Option }, + Date { date: Option> }, + Array { array: Vec }, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct FileReference { + pub name: String, + pub url: String, + pub mime_type: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum PropertyValue { + // + Title { + id: PropertyId, + title: Vec, + }, + /// + #[serde(rename = "rich_text")] + Text { + id: PropertyId, + rich_text: Vec, + }, + /// + Number { + id: PropertyId, + number: Option, + }, + /// + Select { + id: PropertyId, + select: Option, + }, + /// + Status { + id: PropertyId, + status: Option, + }, + /// + MultiSelect { + id: PropertyId, + multi_select: Option>, + }, + /// + Date { + id: PropertyId, + date: Option, + }, + /// + Formula { + id: PropertyId, + formula: FormulaResultValue, + }, + /// + /// It is actually an array of relations + Relation { + id: PropertyId, + relation: Option>, + }, + /// + Rollup { + id: PropertyId, + rollup: Option, + }, + /// + People { + id: PropertyId, + people: Vec, + }, + /// + Files { + id: PropertyId, + files: Option>, + }, + /// + Checkbox { + id: PropertyId, + checkbox: bool, + }, + /// + Url { + id: PropertyId, + url: Option, + }, + /// + Email { + id: PropertyId, + email: Option, + }, + /// + PhoneNumber { + id: PropertyId, + phone_number: String, + }, + /// + CreatedTime { + id: PropertyId, + created_time: DateTime, + }, + /// + CreatedBy { + id: PropertyId, + created_by: User, + }, + /// + LastEditedTime { + id: PropertyId, + last_edited_time: DateTime, + }, + /// + LastEditedBy { + id: PropertyId, + last_edited_by: User, + }, + UniqueId { + id: PropertyId, + unique_id: UniqueidValue, + }, + Button { + id: PropertyId, + }, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct UniqueidValue { + pub prefix: Option, + pub number: u32, +} + +/// +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum RollupPropertyValue { + /// + #[serde(rename = "rich_text")] + Text { + rich_text: Vec, + }, + /// + Number { + number: Option, + }, + /// + Select { + select: Option, + }, + Status { + status: Option, + }, + MultiSelect { + multi_select: Option>, + }, + Date { + date: Option, + }, + /// + Formula { + formula: FormulaResultValue, + }, + /// + /// It is actually an array of relations + Relation { + relation: Option>, + }, + /// + Rollup { + rollup: Option, + }, + People { + people: Vec, + }, + Files { + files: Option>, + }, + Checkbox { + checkbox: bool, + }, + Url { + url: Option, + }, + Email { + email: Option, + }, + PhoneNumber { + phone_number: String, + }, + CreatedTime { + created_time: DateTime, + }, + CreatedBy { + created_by: User, + }, + LastEditedTime { + last_edited_time: DateTime, + }, + LastEditedBy { + last_edited_by: User, + }, +} diff --git a/crates/suiop-cli/src/cli/notion/models/properties/formulas.rs b/crates/suiop-cli/src/cli/notion/models/properties/formulas.rs new file mode 100644 index 0000000000000..dac40c94cb869 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/properties/formulas.rs @@ -0,0 +1,30 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(test)] +mod tests { + use crate::cli::notion::models::properties::{FormulaResultValue, PropertyValue}; + + #[test] + fn parse_number_formula_prop() { + let _property: PropertyValue = + serde_json::from_str(include_str!("tests/formula_number_value.json")).unwrap(); + } + + #[test] + fn parse_date_formula_prop() { + let _property: PropertyValue = + serde_json::from_str(include_str!("tests/formula_date_value.json")).unwrap(); + } + + #[test] + fn parse_number_formula() { + let _value: FormulaResultValue = serde_json::from_str( + r#"{ + "type": "number", + "number": 0 + }"#, + ) + .unwrap(); + } +} diff --git a/crates/suiop-cli/src/cli/notion/models/properties/tests.rs b/crates/suiop-cli/src/cli/notion/models/properties/tests.rs new file mode 100644 index 0000000000000..3d7769121c8e7 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/properties/tests.rs @@ -0,0 +1,62 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use chrono::NaiveDate; + +use crate::cli::notion::models::properties::{DateOrDateTime, RollupPropertyValue, RollupValue}; + +use super::PropertyValue; + +#[test] +fn verify_date_parsing() { + let date = NaiveDate::from_ymd_opt(2021, 1, 2).unwrap(); + let result = serde_json::to_string(&DateOrDateTime::Date(date)).unwrap(); + let parsed: DateOrDateTime = serde_json::from_str(&result).unwrap(); + println!("{:?}", parsed); +} + +#[test] +fn parse_date_property() { + let _property: PropertyValue = + serde_json::from_str(include_str!("tests/date_property.json")).unwrap(); +} + +#[test] +fn parse_null_select_property() { + let _property: PropertyValue = + serde_json::from_str(include_str!("tests/null_select_property.json")).unwrap(); +} + +#[test] +fn parse_select_property() { + let _property: PropertyValue = + serde_json::from_str(include_str!("tests/select_property.json")).unwrap(); +} + +#[test] +fn parse_text_property_with_link() { + let _property: PropertyValue = + serde_json::from_str(include_str!("tests/text_with_link.json")).unwrap(); +} + +#[test] +fn parse_rollup_property() { + let property: PropertyValue = + serde_json::from_str(include_str!("tests/rollup_property.json")).unwrap(); + + assert!(matches!( + property, + PropertyValue::Rollup { + rollup: Some(RollupValue::Array { .. }), + .. + } + )); + + if let PropertyValue::Rollup { + rollup: Some(RollupValue::Array { array }), + .. + } = property + { + assert!(matches!(array[0], RollupPropertyValue::Text { .. })) + } +} diff --git a/crates/suiop-cli/src/cli/notion/models/properties/tests/date_property.json b/crates/suiop-cli/src/cli/notion/models/properties/tests/date_property.json new file mode 100644 index 0000000000000..fe2b04f9f56f0 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/properties/tests/date_property.json @@ -0,0 +1,8 @@ +{ + "id": "VXfM", + "type": "date", + "date": { + "start": "2021-09-30", + "end": null + } +} diff --git a/crates/suiop-cli/src/cli/notion/models/properties/tests/formula_date_value.json b/crates/suiop-cli/src/cli/notion/models/properties/tests/formula_date_value.json new file mode 100644 index 0000000000000..84728c531332b --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/properties/tests/formula_date_value.json @@ -0,0 +1,11 @@ +{ + "id": "7*%269", + "type": "formula", + "formula": { + "type": "date", + "date": { + "start": "2021-09-30", + "end": null + } + } +} diff --git a/crates/suiop-cli/src/cli/notion/models/properties/tests/formula_number_value.json b/crates/suiop-cli/src/cli/notion/models/properties/tests/formula_number_value.json new file mode 100644 index 0000000000000..2a831e3dde6fc --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/properties/tests/formula_number_value.json @@ -0,0 +1,8 @@ +{ + "id": "abc", + "type": "formula", + "formula": { + "type": "number", + "number": 0 + } +} diff --git a/crates/suiop-cli/src/cli/notion/models/properties/tests/null_select_property.json b/crates/suiop-cli/src/cli/notion/models/properties/tests/null_select_property.json new file mode 100644 index 0000000000000..e0ed24eff4477 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/properties/tests/null_select_property.json @@ -0,0 +1,5 @@ +{ + "id": "uX", + "type": "select", + "select": null +} diff --git a/crates/suiop-cli/src/cli/notion/models/properties/tests/rollup_property.json b/crates/suiop-cli/src/cli/notion/models/properties/tests/rollup_property.json new file mode 100644 index 0000000000000..c80d33b22ef29 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/properties/tests/rollup_property.json @@ -0,0 +1,32 @@ +{ + "id": "R%7Cm%3F", + "type": "rollup", + "rollup": { + "type": "array", + "array": [ + { + "type": "rich_text", + "rich_text": [ + { + "type": "text", + "text": { + "content": "personal", + "link": null + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "personal", + "href": null + } + ] + } + ], + "function": "show_original" + } +} diff --git a/crates/suiop-cli/src/cli/notion/models/properties/tests/select_property.json b/crates/suiop-cli/src/cli/notion/models/properties/tests/select_property.json new file mode 100644 index 0000000000000..db65bdf044cab --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/properties/tests/select_property.json @@ -0,0 +1,9 @@ +{ + "id": "uX", + "type": "select", + "select": { + "id": "9c", + "name": "Reserved", + "color": "green" + } +} diff --git a/crates/suiop-cli/src/cli/notion/models/properties/tests/text_with_link.json b/crates/suiop-cli/src/cli/notion/models/properties/tests/text_with_link.json new file mode 100644 index 0000000000000..6a8e081ed2b0e --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/properties/tests/text_with_link.json @@ -0,0 +1,25 @@ +{ + "id": "6BZi", + "type": "rich_text", + "rich_text": [ + { + "type": "text", + "text": { + "content": "https://notion.so/", + "link": { + "url": "https://notion.so/" + } + }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "https://notion.so/", + "href": "https://notion.so/" + } + ] +} diff --git a/crates/suiop-cli/src/cli/notion/models/search.rs b/crates/suiop-cli/src/cli/notion/models/search.rs new file mode 100644 index 0000000000000..bca22b69b75cd --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/search.rs @@ -0,0 +1,531 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use super::super::ids::{PageId, UserId}; +use super::paging::{Pageable, Paging, PagingCursor}; +use super::Number; +use chrono::{DateTime, Utc}; +use serde::ser::SerializeMap; +use serde::{Serialize, Serializer}; + +#[derive(Serialize, Debug, Eq, PartialEq, Hash, Copy, Clone)] +#[serde(rename_all = "snake_case")] +#[allow(dead_code)] +pub enum SortDirection { + Ascending, + Descending, +} + +#[derive(Serialize, Debug, Eq, PartialEq, Hash, Copy, Clone)] +#[serde(rename_all = "snake_case")] +#[allow(dead_code)] +pub enum SortTimestamp { + LastEditedTime, +} + +#[derive(Serialize, Debug, Eq, PartialEq, Hash, Copy, Clone)] +#[serde(rename_all = "snake_case")] +#[allow(dead_code)] +pub enum FilterValue { + Page, + Database, +} + +#[derive(Serialize, Debug, Eq, PartialEq, Hash, Copy, Clone)] +#[serde(rename_all = "snake_case")] +#[allow(dead_code)] +pub enum FilterProperty { + Object, +} + +#[derive(Serialize, Debug, Eq, PartialEq, Clone)] +pub struct Sort { + /// The name of the timestamp to sort against. + timestamp: SortTimestamp, + direction: SortDirection, +} + +#[derive(Serialize, Debug, Eq, PartialEq, Clone)] +pub struct Filter { + property: FilterProperty, + value: FilterValue, +} + +#[derive(Serialize, Debug, Eq, PartialEq, Default)] +pub struct SearchRequest { + #[serde(skip_serializing_if = "Option::is_none")] + query: Option, + #[serde(skip_serializing_if = "Option::is_none")] + sort: Option, + #[serde(skip_serializing_if = "Option::is_none")] + filter: Option, + #[serde(flatten)] + paging: Option, +} + +#[derive(Serialize, Debug, Eq, PartialEq, Clone)] +#[serde(rename_all = "snake_case")] +#[allow(unused)] +pub enum TextCondition { + Equals(String), + DoesNotEqual(String), + Contains(String), + DoesNotContain(String), + StartsWith(String), + EndsWith(String), + #[serde(serialize_with = "serialize_to_true")] + IsEmpty, + #[serde(serialize_with = "serialize_to_true")] + IsNotEmpty, +} + +fn serialize_to_true(serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_bool(true) +} + +fn serialize_to_empty_object(serializer: S) -> Result +where + S: Serializer, +{ + // Todo: there has to be a better way? + serializer.serialize_map(Some(0))?.end() +} + +#[derive(Serialize, Debug, Eq, PartialEq, Clone)] +#[serde(rename_all = "snake_case")] +#[allow(unused)] +pub enum NumberCondition { + Equals(Number), + DoesNotEqual(Number), + GreaterThan(Number), + LessThan(Number), + GreaterThanOrEqualTo(Number), + LessThanOrEqualTo(Number), + #[serde(serialize_with = "serialize_to_true")] + IsEmpty, + #[serde(serialize_with = "serialize_to_true")] + IsNotEmpty, +} + +#[derive(Serialize, Debug, Eq, PartialEq, Clone)] +#[serde(rename_all = "snake_case")] +#[allow(unused)] + +pub enum CheckboxCondition { + Equals(bool), + DoesNotEqual(bool), +} + +#[derive(Serialize, Debug, Eq, PartialEq, Clone)] +#[serde(rename_all = "snake_case")] +#[allow(unused)] +pub enum SelectCondition { + /// Only return pages where the page property value matches the provided value exactly. + Equals(String), + /// Only return pages where the page property value does not match the provided value exactly. + DoesNotEqual(String), + /// Only return pages where the page property value is empty. + #[serde(serialize_with = "serialize_to_true")] + IsEmpty, + /// Only return pages where the page property value is present. + #[serde(serialize_with = "serialize_to_true")] + IsNotEmpty, +} + +#[derive(Serialize, Debug, Eq, PartialEq, Clone)] +#[serde(rename_all = "snake_case")] +#[allow(dead_code)] +pub enum MultiSelectCondition { + /// Only return pages where the page property value contains the provided value. + Contains(String), + /// Only return pages where the page property value does not contain the provided value. + DoesNotContain(String), + /// Only return pages where the page property value is empty. + #[serde(serialize_with = "serialize_to_true")] + IsEmpty, + /// Only return pages where the page property value is present. + #[serde(serialize_with = "serialize_to_true")] + IsNotEmpty, +} + +#[derive(Serialize, Debug, Eq, PartialEq, Clone)] +#[serde(rename_all = "snake_case")] +#[allow(dead_code)] +pub enum DateCondition { + /// Only return pages where the page property value matches the provided date exactly. + /// Note that the comparison is done against the date. + /// Any time information sent will be ignored. + Equals(DateTime), + /// Only return pages where the page property value is before the provided date. + /// Note that the comparison is done against the date. + /// Any time information sent will be ignored. + Before(DateTime), + /// Only return pages where the page property value is after the provided date. + /// Note that the comparison is done against the date. + /// Any time information sent will be ignored. + After(DateTime), + /// Only return pages where the page property value is on or before the provided date. + /// Note that the comparison is done against the date. + /// Any time information sent will be ignored. + OnOrBefore(DateTime), + /// Only return pages where the page property value is on or after the provided date. + /// Note that the comparison is done against the date. + /// Any time information sent will be ignored. + OnOrAfter(DateTime), + /// Only return pages where the page property value is empty. + #[serde(serialize_with = "serialize_to_true")] + IsEmpty, + /// Only return pages where the page property value is present. + #[serde(serialize_with = "serialize_to_true")] + IsNotEmpty, + /// Only return pages where the page property value is within the past week. + #[serde(serialize_with = "serialize_to_empty_object")] + PastWeek, + /// Only return pages where the page property value is within the past month. + #[serde(serialize_with = "serialize_to_empty_object")] + PastMonth, + /// Only return pages where the page property value is within the past year. + #[serde(serialize_with = "serialize_to_empty_object")] + PastYear, + /// Only return pages where the page property value is within the next week. + #[serde(serialize_with = "serialize_to_empty_object")] + NextWeek, + /// Only return pages where the page property value is within the next month. + #[serde(serialize_with = "serialize_to_empty_object")] + NextMonth, + /// Only return pages where the page property value is within the next year. + #[serde(serialize_with = "serialize_to_empty_object")] + NextYear, +} + +#[derive(Serialize, Debug, Eq, PartialEq, Clone)] +#[serde(rename_all = "snake_case")] +#[allow(dead_code)] +pub enum PeopleCondition { + Contains(UserId), + /// Only return pages where the page property value does not contain the provided value. + DoesNotContain(UserId), + /// Only return pages where the page property value is empty. + #[serde(serialize_with = "serialize_to_true")] + IsEmpty, + /// Only return pages where the page property value is present. + #[serde(serialize_with = "serialize_to_true")] + IsNotEmpty, +} + +#[derive(Serialize, Debug, Eq, PartialEq, Clone)] +#[serde(rename_all = "snake_case")] +#[allow(dead_code)] +pub enum FilesCondition { + /// Only return pages where the page property value is empty. + #[serde(serialize_with = "serialize_to_true")] + IsEmpty, + /// Only return pages where the page property value is present. + #[serde(serialize_with = "serialize_to_true")] + IsNotEmpty, +} + +#[derive(Serialize, Debug, Eq, PartialEq, Clone)] +#[serde(rename_all = "snake_case")] +#[allow(dead_code)] +pub enum RelationCondition { + /// Only return pages where the page property value contains the provided value. + Contains(PageId), + /// Only return pages where the page property value does not contain the provided value. + DoesNotContain(PageId), + /// Only return pages where the page property value is empty. + #[serde(serialize_with = "serialize_to_true")] + IsEmpty, + /// Only return pages where the page property value is present. + #[serde(serialize_with = "serialize_to_true")] + IsNotEmpty, +} + +#[derive(Serialize, Debug, Eq, PartialEq, Clone)] +#[serde(rename_all = "snake_case")] +#[allow(unused)] +pub enum FormulaCondition { + /// Only return pages where the result type of the page property formula is "text" + /// and the provided text filter condition matches the formula's value. + Text(TextCondition), + /// Only return pages where the result type of the page property formula is "number" + /// and the provided number filter condition matches the formula's value. + Number(NumberCondition), + /// Only return pages where the result type of the page property formula is "checkbox" + /// and the provided checkbox filter condition matches the formula's value. + Checkbox(CheckboxCondition), + /// Only return pages where the result type of the page property formula is "date" + /// and the provided date filter condition matches the formula's value. + Date(DateCondition), +} + +#[derive(Serialize, Debug, Eq, PartialEq, Clone)] +#[serde(rename_all = "snake_case")] +#[allow(unused)] +pub enum PropertyCondition { + RichText(TextCondition), + Number(NumberCondition), + Checkbox(CheckboxCondition), + Select(SelectCondition), + MultiSelect(MultiSelectCondition), + Date(DateCondition), + People(PeopleCondition), + Files(FilesCondition), + Relation(RelationCondition), + Formula(FormulaCondition), +} + +#[derive(Serialize, Debug, Eq, PartialEq, Clone)] +#[serde(untagged)] +#[allow(dead_code)] +pub enum FilterCondition { + Property { + property: String, + #[serde(flatten)] + condition: PropertyCondition, + }, + /// Returns pages when **all** of the filters inside the provided vector match. + And { and: Vec }, + /// Returns pages when **any** of the filters inside the provided vector match. + Or { or: Vec }, +} + +#[derive(Serialize, Debug, Eq, PartialEq, Hash, Copy, Clone)] +#[serde(rename_all = "snake_case")] +#[allow(dead_code)] +pub enum DatabaseSortTimestamp { + CreatedTime, + LastEditedTime, +} + +#[derive(Serialize, Debug, Eq, PartialEq, Clone)] +pub struct DatabaseSort { + // Todo: Should property and timestamp be mutually exclusive? (i.e a flattened enum?) + // the documentation is not clear: + // https://developers.notion.com/reference/post-database-query#post-database-query-sort + #[serde(skip_serializing_if = "Option::is_none")] + pub property: Option, + /// The name of the timestamp to sort against. + #[serde(skip_serializing_if = "Option::is_none")] + pub timestamp: Option, + pub direction: SortDirection, +} + +#[derive(Serialize, Debug, Eq, PartialEq, Default, Clone)] +pub struct DatabaseQuery { + #[serde(skip_serializing_if = "Option::is_none")] + pub sorts: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub filter: Option, + #[serde(flatten)] + pub paging: Option, +} + +impl Pageable for DatabaseQuery { + fn start_from(self, starting_point: Option) -> Self { + DatabaseQuery { + paging: Some(Paging { + start_cursor: starting_point, + page_size: self.paging.and_then(|p| p.page_size), + }), + ..self + } + } +} + +#[derive(Debug, Eq, PartialEq)] +#[allow(dead_code)] +pub enum NotionSearch { + /// When supplied, limits which pages are returned by comparing the query to the page title. + Query(String), + /// When supplied, sorts the results based on the provided criteria. + /// + /// Limitation: Currently only a single sort is allowed and is limited to `last_edited_time` + Sort { + timestamp: SortTimestamp, + direction: SortDirection, + }, + /// When supplied, filters the results based on the provided criteria. + /// + /// Limitation: Currently the only filter allowed is `object` which will filter by type of object (either page or database) + Filter { + /// The name of the property to filter by. + /// Currently the only property you can filter by is the `object` type. + property: FilterProperty, + /// The value of the property to filter the results by. + /// Possible values for object type include `page` or `database`. + value: FilterValue, + }, +} + +#[allow(dead_code)] +impl NotionSearch { + pub fn filter_by_databases() -> Self { + Self::Filter { + property: FilterProperty::Object, + value: FilterValue::Database, + } + } +} + +impl From for SearchRequest { + fn from(search: NotionSearch) -> Self { + match search { + NotionSearch::Query(query) => SearchRequest { + query: Some(query), + ..Default::default() + }, + NotionSearch::Sort { + direction, + timestamp, + } => SearchRequest { + sort: Some(Sort { + timestamp, + direction, + }), + ..Default::default() + }, + NotionSearch::Filter { property, value } => SearchRequest { + filter: Some(Filter { property, value }), + ..Default::default() + }, + } + } +} + +#[cfg(test)] +mod tests { + mod text_filters { + use crate::cli::notion::models::search::PropertyCondition::{ + Checkbox, Number, RichText, Select, + }; + use crate::cli::notion::models::search::{ + CheckboxCondition, FilterCondition, NumberCondition, SelectCondition, TextCondition, + }; + use serde_json::json; + + #[test] + fn text_property_equals() -> Result<(), Box> { + let json = serde_json::to_value(&FilterCondition::Property { + property: "Name".to_string(), + condition: RichText(TextCondition::Equals("Test".to_string())), + })?; + assert_eq!( + json, + json!({"property":"Name","rich_text":{"equals":"Test"}}) + ); + + Ok(()) + } + + #[test] + fn text_property_contains() -> Result<(), Box> { + let json = serde_json::to_value(&FilterCondition::Property { + property: "Name".to_string(), + condition: RichText(TextCondition::Contains("Test".to_string())), + })?; + assert_eq!( + dbg!(json), + json!({"property":"Name","rich_text":{"contains":"Test"}}) + ); + + Ok(()) + } + + #[test] + fn text_property_is_empty() -> Result<(), Box> { + let json = serde_json::to_value(&FilterCondition::Property { + property: "Name".to_string(), + condition: RichText(TextCondition::IsEmpty), + })?; + assert_eq!( + dbg!(json), + json!({"property":"Name","rich_text":{"is_empty":true}}) + ); + + Ok(()) + } + + #[test] + fn text_property_is_not_empty() -> Result<(), Box> { + let json = serde_json::to_value(&FilterCondition::Property { + property: "Name".to_string(), + condition: RichText(TextCondition::IsNotEmpty), + })?; + assert_eq!( + dbg!(json), + json!({"property":"Name","rich_text":{"is_not_empty":true}}) + ); + + Ok(()) + } + + #[test] + fn compound_query_and() -> Result<(), Box> { + let json = serde_json::to_value(&FilterCondition::And { + and: vec![ + FilterCondition::Property { + property: "Seen".to_string(), + condition: Checkbox(CheckboxCondition::Equals(false)), + }, + FilterCondition::Property { + property: "Yearly visitor count".to_string(), + condition: Number(NumberCondition::GreaterThan(serde_json::Number::from( + 1000000, + ))), + }, + ], + })?; + assert_eq!( + dbg!(json), + json!({"and":[ + {"property":"Seen","checkbox":{"equals":false}}, + {"property":"Yearly visitor count","number":{"greater_than":1000000}} + ]}) + ); + + Ok(()) + } + + #[test] + fn compound_query_or() -> Result<(), Box> { + let json = serde_json::to_value(&FilterCondition::Or { + or: vec![ + FilterCondition::Property { + property: "Description".to_string(), + condition: RichText(TextCondition::Contains("fish".to_string())), + }, + FilterCondition::And { + and: vec![ + FilterCondition::Property { + property: "Food group".to_string(), + condition: Select(SelectCondition::Equals( + "🥦Vegetable".to_string(), + )), + }, + FilterCondition::Property { + property: "Is protein rich?".to_string(), + condition: Checkbox(CheckboxCondition::Equals(true)), + }, + ], + }, + ], + })?; + assert_eq!( + dbg!(json), + json!({"or":[ + {"property":"Description","rich_text":{"contains":"fish"}}, + {"and":[ + {"property":"Food group","select":{"equals":"🥦Vegetable"}}, + {"property":"Is protein rich?","checkbox":{"equals":true}} + ]} + ]}) + ); + + Ok(()) + } + } +} diff --git a/crates/suiop-cli/src/cli/notion/models/tests.rs b/crates/suiop-cli/src/cli/notion/models/tests.rs new file mode 100644 index 0000000000000..c6176e0e10fe5 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/tests.rs @@ -0,0 +1,225 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use super::super::ids::UserId; +use super::properties::{DateOrDateTime, DateValue}; +use super::text::{Annotations, Link, MentionObject, RichText, RichTextCommon, Text, TextColor}; +use super::users::{Person, User, UserCommon}; +use super::{ListResponse, Object, Page}; +use chrono::{DateTime, NaiveDate}; +use std::str::FromStr; + +#[test] +fn deserialize_page() { + let _page: Page = serde_json::from_str(include_str!("tests/page.json")).unwrap(); +} + +#[test] +fn deserialize_query_result() { + let _page: ListResponse = + serde_json::from_str(include_str!("tests/query_result.json")).unwrap(); +} + +#[test] +fn deserialize_number_format() { + let _search_results: ListResponse = + serde_json::from_str(include_str!("tests/issue_15.json")).unwrap(); +} + +#[test] +fn rich_text() { + let rich_text_text: RichText = + serde_json::from_str(include_str!("tests/rich_text_text.json")).unwrap(); + assert_eq!( + rich_text_text, + RichText::Text { + rich_text: RichTextCommon { + plain_text: "Rich".to_string(), + href: Some("https://github.com/jakeswenson/notion".to_string()), + annotations: Some(Annotations { + bold: Some(true), + code: Some(true), + color: Some(TextColor::Default), + italic: Some(true), + strikethrough: Some(true), + underline: Some(true), + }), + }, + text: Text { + content: "Rich".to_string(), + link: Some(Link { + url: "https://github.com/jakeswenson/notion".to_string() + }), + }, + } + ) +} + +#[test] +fn rich_text_mention_user_person() { + let rich_text_mention_user_person: RichText = + serde_json::from_str(include_str!("tests/rich_text_mention_user_person.json")).unwrap(); + assert_eq!( + rich_text_mention_user_person, + RichText::Mention { + rich_text: RichTextCommon { + plain_text: "@John Doe".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(false), + }), + }, + mention: MentionObject::User { + user: User::Person { + common: UserCommon { + id: UserId::from_str("1118608e-35e8-4fa3-aef7-a4ced85ce8e0").unwrap(), + name: Some("John Doe".to_string()), + avatar_url: Some( + "https://secure.notion-static.com/e6a352a8-8381-44d0-a1dc-9ed80e62b53d.jpg" + .to_string() + ), + }, + person: Person { + email: "john.doe@gmail.com".to_string() + }, + } + }, + } + ) +} + +#[test] +fn rich_text_mention_date() { + let rich_text_mention_date: RichText = + serde_json::from_str(include_str!("tests/rich_text_mention_date.json")).unwrap(); + assert_eq!( + rich_text_mention_date, + RichText::Mention { + rich_text: RichTextCommon { + plain_text: "2022-04-16 → ".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(false), + }), + }, + mention: MentionObject::Date { + date: DateValue { + start: DateOrDateTime::Date(NaiveDate::from_str("2022-04-16").unwrap()), + end: None, + time_zone: None, + } + }, + } + ) +} + +#[test] +fn rich_text_mention_date_with_time() { + let rich_text_mention_date_with_time: RichText = + serde_json::from_str(include_str!("tests/rich_text_mention_date_with_time.json")).unwrap(); + assert_eq!( + rich_text_mention_date_with_time, + RichText::Mention { + rich_text: RichTextCommon { + plain_text: "2022-05-14T09:00:00.000-04:00 → ".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(false), + }), + }, + mention: MentionObject::Date { + date: DateValue { + start: DateOrDateTime::DateTime( + DateTime::from_str("2022-05-14T09:00:00.000-04:00").unwrap() + ), + end: None, + time_zone: None, + } + }, + } + ) +} + +#[test] +fn rich_text_mention_date_with_end() { + let rich_text_mention_date_with_end: RichText = + serde_json::from_str(include_str!("tests/rich_text_mention_date_with_end.json")).unwrap(); + assert_eq!( + rich_text_mention_date_with_end, + RichText::Mention { + rich_text: RichTextCommon { + plain_text: "2022-05-12 → 2022-05-13".to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(false), + }), + }, + mention: MentionObject::Date { + date: DateValue { + start: DateOrDateTime::Date(NaiveDate::from_str("2022-05-12").unwrap()), + end: Some(DateOrDateTime::Date( + NaiveDate::from_str("2022-05-13").unwrap() + )), + time_zone: None, + } + }, + } + ) +} + +#[test] +fn rich_text_mention_date_with_end_and_time() { + let rich_text_mention_date_with_end_and_time: RichText = serde_json::from_str(include_str!( + "tests/rich_text_mention_date_with_end_and_time.json" + )) + .unwrap(); + assert_eq!( + rich_text_mention_date_with_end_and_time, + RichText::Mention { + rich_text: RichTextCommon { + plain_text: "2022-04-16T12:00:00.000-04:00 → 2022-04-16T12:00:00.000-04:00" + .to_string(), + href: None, + annotations: Some(Annotations { + bold: Some(false), + code: Some(false), + color: Some(TextColor::Default), + italic: Some(false), + strikethrough: Some(false), + underline: Some(false), + }), + }, + mention: MentionObject::Date { + date: DateValue { + start: DateOrDateTime::DateTime( + DateTime::from_str("2022-04-16T12:00:00.000-04:00").unwrap() + ), + end: Some(DateOrDateTime::DateTime( + DateTime::from_str("2022-04-16T12:00:00.000-04:00").unwrap() + )), + time_zone: None, + } + }, + } + ) +} diff --git a/crates/suiop-cli/src/cli/notion/models/tests/error.json b/crates/suiop-cli/src/cli/notion/models/tests/error.json new file mode 100644 index 0000000000000..0194824457313 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/tests/error.json @@ -0,0 +1,6 @@ +{ + "object": "error", + "status": 400, + "code": "validation_error", + "message": "Could not find property with name or id: LastEditedTime" +} diff --git a/crates/suiop-cli/src/cli/notion/models/tests/issue_15.json b/crates/suiop-cli/src/cli/notion/models/tests/issue_15.json new file mode 100644 index 0000000000000..7ca06153d016d --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/tests/issue_15.json @@ -0,0 +1,89 @@ +{ + "object": "list", + "results": [ + { + "object": "database", + "id": "58be2827-5ca0-4cc4-85a8-ff656911df67", + "created_time": "2021-03-07T19:20:00.000Z", + "last_edited_time": "2021-07-11T22:04:00.000Z", + "title": [ + { + "type": "text", + "text": { "content": "Movie Collection", "link": null }, + "annotations": { + "bold": false, + "italic": false, + "strikethrough": false, + "underline": false, + "code": false, + "color": "default" + }, + "plain_text": "Movie Collection", + "href": null + } + ], + "properties": { + "Watched": { "id": " +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Annotations { + pub bold: Option, + pub code: Option, + pub color: Option, + pub italic: Option, + pub strikethrough: Option, + pub underline: Option, +} + +/// Properties common on all rich text objects +/// See +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct RichTextCommon { + pub plain_text: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub href: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub annotations: Option, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Link { + pub url: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Text { + pub content: String, + pub link: Option, +} + +/// See https://developers.notion.com/reference/rich-text#mention-objects +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum MentionObject { + User { + user: User, + }, + // TODO: need to add tests + Page { + page: Page, + }, + // TODO: need to add tests + Database { + database: Database, + }, + Date { + date: DateValue, + }, + // TODO: need to add LinkPreview + // LinkPreview { + // + // }, + #[serde(other)] + Unknown, +} + +/// Rich text objects contain data for displaying formatted text, mentions, and equations. +/// A rich text object also contains annotations for style information. +/// Arrays of rich text objects are used within property objects and property +/// value objects to create what a user sees as a single text value in Notion. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum RichText { + /// See + Text { + #[serde(flatten)] + rich_text: RichTextCommon, + text: Text, + }, + /// See + Mention { + #[serde(flatten)] + rich_text: RichTextCommon, + mention: MentionObject, + }, + /// See + Equation { + #[serde(flatten)] + rich_text: RichTextCommon, + }, +} + +#[allow(dead_code)] +impl RichText { + pub fn plain_text(&self) -> &str { + use RichText::*; + match self { + Text { rich_text, .. } | Mention { rich_text, .. } | Equation { rich_text, .. } => { + &rich_text.plain_text + } + } + } +} diff --git a/crates/suiop-cli/src/cli/notion/models/users.rs b/crates/suiop-cli/src/cli/notion/models/users.rs new file mode 100644 index 0000000000000..0a98c58e70c23 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/models/users.rs @@ -0,0 +1,37 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use super::super::ids::UserId; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub struct UserCommon { + pub id: UserId, + pub name: Option, + pub avatar_url: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub struct Person { + pub email: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub struct Bot { + pub email: String, +} + +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum User { + Person { + #[serde(flatten)] + common: UserCommon, + person: Person, + }, + Bot { + #[serde(flatten)] + common: UserCommon, + bot: Bot, + }, +} diff --git a/crates/suiop-cli/src/cli/notion/properties.rs b/crates/suiop-cli/src/cli/notion/properties.rs new file mode 100644 index 0000000000000..307075ab3d736 --- /dev/null +++ b/crates/suiop-cli/src/cli/notion/properties.rs @@ -0,0 +1,520 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use crate::models::text::RichText; +use crate::models::users::User; + +use crate::ids::{DatabaseId, PageId, PropertyId}; +use crate::models::{DateTime, Number, Utc}; +use chrono::NaiveDate; +use serde::{Deserialize, Serialize}; + +pub mod formulas; + +#[cfg(test)] +mod tests; + +/// How the number is displayed in Notion. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Copy, Clone, Hash)] +#[serde(rename_all = "snake_case")] +pub enum NumberFormat { + Number, + NumberWithCommas, + Percent, + Dollar, + Euro, + Pound, + Yen, + Ruble, + Rupee, + Won, + Yuan, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Hash, Clone)] +pub struct NumberDetails { + pub format: NumberFormat, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone)] +#[serde(transparent)] +pub struct SelectOptionId(String); + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Copy, Clone)] +#[serde(rename_all = "lowercase")] +pub enum Color { + Default, + Gray, + Brown, + Orange, + Yellow, + Green, + Blue, + Purple, + Pink, + Red, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct SelectOption { + pub name: String, + pub id: SelectOptionId, + pub color: Color, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Select { + /// Sorted list of options available for this property. + pub options: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct StatusGroupOption { + pub name: String, + pub id: SelectOptionId, + pub color: Color, + pub option_ids: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Status { + /// Sorted list of options available for this property. + pub options: Vec, + /// Sorted list of groups available for this property. + pub groups: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Formula { + /// Formula to evaluate for this property + pub expression: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Relation { + /// The database this relation refers to. + /// New linked pages must belong to this database in order to be valid. + pub database_id: DatabaseId, + /// By default, relations are formed as two synced properties across databases: + /// if you make a change to one property, it updates the synced property at the same time. + /// `synced_property_name` refers to the name of the property in the related database. + pub synced_property_name: Option, + /// By default, relations are formed as two synced properties across databases: + /// if you make a change to one property, it updates the synced property at the same time. + /// `synced_property_id` refers to the id of the property in the related database. + /// This is usually a short string of random letters and symbols. + pub synced_property_id: Option, +} + +/// The function used to roll up the values of the relation property. +/// +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Copy, Clone)] +#[serde(rename_all = "snake_case")] +pub enum RollupFunction { + Average, + Checked, + Count, + CountPerGroup, + CountValues, + DateRange, + EarliestDate, + Empty, + LatestDate, + Max, + Median, + Min, + NotEmpty, + PercentChecked, + PercentEmpty, + PercentNotEmpty, + PercentPerGroup, + PercentUnchecked, + Range, + ShowOriginal, + ShowUnique, + Sum, + Unchecked, + Unique, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct Rollup { + /// The name of the relation property this property is responsible for rolling up. + pub relation_property_name: String, + /// The id of the relation property this property is responsible for rolling up. + pub relation_property_id: PropertyId, + /// The name of the property of the pages in the related database + /// that is used as an input to `function`. + pub rollup_property_name: String, + /// The id of the property of the pages in the related database + /// that is used as an input to `function`. + pub rollup_property_id: String, + /// The function that is evaluated for every page in the relation of the rollup. + pub function: RollupFunction, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum PropertyConfiguration { + /// Represents the special Title property required on every database. + /// See + Title { + id: PropertyId, + }, + /// Represents a Text property + /// + #[serde(rename = "rich_text")] + Text { + id: PropertyId, + }, + /// Represents a Number Property + /// See + Number { + id: PropertyId, + /// How the number is displayed in Notion. + number: NumberDetails, + }, + /// Represents a Select Property + /// See + Select { + id: PropertyId, + select: Select, + }, + /// Represents a Status property + Status { + id: PropertyId, + status: Status, + }, + /// Represents a Multi-select Property + /// See + MultiSelect { + id: PropertyId, + multi_select: Select, + }, + /// Represents a Date Property + /// See + Date { + id: PropertyId, + }, + /// Represents a People Property + /// See + People { + id: PropertyId, + }, + /// Represents a File Property + /// See + // Todo: File a bug with notion + // Documentation issue: docs claim type name is `file` but it is in fact `files` + Files { + id: PropertyId, + }, + /// Represents a Checkbox Property + /// See + Checkbox { + id: PropertyId, + }, + /// Represents a URL Property + /// See + Url { + id: PropertyId, + }, + /// Represents a Email Property + /// See + Email { + id: PropertyId, + }, + /// Represents a Phone number Property + /// See + PhoneNumber { + id: PropertyId, + }, + /// See + Formula { + id: PropertyId, + formula: Formula, + }, + /// See + Relation { + id: PropertyId, + relation: Relation, + }, + /// See + Rollup { + id: PropertyId, + rollup: Rollup, + }, + /// See + CreatedTime { + id: PropertyId, + }, + /// See + CreatedBy { + id: PropertyId, + }, + /// See + LastEditedTime { + id: PropertyId, + }, + /// See + LastEditBy { + id: PropertyId, + }, + UniqueId { + id: PropertyId, + }, + Button { + id: PropertyId, + }, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct SelectedValue { + #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub name: Option, + pub color: Color, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(untagged)] +pub enum DateOrDateTime { + Date(NaiveDate), + DateTime(DateTime), +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct DateValue { + pub start: DateOrDateTime, + pub end: Option, + pub time_zone: Option, +} + +/// Formula property value objects represent the result of evaluating a formula +/// described in the database's properties. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum FormulaResultValue { + String { string: Option }, + Number { number: Option }, + Boolean { boolean: Option }, + Date { date: Option }, +} + +/// Relation property value objects contain an array of page references within the relation property. +/// A page reference is an object with an id property, +/// with a string value (UUIDv4) corresponding to a page ID in another database. +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct RelationValue { + pub id: PageId, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type", rename_all = "snake_case")] +pub enum RollupValue { + Number { number: Option }, + Date { date: Option> }, + Array { array: Vec }, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +pub struct FileReference { + pub name: String, + pub url: String, + pub mime_type: String, +} + +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum PropertyValue { + // + Title { + id: PropertyId, + title: Vec, + }, + /// + #[serde(rename = "rich_text")] + Text { + id: PropertyId, + rich_text: Vec, + }, + /// + Number { + id: PropertyId, + number: Option, + }, + /// + Select { + id: PropertyId, + select: Option, + }, + /// + Status { + id: PropertyId, + status: Option, + }, + /// + MultiSelect { + id: PropertyId, + multi_select: Option>, + }, + /// + Date { + id: PropertyId, + date: Option, + }, + /// + Formula { + id: PropertyId, + formula: FormulaResultValue, + }, + /// + /// It is actually an array of relations + Relation { + id: PropertyId, + relation: Option>, + }, + /// + Rollup { + id: PropertyId, + rollup: Option, + }, + /// + People { + id: PropertyId, + people: Vec, + }, + /// + Files { + id: PropertyId, + files: Option>, + }, + /// + Checkbox { + id: PropertyId, + checkbox: bool, + }, + /// + Url { + id: PropertyId, + url: Option, + }, + /// + Email { + id: PropertyId, + email: Option, + }, + /// + PhoneNumber { + id: PropertyId, + phone_number: String, + }, + /// + CreatedTime { + id: PropertyId, + created_time: DateTime, + }, + /// + CreatedBy { + id: PropertyId, + created_by: User, + }, + /// + LastEditedTime { + id: PropertyId, + last_edited_time: DateTime, + }, + /// + LastEditedBy { + id: PropertyId, + last_edited_by: User, + }, + UniqueId { + id: PropertyId, + unique_id: UniqueidValue, + }, + Button { + id: PropertyId, + }, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct UniqueidValue { + pub prefix: Option, + pub number: u32, +} + +/// +#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone)] +#[serde(tag = "type")] +#[serde(rename_all = "snake_case")] +pub enum RollupPropertyValue { + /// + #[serde(rename = "rich_text")] + Text { + rich_text: Vec, + }, + /// + Number { + number: Option, + }, + /// + Select { + select: Option, + }, + Status { + status: Option, + }, + MultiSelect { + multi_select: Option>, + }, + Date { + date: Option, + }, + /// + Formula { + formula: FormulaResultValue, + }, + /// + /// It is actually an array of relations + Relation { + relation: Option>, + }, + /// + Rollup { + rollup: Option, + }, + People { + people: Vec, + }, + Files { + files: Option>, + }, + Checkbox { + checkbox: bool, + }, + Url { + url: Option, + }, + Email { + email: Option, + }, + PhoneNumber { + phone_number: String, + }, + CreatedTime { + created_time: DateTime, + }, + CreatedBy { + created_by: User, + }, + LastEditedTime { + last_edited_time: DateTime, + }, + LastEditedBy { + last_edited_by: User, + }, +} diff --git a/crates/suiop-cli/src/cli/pulumi/init.rs b/crates/suiop-cli/src/cli/pulumi/init.rs index 63af30be04fd4..7fa9551722fc7 100644 --- a/crates/suiop-cli/src/cli/pulumi/init.rs +++ b/crates/suiop-cli/src/cli/pulumi/init.rs @@ -12,6 +12,7 @@ use std::fs; use std::path::{Path, PathBuf}; use tracing::{debug, error, info, warn}; +#[derive(clap::Subcommand, Clone, Debug)] pub enum ProjectType { App, Service, @@ -295,11 +296,10 @@ fn create_basic_project( ); fs::create_dir_all(project_dir).context("failed to create project directory")?; // initialize pulumi project - run_pulumi_new(project_name, project_dir_str, project_opts).map_err(|e| { + run_pulumi_new(project_name, project_dir_str, project_opts).inspect_err(|_| { remove_project_dir(project_dir).unwrap(); let backend = get_current_backend().unwrap(); remove_stack(&backend, project_name, "mysten/dev").unwrap(); - e })?; // run go mod tidy to make sure all dependencies are installed run_go_mod_tidy(project_dir_str)?; @@ -323,11 +323,10 @@ fn create_mysten_k8s_project( fs::create_dir_all(project_dir).context("failed to create project directory")?; // initialize pulumi project run_pulumi_new_from_template(project_name, project_dir_str, project_type, project_opts) - .map_err(|e| { + .inspect_err(|_| { remove_project_dir(project_dir).unwrap(); let backend = get_current_backend().unwrap(); remove_stack(&backend, project_name, "mysten/dev").unwrap(); - e })?; // run go mod tidy to make sure all dependencies are installed run_go_mod_tidy(project_dir_str)?; @@ -360,12 +359,11 @@ fn get_encryption_key_id(project_name: &str) -> Result { ], None, ) - .map_err(|e| { + .inspect_err(|_| { error!( "Cannot list KMS keys, please add your Google account to {}", "pulumi/meta/gcp-iam-automation/config.toml".bright_yellow() ); - e })?; let stdout_str = String::from_utf8(output.stdout)?; let keys: Vec = serde_json::from_str(&stdout_str)?; diff --git a/crates/suiop-cli/src/cli/pulumi/mod.rs b/crates/suiop-cli/src/cli/pulumi/mod.rs index 2da28b08f29a2..68b0d6145e999 100644 --- a/crates/suiop-cli/src/cli/pulumi/mod.rs +++ b/crates/suiop-cli/src/cli/pulumi/mod.rs @@ -8,7 +8,7 @@ use anyhow::Result; use clap::Parser; use init::ProjectType; use setup::ensure_gcloud; -use setup::ensure_setup; +use setup::ensure_pulumi_setup; #[derive(Parser, Debug, Clone)] pub struct PulumiArgs { @@ -21,20 +21,9 @@ pub enum PulumiAction { /// initialize a new pulumi project #[command(name = "init", aliases=["i"])] InitProject { - /// initialize app project - #[arg(short, long, group = "type")] - app: bool, - - #[arg(short, long, group = "type")] - service: bool, - - /// initialize barebones project (default) - #[arg(short, long, group = "type")] - basic: bool, - - /// initialize cronjob project - #[arg(short, long, group = "type")] - cronjob: bool, + /// subcommand for project type + #[command(subcommand)] + project_type: ProjectType, /// use GCP KMS as encryption provider #[arg(short, long, group = "feature")] @@ -46,26 +35,17 @@ pub enum PulumiAction { }, } -pub async fn pulumi_cmd(args: &PulumiArgs) -> Result<()> { - ensure_setup()?; +pub fn pulumi_cmd(args: &PulumiArgs) -> Result<()> { + ensure_pulumi_setup()?; match &args.action { PulumiAction::InitProject { - app, - service, - basic, - cronjob, + project_type, kms, project_name, } => { if *kms { ensure_gcloud()?; } - let project_type = match (app, service, basic, cronjob) { - (false, true, false, false) => ProjectType::Service, - (true, false, false, false) => ProjectType::App, - (false, false, false, true) => ProjectType::CronJob, - (_, _, _, _) => ProjectType::Basic, - }; project_type.create_project(kms, project_name.clone()) } } diff --git a/crates/suiop-cli/src/cli/pulumi/setup.rs b/crates/suiop-cli/src/cli/pulumi/setup.rs index c5c4452ce5194..fe4922a29ce2c 100644 --- a/crates/suiop-cli/src/cli/pulumi/setup.rs +++ b/crates/suiop-cli/src/cli/pulumi/setup.rs @@ -156,7 +156,7 @@ pub fn ensure_gcloud() -> Result<()> { } } -pub fn ensure_setup() -> Result<()> { +pub fn ensure_pulumi_setup() -> Result<()> { let home = env::var("HOME").unwrap(); // check for marker file let setup_marker = PathBuf::from(format!("{}/.suiop/pulumi_setup", home)); diff --git a/crates/suiop-cli/src/cli/slack/mod.rs b/crates/suiop-cli/src/cli/slack/mod.rs index bf7907a07e393..95d77d601ee53 100644 --- a/crates/suiop-cli/src/cli/slack/mod.rs +++ b/crates/suiop-cli/src/cli/slack/mod.rs @@ -19,7 +19,7 @@ pub use slack_api::*; pub struct Slack { client: Client, pub channels: Vec, - pub users: Vec, + pub users: Vec, } fn get_serialize_filepath(subname: &str) -> PathBuf { @@ -42,9 +42,12 @@ pub fn serialize_to_file(subname: &str, obj: &Vec) -> Result<() /// /// Otherwise return None pub fn deserialize_from_file(subname: &str) -> Option> { + let force_refresh = std::env::var("FORCE_REFRESH").is_ok(); let mut result = None; let file_path = get_serialize_filepath(subname); - if let Ok(metadata) = file_path.metadata() { + if force_refresh { + result = None; + } else if let Ok(metadata) = file_path.metadata() { if let Ok(modified) = metadata.modified() { if let Ok(elapsed) = modified.elapsed() { // 1 day diff --git a/crates/suiop-cli/src/cli/slack/slack_api.rs b/crates/suiop-cli/src/cli/slack/slack_api.rs index 633c5f652987d..11649d12f2a1e 100644 --- a/crates/suiop-cli/src/cli/slack/slack_api.rs +++ b/crates/suiop-cli/src/cli/slack/slack_api.rs @@ -10,22 +10,29 @@ use serde::Serialize; use std::fmt::Display; use std::fmt::Formatter; use std::time::{SystemTime, UNIX_EPOCH}; +use tracing::debug; const CHANNELS_URL: &str = "https://slack.com/api/conversations.list"; #[derive(Debug, Deserialize, Serialize, Clone)] pub struct UsersResponse { ok: bool, - members: Option>, + members: Option>, } #[derive(Debug, Deserialize, Serialize, Clone)] -pub struct User { +pub struct SlackUser { pub id: String, pub name: String, + pub profile: Option, } -impl Display for User { +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct Profile { + pub email: Option, +} + +impl Display for SlackUser { fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { write!(f, "{}", self.name) } @@ -47,7 +54,7 @@ struct ConversationsResponse { ok: bool, error: Option, channels: Option>, - response_metadata: ResponseMetadata, + response_metadata: Option, } #[derive(Debug, Deserialize, Serialize, Clone)] @@ -68,9 +75,21 @@ pub async fn get_channels(client: &Client) -> Result> { .map_err(|e| anyhow!(e))? .json() .await?; - let new_channels = result.channels.expect("Expected channels to exist").clone(); + let new_channels = result + .clone() + .channels + .unwrap_or_else(|| panic!("Expected channels to exist for {:?}", result)) + .clone(); channels.extend(new_channels.into_iter()); - while let Some(cursor) = result.response_metadata.next_cursor { + if result.response_metadata.is_none() { + debug!("No pagination in channels response"); + return Ok(channels); + } + while let Some(cursor) = result + .response_metadata + .expect("Expected response metadata") + .next_cursor + { if cursor.is_empty() { break; } @@ -83,14 +102,18 @@ pub async fn get_channels(client: &Client) -> Result> { .json() .await .context("parsing json from channels api")?; - let extra_channels = result.channels.expect("Expected channels to exist").clone(); + let extra_channels = result + .clone() + .channels + .unwrap_or_else(|| panic!("Expected channels to exist for {:?}", result)) + .clone(); channels.extend(extra_channels.into_iter()); } channels = channels.iter().map(|c| (*c).clone()).collect(); Ok(channels) } -pub async fn get_users(client: &Client) -> Result> { +pub async fn get_users(client: &Client) -> Result> { let url = "https://slack.com/api/users.list"; let response = client .get(url) diff --git a/crates/suiop-cli/src/main.rs b/crates/suiop-cli/src/main.rs index f897e903feef9..3d3c7c99c83ea 100644 --- a/crates/suiop-cli/src/main.rs +++ b/crates/suiop-cli/src/main.rs @@ -68,7 +68,7 @@ async fn main() -> Result<()> { incidents_cmd(&args).await?; } Resource::Pulumi(args) => { - pulumi_cmd(&args).await?; + pulumi_cmd(&args)?; } Resource::Service(args) => { service_cmd(&args).await?; diff --git a/crates/telemetry-subscribers/Cargo.toml b/crates/telemetry-subscribers/Cargo.toml index 0f6e9ba7305ff..1ed249129edac 100644 --- a/crates/telemetry-subscribers/Cargo.toml +++ b/crates/telemetry-subscribers/Cargo.toml @@ -17,11 +17,12 @@ prometheus.workspace = true tracing.workspace = true tracing-appender.workspace = true tracing-subscriber.workspace = true -opentelemetry = { version = "0.20.0", features = ["rt-tokio"], optional = true } +opentelemetry = { version = "0.25.0", optional = true } opentelemetry_api = { version = "0.20.0", optional = true } -opentelemetry-otlp = { version = "0.13.0", features = ["grpc-tonic"], optional = true } -tracing-opentelemetry = { version = "0.21.0", optional = true } -opentelemetry-proto = { version = "0.3", optional = true } +opentelemetry_sdk = { version = "0.25.0", features = ["rt-tokio"], optional = true } +opentelemetry-otlp = { version = "0.25.0", features = ["grpc-tonic"], optional = true } +tracing-opentelemetry = { version = "0.26.0", optional = true } +opentelemetry-proto = { version = "0.25", optional = true } tokio = { workspace = true, features = ["full"] } futures.workspace = true clap.workspace = true @@ -30,8 +31,8 @@ bytes-varint = { version = "1" } # must use same version as opentelemetry for tonic and prost, so we can't use from # workspace -tonic = { version = "0.9" } -prost = "0.11.9" +tonic = { version = "0.12.3" } +prost = "0.13" [features] default = ["otlp"] @@ -41,7 +42,8 @@ otlp = [ "opentelemetry", "opentelemetry-otlp", "opentelemetry-proto", - "opentelemetry_api" + "opentelemetry_api", + "opentelemetry_sdk" ] [dev-dependencies] diff --git a/crates/telemetry-subscribers/src/bin/import-trace.rs b/crates/telemetry-subscribers/src/bin/import-trace.rs index 82b0727bc1d75..6b5566b3aaeed 100644 --- a/crates/telemetry-subscribers/src/bin/import-trace.rs +++ b/crates/telemetry-subscribers/src/bin/import-trace.rs @@ -6,7 +6,7 @@ use bytes_varint::VarIntSupport; use clap::*; use opentelemetry_proto::tonic::{ collector::trace::v1::{trace_service_client::TraceServiceClient, ExportTraceServiceRequest}, - common::v1::{any_value, AnyValue}, + common::v1::{any_value, AnyValue, KeyValue}, }; use prost::Message; use std::io::{self, Cursor, Read}; @@ -38,7 +38,7 @@ async fn main() { if args.dump_spans { for message in messages.iter() { for span in &message.resource_spans { - println!("{:?}", span); + println!("{:#?}", span); } } return; @@ -58,24 +58,36 @@ async fn main() { println!("importing trace with service name {:?}", service_name); for mut message in messages { - println!( - "sending {} spans to otlp collector", - message.resource_spans.len() - ); + let mut span_count = 0; // Rewrite the service name to separate the imported trace from other traces for resource_span in message.resource_spans.iter_mut() { + for scope_span in resource_span.scope_spans.iter() { + span_count += scope_span.spans.len(); + } + if let Some(resource) = resource_span.resource.as_mut() { + let mut service_name_found = false; for attr in resource.attributes.iter_mut() { if attr.key == "service.name" { + service_name_found = true; attr.value = Some(AnyValue { value: Some(any_value::Value::StringValue(service_name.clone())), }); } } + if !service_name_found { + resource.attributes.push(KeyValue { + key: "service.name".to_string(), + value: Some(AnyValue { + value: Some(any_value::Value::StringValue(service_name.clone())), + }), + }); + } } } + println!("sending {} spans to otlp collector", span_count); trace_exporter.export(Request::new(message)).await.unwrap(); } println!("all spans imported"); diff --git a/crates/telemetry-subscribers/src/file_exporter.rs b/crates/telemetry-subscribers/src/file_exporter.rs index f145860b1c832..34fa0d16dae6f 100644 --- a/crates/telemetry-subscribers/src/file_exporter.rs +++ b/crates/telemetry-subscribers/src/file_exporter.rs @@ -2,9 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 use futures::{future::BoxFuture, FutureExt}; -use opentelemetry::sdk::export::trace::{ExportResult, SpanData, SpanExporter}; use opentelemetry::trace::TraceError; -use opentelemetry_proto::tonic::collector::trace::v1::ExportTraceServiceRequest; +use opentelemetry_proto::{ + tonic::collector::trace::v1::ExportTraceServiceRequest, + transform::{ + common::tonic::ResourceAttributesWithSchema, + trace::tonic::group_spans_by_resource_and_scope, + }, +}; +use opentelemetry_sdk::export::trace::{ExportResult, SpanData, SpanExporter}; use prost::Message; use std::fs::OpenOptions; use std::io::Write; @@ -70,12 +76,14 @@ impl CachedOpenFile { #[derive(Debug)] pub(crate) struct FileExporter { pub cached_open_file: CachedOpenFile, + resource: ResourceAttributesWithSchema, } impl FileExporter { pub fn new(file_path: Option) -> std::io::Result { Ok(Self { cached_open_file: CachedOpenFile::new(file_path)?, + resource: ResourceAttributesWithSchema::default(), }) } } @@ -83,13 +91,12 @@ impl FileExporter { impl SpanExporter for FileExporter { fn export(&mut self, batch: Vec) -> BoxFuture<'static, ExportResult> { let cached_open_file = self.cached_open_file.clone(); + let resource_spans = group_spans_by_resource_and_scope(batch, &self.resource); async move { cached_open_file .with_file(|maybe_file| { if let Some(file) = maybe_file { - let request = ExportTraceServiceRequest { - resource_spans: batch.into_iter().map(Into::into).collect(), - }; + let request = ExportTraceServiceRequest { resource_spans }; let buf = request.encode_length_delimited_to_vec(); diff --git a/crates/telemetry-subscribers/src/lib.rs b/crates/telemetry-subscribers/src/lib.rs index 7221034ad9273..1ccd7c65e57cd 100644 --- a/crates/telemetry-subscribers/src/lib.rs +++ b/crates/telemetry-subscribers/src/lib.rs @@ -4,20 +4,18 @@ use atomic_float::AtomicF64; use crossterm::tty::IsTty; use once_cell::sync::Lazy; -use opentelemetry::sdk::trace::Sampler; -use opentelemetry::sdk::{ +use opentelemetry::{ + trace::{Link, SamplingResult, SpanKind, TraceId, TracerProvider as _}, + Context, KeyValue, +}; +use opentelemetry_otlp::WithExportConfig; +use opentelemetry_sdk::trace::Sampler; +use opentelemetry_sdk::{ self, runtime, trace::{BatchSpanProcessor, ShouldSample, TracerProvider}, Resource, }; -use opentelemetry::trace::TracerProvider as _; -use opentelemetry_api::{ - trace::{Link, SamplingResult, SpanKind, TraceId}, - Context, Key, OrderMap, Value, -}; -use opentelemetry_otlp::WithExportConfig; use span_latency_prom::PrometheusSpanLatencyLayer; -use std::collections::hash_map::RandomState; use std::path::PathBuf; use std::time::Duration; use std::{ @@ -385,7 +383,7 @@ impl TelemetryConfig { if config.enable_otlp_tracing { let trace_file = env::var("TRACE_FILE").ok(); - let config = sdk::trace::config() + let config = opentelemetry_sdk::trace::Config::default() .with_resource(Resource::new(vec![opentelemetry::KeyValue::new( "service.name", service_name.clone(), @@ -413,7 +411,7 @@ impl TelemetryConfig { let endpoint = env::var("OTLP_ENDPOINT") .unwrap_or_else(|_| "http://localhost:4317".to_string()); - let tracer = opentelemetry_otlp::new_pipeline() + let p = opentelemetry_otlp::new_pipeline() .tracing() .with_exporter( opentelemetry_otlp::new_exporter() @@ -421,15 +419,17 @@ impl TelemetryConfig { .with_endpoint(endpoint), ) .with_trace_config(config) - .install_batch(sdk::runtime::Tokio) + .install_batch(runtime::Tokio) .expect("Could not create async Tracer"); + let tracer = p.tracer(service_name); + tracing_opentelemetry::layer().with_tracer(tracer) }; // Enable Trace Contexts for tying spans together opentelemetry::global::set_text_map_propagator( - opentelemetry::sdk::propagation::TraceContextPropagator::new(), + opentelemetry_sdk::propagation::TraceContextPropagator::new(), ); let trace_env_filter = EnvFilter::try_from_env("TRACE_FILTER").unwrap(); @@ -517,7 +517,7 @@ impl ShouldSample for SamplingFilter { trace_id: TraceId, name: &str, span_kind: &SpanKind, - attributes: &OrderMap, + attributes: &[KeyValue], links: &[Link], ) -> SamplingResult { let sample_rate = self.sample_rate.load(Ordering::Relaxed); diff --git a/crates/test-cluster/Cargo.toml b/crates/test-cluster/Cargo.toml index 6f967e93389b8..58ca4d37edd93 100644 --- a/crates/test-cluster/Cargo.toml +++ b/crates/test-cluster/Cargo.toml @@ -31,7 +31,6 @@ sui-swarm.workspace = true sui-types = { workspace = true, features = ["test-utils"] } prometheus.workspace = true sui-keys.workspace = true -sui-bridge.workspace = true sui-sdk.workspace = true sui-test-transaction-builder.workspace = true diff --git a/crates/test-cluster/src/lib.rs b/crates/test-cluster/src/lib.rs index 19df6434bf337..05643d8716b01 100644 --- a/crates/test-cluster/src/lib.rs +++ b/crates/test-cluster/src/lib.rs @@ -1,35 +1,21 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use futures::Future; use futures::{future::join_all, StreamExt}; -use jsonrpsee::core::RpcResult; use jsonrpsee::http_client::{HttpClient, HttpClientBuilder}; use rand::{distributions::*, rngs::OsRng, seq::SliceRandom}; -use std::collections::BTreeMap; use std::collections::HashMap; use std::net::SocketAddr; use std::num::NonZeroUsize; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::sync::{Arc, Mutex}; use std::time::Duration; -use sui_bridge::crypto::{BridgeAuthorityKeyPair, BridgeAuthoritySignInfo}; -use sui_bridge::sui_transaction_builder::build_add_tokens_on_sui_transaction; -use sui_bridge::sui_transaction_builder::build_committee_register_transaction; -use sui_bridge::types::BridgeCommitteeValiditySignInfo; -use sui_bridge::types::CertifiedBridgeAction; -use sui_bridge::types::VerifiedCertifiedBridgeAction; -use sui_bridge::utils::publish_and_register_coins_return_add_coins_on_sui_action; -use sui_bridge::utils::wait_for_server_to_be_up; use sui_config::genesis::Genesis; -use sui_config::local_ip_utils::get_available_port; use sui_config::node::{AuthorityOverloadConfig, DBCheckpointConfig, RunWithRange}; use sui_config::{Config, SUI_CLIENT_CONFIG, SUI_NETWORK_CONFIG}; use sui_config::{NodeConfig, PersistedConfig, SUI_KEYSTORE_FILENAME}; use sui_core::authority_aggregator::AuthorityAggregator; use sui_core::authority_client::NetworkAuthorityClient; -use sui_json_rpc_api::BridgeReadApiClient; -use sui_json_rpc_types::SuiTransactionBlockResponseOptions; use sui_json_rpc_types::{ SuiExecutionStatus, SuiTransactionBlockEffectsAPI, SuiTransactionBlockResponse, TransactionFilter, @@ -54,12 +40,10 @@ use sui_swarm_config::node_config_builder::{FullnodeConfigBuilder, ValidatorConf use sui_test_transaction_builder::TestTransactionBuilder; use sui_types::base_types::ConciseableName; use sui_types::base_types::{AuthorityName, ObjectID, ObjectRef, SuiAddress}; -use sui_types::bridge::{get_bridge, TOKEN_ID_BTC, TOKEN_ID_ETH, TOKEN_ID_USDC, TOKEN_ID_USDT}; -use sui_types::bridge::{get_bridge_obj_initial_shared_version, BridgeSummary, BridgeTrait}; use sui_types::committee::CommitteeTrait; use sui_types::committee::{Committee, EpochId}; +use sui_types::crypto::KeypairTraits; use sui_types::crypto::SuiKeyPair; -use sui_types::crypto::{KeypairTraits, ToFromBytes}; use sui_types::effects::{TransactionEffects, TransactionEvents}; use sui_types::error::SuiResult; use sui_types::governance::MIN_VALIDATOR_JOINING_STAKE_MIST; @@ -71,10 +55,8 @@ use sui_types::sui_system_state::SuiSystemStateTrait; use sui_types::supported_protocol_versions::SupportedProtocolVersions; use sui_types::traffic_control::{PolicyConfig, RemoteFirewallConfig}; use sui_types::transaction::{ - CertifiedTransaction, ObjectArg, Transaction, TransactionData, TransactionDataAPI, - TransactionKind, + CertifiedTransaction, Transaction, TransactionData, TransactionDataAPI, TransactionKind, }; -use sui_types::SUI_BRIDGE_OBJECT_ID; use tokio::time::{timeout, Instant}; use tokio::{task::JoinHandle, time::sleep}; use tracing::{error, info}; @@ -108,9 +90,6 @@ pub struct TestCluster { pub swarm: Swarm, pub wallet: WalletContext, pub fullnode_handle: FullNodeHandle, - - pub bridge_authority_keys: Option>, - pub bridge_server_ports: Option>, } impl TestCluster { @@ -268,10 +247,6 @@ impl TestCluster { .compute_object_reference() } - pub async fn get_bridge_summary(&self) -> RpcResult { - self.sui_client().http().get_latest_bridge().await - } - pub async fn get_object_or_tombstone_from_fullnode_store( &self, object_id: ObjectID, @@ -516,54 +491,6 @@ impl TestCluster { } } - pub async fn trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized(&self) { - let mut bridge = - get_bridge(self.fullnode_handle.sui_node.state().get_object_store()).unwrap(); - if !bridge.committee().members.contents.is_empty() { - assert_eq!( - self.swarm.active_validators().count(), - bridge.committee().members.contents.len() - ); - return; - } - // wait for next epoch - self.trigger_reconfiguration().await; - bridge = get_bridge(self.fullnode_handle.sui_node.state().get_object_store()).unwrap(); - // Committee should be initiated - assert!(bridge.committee().member_registrations.contents.is_empty()); - assert_eq!( - self.swarm.active_validators().count(), - bridge.committee().members.contents.len() - ); - } - - // Wait for bridge node in the cluster to be up and running. - pub async fn wait_for_bridge_cluster_to_be_up(&self, timeout_sec: u64) { - let bridge_ports = self.bridge_server_ports.as_ref().unwrap(); - let mut tasks = vec![]; - for port in bridge_ports.iter() { - let server_url = format!("http://127.0.0.1:{}", port); - tasks.push(wait_for_server_to_be_up(server_url, timeout_sec)); - } - join_all(tasks) - .await - .into_iter() - .collect::>>() - .unwrap(); - } - - pub async fn get_mut_bridge_arg(&self) -> Option { - get_bridge_obj_initial_shared_version( - self.fullnode_handle.sui_node.state().get_object_store(), - ) - .unwrap() - .map(|seq| ObjectArg::SharedObject { - id: SUI_BRIDGE_OBJECT_ID, - initial_shared_version: seq, - mutable: true, - }) - } - pub async fn wait_for_authenticator_state_update(&self) { timeout( Duration::from_secs(60), @@ -883,6 +810,7 @@ pub struct TestClusterBuilder { num_validators: Option, fullnode_rpc_port: Option, enable_fullnode_events: bool, + disable_fullnode_pruning: bool, validator_supported_protocol_versions_config: ProtocolVersionsConfig, // Default to validator_supported_protocol_versions_config, but can be overridden. fullnode_supported_protocol_versions_config: Option, @@ -912,6 +840,7 @@ impl TestClusterBuilder { fullnode_rpc_port: None, num_validators: None, enable_fullnode_events: false, + disable_fullnode_pruning: false, validator_supported_protocol_versions_config: ProtocolVersionsConfig::Default, fullnode_supported_protocol_versions_config: None, db_checkpoint_config_validators: DBCheckpointConfig::default(), @@ -982,6 +911,11 @@ impl TestClusterBuilder { self } + pub fn disable_fullnode_pruning(mut self) -> Self { + self.disable_fullnode_pruning = true; + self + } + pub fn with_enable_db_checkpoints_validators(mut self) -> Self { self.db_checkpoint_config_validators = DBCheckpointConfig { perform_db_checkpoints_at_epoch_end: true, @@ -1184,212 +1118,9 @@ impl TestClusterBuilder { swarm, wallet, fullnode_handle, - bridge_authority_keys: None, - bridge_server_ports: None, } } - pub async fn build_with_bridge( - self, - // Note: caller should make sure to keep the authority with largest stake at the end of the list. - // This is because we try to set up eth env and sui cluster at the same time. Since we use evenly - // distributed stake, we want to keep the authority with the remainder consistent in eth contracts - // and sui cluster. - bridge_authority_keys: Vec, - deploy_tokens: bool, - ) -> TestCluster { - let timer = Instant::now(); - let gas_objects_for_authority_keys = bridge_authority_keys - .iter() - .map(|k| { - let address = SuiAddress::from(k.public()); - Object::with_id_owner_for_testing(ObjectID::random(), address) - }) - .collect::>(); - let mut test_cluster = self - .with_objects(gas_objects_for_authority_keys) - .build() - .await; - info!( - "TestCluster build took {:?} secs", - timer.elapsed().as_secs() - ); - let ref_gas_price = test_cluster.get_reference_gas_price().await; - let bridge_arg = test_cluster.get_mut_bridge_arg().await.unwrap(); - assert_eq!( - bridge_authority_keys.len(), - test_cluster.swarm.active_validators().count() - ); - - // Committee registers themselves - let mut server_ports = vec![]; - let mut tasks = vec![]; - let quorum_driver_api = test_cluster.quorum_driver_api().clone(); - // Reorder the nodes so that the last node has the largest stake. - let validator_with_max_stake = test_cluster - .sui_client() - .governance_api() - .get_committee_info(None) - .await - .unwrap() - .validators - .iter() - .max_by(|a, b| a.0.cmp(&b.0)) - .unwrap() - .0; - let node_with_max_stake = test_cluster - .swarm - .active_validators() - .find(|v| v.config().protocol_public_key() == validator_with_max_stake) - .unwrap(); - let other_nodes = test_cluster - .swarm - .active_validators() - .filter(|v| v.config().protocol_public_key() != validator_with_max_stake) - .collect::>(); - let reordered_nodes = other_nodes - .iter() - .chain(std::iter::once(&node_with_max_stake)); - for (node, kp) in reordered_nodes.zip(bridge_authority_keys.iter()) { - let validator_address = node.config().sui_address(); - // create committee registration tx - let gas = test_cluster - .wallet - .get_one_gas_object_owned_by_address(validator_address) - .await - .unwrap() - .unwrap(); - - let server_port = get_available_port("127.0.0.1"); - let server_url = format!("http://127.0.0.1:{}", server_port); - server_ports.push(server_port); - let data = build_committee_register_transaction( - validator_address, - &gas, - bridge_arg, - kp.public().as_bytes().to_vec(), - &server_url, - ref_gas_price, - 1000000000, - ) - .unwrap(); - - let tx = Transaction::from_data_and_signer( - data, - vec![node.config().account_key_pair.keypair()], - ); - let api_clone = quorum_driver_api.clone(); - tasks.push(async move { - api_clone - .execute_transaction_block( - tx, - SuiTransactionBlockResponseOptions::new().with_effects(), - None, - ) - .await - }); - } - - if deploy_tokens { - let timer = Instant::now(); - let token_ids = vec![TOKEN_ID_BTC, TOKEN_ID_ETH, TOKEN_ID_USDC, TOKEN_ID_USDT]; - let token_prices = vec![500_000_000u64, 30_000_000u64, 1_000u64, 1_000u64]; - let action = publish_and_register_coins_return_add_coins_on_sui_action( - test_cluster.wallet(), - bridge_arg, - vec![ - Path::new("../../bridge/move/tokens/btc").into(), - Path::new("../../bridge/move/tokens/eth").into(), - Path::new("../../bridge/move/tokens/usdc").into(), - Path::new("../../bridge/move/tokens/usdt").into(), - ], - token_ids, - token_prices, - 0, - ); - let action = action.await; - info!("register tokens took {:?} secs", timer.elapsed().as_secs()); - let sig_map = bridge_authority_keys - .iter() - .map(|key| { - ( - key.public().into(), - BridgeAuthoritySignInfo::new(&action, key).signature, - ) - }) - .collect::>(); - let certified_action = CertifiedBridgeAction::new_from_data_and_sig( - action, - BridgeCommitteeValiditySignInfo { - signatures: sig_map.clone(), - }, - ); - let verifired_action_cert = - VerifiedCertifiedBridgeAction::new_from_verified(certified_action); - let sender_address = test_cluster.get_address_0(); - - await_committee_register_tasks(&test_cluster, tasks).await; - - // Wait until committee is set up - test_cluster - .trigger_reconfiguration_if_not_yet_and_assert_bridge_committee_initialized() - .await; - - let tx = build_add_tokens_on_sui_transaction( - sender_address, - &test_cluster - .wallet - .get_one_gas_object_owned_by_address(sender_address) - .await - .unwrap() - .unwrap(), - verifired_action_cert, - bridge_arg, - ref_gas_price, - ) - .unwrap(); - - let response = test_cluster.sign_and_execute_transaction(&tx).await; - assert_eq!( - response.effects.unwrap().status(), - &SuiExecutionStatus::Success - ); - info!("Deploy tokens took {:?} secs", timer.elapsed().as_secs()); - } else { - await_committee_register_tasks(&test_cluster, tasks).await; - } - - async fn await_committee_register_tasks( - test_cluster: &TestCluster, - tasks: Vec< - impl Future>, - >, - ) { - // The tx may fail if a member tries to register when the committee is already finalized. - // In that case, we just need to check the committee members is not empty since once - // the committee is finalized, it should not be empty. - let responses = join_all(tasks).await; - let mut has_failure = false; - for response in responses { - if response.unwrap().effects.unwrap().status() != &SuiExecutionStatus::Success { - has_failure = true; - } - } - if has_failure { - let bridge_summary = test_cluster.get_bridge_summary().await.unwrap(); - assert_ne!(bridge_summary.committee.members.len(), 0); - } - } - - info!( - "TestCluster build_with_bridge took {:?} secs", - timer.elapsed().as_secs() - ); - test_cluster.bridge_authority_keys = Some(bridge_authority_keys); - test_cluster.bridge_server_ports = Some(server_ports); - test_cluster - } - /// Start a Swarm and set up WalletConfig async fn start_swarm(&mut self) -> Result { let mut builder: SwarmBuilder = Swarm::builder() @@ -1455,6 +1186,10 @@ impl TestClusterBuilder { builder.with_submit_delay_step_override_millis(submit_delay_step_override_millis); } + if self.disable_fullnode_pruning { + builder = builder.with_disable_fullnode_pruning(); + } + let mut swarm = builder.build(); swarm.launch().await?; diff --git a/crates/typed-store-workspace-hack/Cargo.toml b/crates/typed-store-workspace-hack/Cargo.toml index 2fa755d1fe310..f86e57cd2f03e 100644 --- a/crates/typed-store-workspace-hack/Cargo.toml +++ b/crates/typed-store-workspace-hack/Cargo.toml @@ -9,6 +9,7 @@ name = "typed-store-workspace-hack" version = "0.0.0" license = "Apache-2.0" +edition = "2021" publish = false [dependencies] diff --git a/crates/typed-store/src/rocks/mod.rs b/crates/typed-store/src/rocks/mod.rs index 5f65067cc4525..7321acc3aa56f 100644 --- a/crates/typed-store/src/rocks/mod.rs +++ b/crates/typed-store/src/rocks/mod.rs @@ -62,6 +62,7 @@ const DEFAULT_DB_WAL_SIZE: usize = 1024; // Environment variable to control behavior of write throughput optimized tables. const ENV_VAR_L0_NUM_FILES_COMPACTION_TRIGGER: &str = "L0_NUM_FILES_COMPACTION_TRIGGER"; const DEFAULT_L0_NUM_FILES_COMPACTION_TRIGGER: usize = 4; +const DEFAULT_UNIVERSAL_COMPACTION_L0_NUM_FILES_COMPACTION_TRIGGER: usize = 80; const ENV_VAR_MAX_WRITE_BUFFER_SIZE_MB: &str = "MAX_WRITE_BUFFER_SIZE_MB"; const DEFAULT_MAX_WRITE_BUFFER_SIZE_MB: usize = 256; const ENV_VAR_MAX_WRITE_BUFFER_NUMBER: &str = "MAX_WRITE_BUFFER_NUMBER"; @@ -838,6 +839,10 @@ impl DBMap { Ok(DBMap::new(db.clone(), rw_options, &cf_key, is_deprecated)) } + pub fn cf_name(&self) -> &str { + &self.cf + } + pub fn batch(&self) -> DBBatch { let batch = match *self.rocksdb { RocksDB::DBWithThreadMode(_) => RocksDBBatch::Regular(WriteBatch::default()), @@ -915,7 +920,7 @@ impl DBMap { property_name: &std::ffi::CStr, ) -> Result { match rocksdb.property_int_value_cf(cf, property_name) { - Ok(Some(value)) => Ok(value.try_into().unwrap()), + Ok(Some(value)) => Ok(value.min(i64::MAX as u64).try_into().unwrap_or_default()), Ok(None) => Ok(0), Err(e) => Err(TypedStoreError::RocksDBError(e.into_string())), } @@ -976,7 +981,14 @@ impl DBMap { } fn report_metrics(rocksdb: &Arc, cf_name: &str, db_metrics: &Arc) { - let cf = rocksdb.cf_handle(cf_name).expect("Failed to get cf"); + let Some(cf) = rocksdb.cf_handle(cf_name) else { + tracing::warn!( + "unable to report metrics for cf {cf_name:?} in db {:?}", + rocksdb.db_name() + ); + return; + }; + db_metrics .cf_metrics .rocksdb_total_sst_files_size @@ -2443,7 +2455,7 @@ impl DBOptions { // Optimize tables with a mix of lookup and scan workloads. pub fn optimize_for_read(mut self, block_cache_size_mb: usize) -> DBOptions { self.options - .set_block_based_table_factory(&get_block_options(block_cache_size_mb)); + .set_block_based_table_factory(&get_block_options(block_cache_size_mb, 16 << 10)); self } @@ -2500,6 +2512,74 @@ impl DBOptions { self } + // Optimize tables receiving significant insertions, without any deletions. + // TODO: merge this function with optimize_for_write_throughput(), and use a flag to + // indicate if deletion is received. + pub fn optimize_for_write_throughput_no_deletion(mut self) -> DBOptions { + // Increase write buffer size to 256MiB. + let write_buffer_size = read_size_from_env(ENV_VAR_MAX_WRITE_BUFFER_SIZE_MB) + .unwrap_or(DEFAULT_MAX_WRITE_BUFFER_SIZE_MB) + * 1024 + * 1024; + self.options.set_write_buffer_size(write_buffer_size); + // Increase write buffers to keep to 6 before slowing down writes. + let max_write_buffer_number = read_size_from_env(ENV_VAR_MAX_WRITE_BUFFER_NUMBER) + .unwrap_or(DEFAULT_MAX_WRITE_BUFFER_NUMBER); + self.options + .set_max_write_buffer_number(max_write_buffer_number.try_into().unwrap()); + // Keep 1 write buffer so recent writes can be read from memory. + self.options + .set_max_write_buffer_size_to_maintain((write_buffer_size).try_into().unwrap()); + + // Switch to universal compactions. + self.options + .set_compaction_style(rocksdb::DBCompactionStyle::Universal); + let mut compaction_options = rocksdb::UniversalCompactOptions::default(); + compaction_options.set_max_size_amplification_percent(10000); + compaction_options.set_stop_style(rocksdb::UniversalCompactionStopStyle::Similar); + self.options + .set_universal_compaction_options(&compaction_options); + + let max_level_zero_file_num = read_size_from_env(ENV_VAR_L0_NUM_FILES_COMPACTION_TRIGGER) + .unwrap_or(DEFAULT_UNIVERSAL_COMPACTION_L0_NUM_FILES_COMPACTION_TRIGGER); + self.options.set_level_zero_file_num_compaction_trigger( + max_level_zero_file_num.try_into().unwrap(), + ); + self.options.set_level_zero_slowdown_writes_trigger( + (max_level_zero_file_num * 12).try_into().unwrap(), + ); + self.options + .set_level_zero_stop_writes_trigger((max_level_zero_file_num * 16).try_into().unwrap()); + + // Increase sst file size to 128MiB. + self.options.set_target_file_size_base( + read_size_from_env(ENV_VAR_TARGET_FILE_SIZE_BASE_MB) + .unwrap_or(DEFAULT_TARGET_FILE_SIZE_BASE_MB) as u64 + * 1024 + * 1024, + ); + + // This should be a no-op for universal compaction but increasing it to be safe. + self.options + .set_max_bytes_for_level_base((write_buffer_size * max_level_zero_file_num) as u64); + + self + } + + // Overrides the block options with different block cache size and block size. + pub fn set_block_options( + mut self, + block_cache_size_mb: usize, + block_size_bytes: usize, + ) -> DBOptions { + self.options + .set_block_based_table_factory(&get_block_options( + block_cache_size_mb, + block_size_bytes, + )); + self + } + // Disables write stalling and stopping based on pending compaction bytes. pub fn disable_write_throttling(mut self) -> DBOptions { self.options.set_soft_pending_compaction_bytes_limit(0); @@ -2524,7 +2604,6 @@ pub fn default_db_options() -> DBOptions { opt.set_table_cache_num_shard_bits(10); // LSM compression settings - opt.set_min_level_to_compress(2); opt.set_compression_type(rocksdb::DBCompressionType::Lz4); opt.set_bottommost_compression_type(rocksdb::DBCompressionType::Zstd); opt.set_bottommost_zstd_max_train_bytes(1024 * 1024, true); @@ -2552,7 +2631,9 @@ pub fn default_db_options() -> DBOptions { opt.set_enable_pipelined_write(true); - opt.set_block_based_table_factory(&get_block_options(128)); + // Increase block size to 16KiB. + // https://github.com/EighteenZi/rocksdb_wiki/blob/master/Memory-usage-in-RocksDB.md#indexes-and-filter-blocks + opt.set_block_based_table_factory(&get_block_options(128, 16 << 10)); // Set memtable bloomfilter. opt.set_memtable_prefix_bloom_ratio(0.02); @@ -2563,15 +2644,14 @@ pub fn default_db_options() -> DBOptions { } } -fn get_block_options(block_cache_size_mb: usize) -> BlockBasedOptions { +fn get_block_options(block_cache_size_mb: usize, block_size_bytes: usize) -> BlockBasedOptions { // Set options mostly similar to those used in optimize_for_point_lookup(), // except non-default binary and hash index, to hopefully reduce lookup latencies // without causing any regression for scanning, with slightly more memory usages. // https://github.com/facebook/rocksdb/blob/11cb6af6e5009c51794641905ca40ce5beec7fee/options/options.cc#L611-L621 let mut block_options = BlockBasedOptions::default(); - // Increase block size to 16KiB. - // https://github.com/EighteenZi/rocksdb_wiki/blob/master/Memory-usage-in-RocksDB.md#indexes-and-filter-blocks - block_options.set_block_size(16 * 1024); + // Overrides block size. + block_options.set_block_size(block_size_bytes); // Configure a block cache. block_options.set_block_cache(&Cache::new_lru_cache(block_cache_size_mb << 20)); // Set a bloomfilter with 1% false positive rate. diff --git a/crates/typed-store/src/traits.rs b/crates/typed-store/src/traits.rs index d5ea0aab06e08..b2676f13ca590 100644 --- a/crates/typed-store/src/traits.rs +++ b/crates/typed-store/src/traits.rs @@ -8,7 +8,7 @@ use std::{borrow::Borrow, collections::BTreeMap, error::Error}; pub trait Map<'a, K, V> where - K: Serialize + DeserializeOwned + ?Sized, + K: Serialize + DeserializeOwned, V: Serialize + DeserializeOwned, { type Error: Error; @@ -151,7 +151,7 @@ where #[async_trait] pub trait AsyncMap<'a, K, V> where - K: Serialize + DeserializeOwned + ?Sized + std::marker::Sync, + K: Serialize + DeserializeOwned + std::marker::Sync, V: Serialize + DeserializeOwned + std::marker::Sync + std::marker::Send, { type Error: Error; diff --git a/dapps/multisig-toolkit/src/routes/signature-analyzer.tsx b/dapps/multisig-toolkit/src/routes/signature-analyzer.tsx index 756ee2afbc275..a0bb875de2067 100644 --- a/dapps/multisig-toolkit/src/routes/signature-analyzer.tsx +++ b/dapps/multisig-toolkit/src/routes/signature-analyzer.tsx @@ -3,7 +3,7 @@ import { parseSerializedSignature, PublicKey, SignatureScheme } from '@mysten/sui/cryptography'; import { parsePartialSignatures } from '@mysten/sui/multisig'; -import { toB64 } from '@mysten/sui/utils'; +import { toBase64 } from '@mysten/sui/utils'; import { publicKeyFromRawBytes } from '@mysten/sui/verify'; import { AlertCircle } from 'lucide-react'; import { useState } from 'react'; @@ -47,7 +47,7 @@ function Signature({ signature, index }: { signature: SignaturePubkeyPair; index { label: 'Signature Public Key', value: pubkey }, { label: 'Sui Format Public Key ( flag | pk )', value: pubkey_base64_sui_format }, { label: 'Sui Address', value: suiAddress }, - { label: 'Signature', value: toB64(signature.signature) }, + { label: 'Signature', value: toBase64(signature.signature) }, ]; return ( diff --git a/docker/deterministic-canary/Dockerfile b/docker/deterministic-canary/Dockerfile index 5ebdef630f182..c0400b8afa52d 100644 --- a/docker/deterministic-canary/Dockerfile +++ b/docker/deterministic-canary/Dockerfile @@ -1,7 +1,7 @@ ARG PROFILE=release # ARG BUILD_DATE # ARG GIT_REVISION -ARG RUST_VERSION=1.80.1 +ARG RUST_VERSION=1.81.0 FROM scratch AS base diff --git a/docker/sui-bridge-indexer/Dockerfile b/docker/sui-bridge-indexer/Dockerfile index 4f0fcde7b9c42..ce5551e79d0a1 100644 --- a/docker/sui-bridge-indexer/Dockerfile +++ b/docker/sui-bridge-indexer/Dockerfile @@ -2,7 +2,7 @@ # # Copy in all crates, Cargo.toml and Cargo.lock unmodified, # and build the application. -FROM rust:1.80.1-bullseye AS builder +FROM rust:1.81-bullseye AS builder ARG PROFILE=release ARG GIT_REVISION ENV GIT_REVISION=$GIT_REVISION diff --git a/docker/sui-graphql-rpc-staging/Dockerfile b/docker/sui-graphql-rpc-staging/Dockerfile new file mode 100644 index 0000000000000..a3f9bfb0fd571 --- /dev/null +++ b/docker/sui-graphql-rpc-staging/Dockerfile @@ -0,0 +1,33 @@ +# Build application +# +# Copy in all crates, Cargo.toml and Cargo.lock unmodified, +# and build the application. +FROM rust:1.81-bullseye AS builder +ARG PROFILE=release +ENV PROFILE=$PROFILE +ARG GIT_REVISION +ENV GIT_REVISION=$GIT_REVISION +WORKDIR "$WORKDIR/sui" +RUN apt-get update && apt-get install -y cmake clang libpq-dev + +COPY Cargo.toml Cargo.lock ./ +COPY consensus consensus +COPY crates crates +COPY sui-execution sui-execution +COPY narwhal narwhal +COPY external-crates external-crates + +RUN cargo build --profile ${PROFILE} --bin sui-graphql-rpc --features staging + +# Production Image +FROM debian:bullseye-slim AS runtime +WORKDIR "$WORKDIR/sui" +# Both bench and release profiles copy from release dir +RUN mkdir -p /opt/sui/bin +COPY --from=builder /sui/target/release/sui-graphql-rpc /opt/sui/bin +RUN apt-get update && apt-get install -y libpq5 ca-certificates libpq-dev postgresql + +ARG BUILD_DATE +ARG GIT_REVISION +LABEL build-date=$BUILD_DATE +LABEL git-revision=$GIT_REVISION diff --git a/docker/sui-graphql-rpc/Dockerfile b/docker/sui-graphql-rpc/Dockerfile index 049b8603e39c8..5f1d663d95427 100644 --- a/docker/sui-graphql-rpc/Dockerfile +++ b/docker/sui-graphql-rpc/Dockerfile @@ -2,7 +2,7 @@ # # Copy in all crates, Cargo.toml and Cargo.lock unmodified, # and build the application. -FROM rust:1.80.1-bullseye AS builder +FROM rust:1.81-bullseye AS builder ARG PROFILE=release ENV PROFILE=$PROFILE ARG GIT_REVISION diff --git a/docker/sui-indexer-tidb/Dockerfile b/docker/sui-indexer-tidb/Dockerfile index 6d11dc569bd60..54880095ba5b9 100644 --- a/docker/sui-indexer-tidb/Dockerfile +++ b/docker/sui-indexer-tidb/Dockerfile @@ -2,7 +2,7 @@ # # Copy in all crates, Cargo.toml and Cargo.lock unmodified, # and build the application. -FROM rust:1.80.1-bullseye AS builder +FROM rust:1.81-bullseye AS builder ARG PROFILE=release ARG GIT_REVISION ENV GIT_REVISION=$GIT_REVISION diff --git a/docker/sui-indexer/Dockerfile b/docker/sui-indexer/Dockerfile index 98635444f78c3..9d8248ce49e9b 100644 --- a/docker/sui-indexer/Dockerfile +++ b/docker/sui-indexer/Dockerfile @@ -2,7 +2,7 @@ # # Copy in all crates, Cargo.toml and Cargo.lock unmodified, # and build the application. -FROM rust:1.80.1-bullseye AS builder +FROM rust:1.81-bullseye AS builder ARG PROFILE=release ARG GIT_REVISION ENV GIT_REVISION=$GIT_REVISION diff --git a/docker/sui-network/docker-compose-antithesis.yaml b/docker/sui-network/docker-compose-antithesis.yaml index 1e67f2884507c..bbbe237f85b79 100644 --- a/docker/sui-network/docker-compose-antithesis.yaml +++ b/docker/sui-network/docker-compose-antithesis.yaml @@ -65,6 +65,7 @@ services: container_name: validator3 hostname: validator3 environment: + - DISABLE_WRITEBACK_CACHE=1 - RUST_BACKTRACE=1 - RUST_LOG=info,sui_core=debug,sui_network=debug,sui_node=debug,consensus=debug,jsonrpsee=error - RPC_WORKER_THREAD=12 @@ -92,6 +93,7 @@ services: container_name: validator4 hostname: validator4 environment: + - DISABLE_WRITEBACK_CACHE=1 - RUST_BACKTRACE=1 - RUST_LOG=info,sui_core=debug,sui_network=debug,sui_node=debug,consensus=debug,jsonrpsee=error - RPC_WORKER_THREAD=12 diff --git a/docker/sui-node/Dockerfile b/docker/sui-node/Dockerfile index 5d72f3d368f1f..6bebe24a6acb1 100644 --- a/docker/sui-node/Dockerfile +++ b/docker/sui-node/Dockerfile @@ -2,17 +2,12 @@ # # Copy in all crates, Cargo.toml and Cargo.lock unmodified, # and build the application. -FROM rust:1.80.1-bullseye AS builder +FROM rust:1.81-bullseye AS builder ARG PROFILE=release ARG GIT_REVISION ENV GIT_REVISION=$GIT_REVISION WORKDIR "$WORKDIR/sui" -RUN apt-get update && apt-get install -y cmake clang protobuf-compiler - -COPY root-config /root/ -RUN sed 's|/home/runner|/root|g' -i.bak /root/.ssh/config -ENV CARGO_NET_GIT_FETCH_WITH_CLI=true - +RUN apt-get update && apt-get install -y cmake clang COPY Cargo.toml Cargo.lock ./ COPY consensus consensus @@ -21,7 +16,7 @@ COPY sui-execution sui-execution COPY narwhal narwhal COPY external-crates external-crates -RUN --mount=type=ssh cargo build --profile ${PROFILE} --bin sui-node +RUN cargo build --profile ${PROFILE} --bin sui-node # Production Image FROM debian:bullseye-slim AS runtime diff --git a/docker/sui-proxy/Dockerfile b/docker/sui-proxy/Dockerfile new file mode 100644 index 0000000000000..0347618ff62f3 --- /dev/null +++ b/docker/sui-proxy/Dockerfile @@ -0,0 +1,20 @@ +FROM rust:1.81-bullseye as build + +ARG PROFILE=release +WORKDIR /work + +RUN apt-get update && apt-get install -y cmake clang + +COPY .git/ .git/ +COPY Cargo.toml Cargo.lock ./ +COPY consensus consensus +COPY crates crates +COPY sui-execution sui-execution +COPY narwhal narwhal +COPY external-crates external-crates + +RUN cargo build --profile ${PROFILE} --bin sui-proxy + +FROM gcr.io/distroless/cc-debian12 as deploy + +COPY --from=build --chmod=755 /work/target/release/sui-proxy /opt/sui/bin/sui-proxy diff --git a/docker/sui-services/Dockerfile b/docker/sui-services/Dockerfile index 2002fe6b9140b..b3cf32fb62d62 100644 --- a/docker/sui-services/Dockerfile +++ b/docker/sui-services/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.80.1-bullseye AS chef +FROM rust:1.81-bullseye AS chef WORKDIR sui ARG GIT_REVISION ENV GIT_REVISION=$GIT_REVISION @@ -23,7 +23,9 @@ RUN find /sui/target/release/ -maxdepth 1 -type f -executable -print | xargs cp # Production Image FROM debian:bullseye-slim AS runtime WORKDIR sui -COPY --from=builder /sui/bin/* /usr/local/bin +COPY --from=builder /sui/bin /tmp/sui-bin +RUN find /tmp/sui-bin -maxdepth 1 -type f -executable -print | xargs cp -t /usr/local/bin/ +RUN rm -rf /tmp/sui-bin RUN apt update && apt install -y libpq5 libpq-dev postgresql ARG BUILD_DATE diff --git a/docker/sui-source-service/Dockerfile b/docker/sui-source-service/Dockerfile index 0936bd0562256..07d8f2ce3003b 100644 --- a/docker/sui-source-service/Dockerfile +++ b/docker/sui-source-service/Dockerfile @@ -1,4 +1,4 @@ -FROM rust:1.80.1-bullseye AS chef +FROM rust:1.81-bullseye AS chef WORKDIR sui ARG GIT_REVISION ENV GIT_REVISION=$GIT_REVISION diff --git a/docker/sui-tools/Dockerfile b/docker/sui-tools/Dockerfile index f6470d12a4783..1d42ab249486f 100644 --- a/docker/sui-tools/Dockerfile +++ b/docker/sui-tools/Dockerfile @@ -2,7 +2,7 @@ # # Copy in all crates, Cargo.toml and Cargo.lock unmodified, # and build the application. -FROM rust:1.80.1-bullseye AS builder +FROM rust:1.81-bullseye AS builder ARG PROFILE=release ARG GIT_REVISION ENV GIT_REVISION=$GIT_REVISION diff --git a/docs/content/concepts/events.mdx b/docs/content/concepts/events.mdx deleted file mode 100644 index 72b0f07dd19eb..0000000000000 --- a/docs/content/concepts/events.mdx +++ /dev/null @@ -1,107 +0,0 @@ ---- -title: Events -description: Events are the main way to track actions on chain. ---- - -Events are the main way to track actions on chain. The following example includes comments to document the logic of the code. The example extends the donut example used elsewhere in the documentation to include events that are emitted when someone purchases a donut and when the `DonutShop` collects coins from that sale. You could then set up subscriptions to listen for these events, then trigger logic when those events fire. - -```move -/// Extended example of a shared object. Now with addition of events! -module examples::donuts_with_events { - use sui::transfer; - use sui::sui::SUI; - use sui::coin::{Self, Coin}; - use sui::object::{Self, ID, UID}; - use sui::balance::{Self, Balance}; - use sui::tx_context::{Self, TxContext}; - - // This is the only dependency you need for events. - use sui::event; - - /// For when Coin balance is too low. - const ENotEnough: u64 = 0; - - /// Capability that grants an owner the right to collect profits. - struct ShopOwnerCap has key { id: UID } - - /// A purchasable Donut. For simplicity's sake we ignore implementation. - struct Donut has key { id: UID } - - struct DonutShop has key { - id: UID, - price: u64, - balance: Balance - } - - // ====== Events ====== - - /// For when someone has purchased a donut. - struct DonutBought has copy, drop { - id: ID - } - - /// For when DonutShop owner has collected profits. - struct ProfitsCollected has copy, drop { - amount: u64 - } - - // ====== Functions ====== - - fun init(ctx: &mut TxContext) { - transfer::transfer(ShopOwnerCap { - id: object::new(ctx) - }, tx_context::sender(ctx)); - - transfer::share_object(DonutShop { - id: object::new(ctx), - price: 1000, - balance: balance::zero() - }) - } - - /// Buy a donut. - public fun buy_donut( - shop: &mut DonutShop, - payment: &mut Coin, - ctx: &mut TxContext, - ): Donut { - assert!(coin::value(payment) >= shop.price, ENotEnough); - - let coin_balance = coin::balance_mut(payment); - let paid = balance::split(coin_balance, shop.price); - let id = object::new(ctx); - - balance::join(&mut shop.balance, paid); - - // Emit the event using future object's ID. - event::emit(DonutBought { id: object::uid_to_inner(&id) }); - Donut { id } - } - - /// Consume donut and get nothing... - public fun eat_donut(d: Donut) { - let Donut { id } = d; - object::delete(id); - } - - /// Take coin from `DonutShop` and transfer it to tx sender. - /// Requires authorization with `ShopOwnerCap`. - public fun collect_profits( - _: &ShopOwnerCap, - shop: &mut DonutShop, - ctx: &mut TxContext, - ): Coin { - let amount = balance::value(&shop.balance); - let profits = coin::take(&mut shop.balance, amount, ctx); - - // simply create new type instance and emit it - event::emit(ProfitsCollected { amount }); - - profits - } -} -``` - -## Related links - -- [Using Events](/guides/developer/sui-101/using-events.mdx): A guide to leveraging Sui events in your smart contracts. \ No newline at end of file diff --git a/docs/content/concepts/object-model.mdx b/docs/content/concepts/object-model.mdx index 4b3b242a9e785..12a8eb0d41f51 100644 --- a/docs/content/concepts/object-model.mdx +++ b/docs/content/concepts/object-model.mdx @@ -52,7 +52,7 @@ The `ProtocolConfig` struct in the [`sui-protocol-config` crate](https://github.
-Toggle source code +`lib.rs` {@inject: crates/sui-protocol-config/src/lib.rs#struct=ProtocolConfig}
diff --git a/docs/content/concepts/research-papers.mdx b/docs/content/concepts/research-papers.mdx index 700889c2dbecf..caaba869ff69b 100644 --- a/docs/content/concepts/research-papers.mdx +++ b/docs/content/concepts/research-papers.mdx @@ -8,6 +8,59 @@ Some of the ideas of these papers are currently being integrated into Sui, other but could be integrated in the future. Start with the [Sui Smart Contract Platform](/doc/sui.pdf) white paper, which contains our latest design inspired by previous works below. +
+ +

Latest

+ +## Mysticeti: Reaching the Limits of Latency with Uncertified DAGs {#mysticeti} + +- **Link:** https://arxiv.org/pdf/2310.14821 +- **Publication:** Network and Distributed System Security Symposium (NDSS), 2025 +- **Relevance:** We introduce Mysticeti-C, the first DAG-based Byzantine consensus protocol to achieve the lower bounds of latency of 3 message rounds. Since Mysticeti-C is built over DAGs it also achieves high resource efficiency and censorship resistance. Mysticeti-C achieves this latency improvement by avoiding explicit certification of the DAG blocks and by proposing a novel commit rule such that every block can be committed without delays, resulting in optimal latency in the steady state and under crash failures. We further extend Mysticeti-C to Mysticeti-FPC, which incorporates a fast commit path that achieves even lower latency for transferring assets. Unlike prior fast commit path protocols, Mysticeti-FPC minimizes the number of signatures and messages by weaving the fast path transactions into the DAG. This frees up resources, which subsequently result in better performance. We prove the safety and liveness in a Byzantine context. We evaluate both Mysticeti protocols and compare them with state-of-the-art consensus and fast path protocols to demonstrate their low latency and resource efficiency, as well as their more graceful degradation under crash failures. Mysticeti-C is the first Byzantine consensus protocol to achieve WAN latency of 0.5s for consensus commit while simultaneously maintaining state-of-the-art throughput of over 200k TPS. Finally, we report on integrating Mysticeti-C as the consensus protocol into the Sui blockchain, resulting in over 4x latency reduction. + +## Sui Lutris: A Blockchain Combining Broadcast and Consensus + +- **Link:** https://arxiv.org/pdf/2310.18042 +- **Publication:** Conference on Computer and Communications Security (CCS), 2024 +- **Relevance:** Sui Lutris is the first smart-contract platform to sustainably achieve sub-second finality. It achieves this significant decrease by employing consensusless agreement not only for simple payments but for a large variety of transactions. Unlike prior work, Sui Lutris neither compromises expressiveness nor throughput and can run perpetually without restarts. Sui Lutris achieves this by safely integrating consensuless agreement with a high-throughput consensus protocol that is invoked out of the critical finality path but ensures that when a transaction is at risk of inconsistent concurrent accesses, its settlement is delayed until the total ordering is resolved. Building such a hybrid architecture is especially delicate during reconfiguration events, where the system needs to preserve the safety of the consensusless path without compromising the long-term liveness of potentially misconfigured clients. We thus develop a novel reconfiguration protocol, the first to provably show the safe and efficient reconfiguration of a consensusless blockchain. Sui Lutris is currently running in production and underpins the Sui smart-contract platform. Combined with the use of Objects instead of accounts it enables the safe execution of smart contracts that expose objects as a first-class resource. In our experiments Sui Lutris achieves latency lower than 0.5 seconds for throughput up to 5,000 certificates per second (150k ops/s with transaction blocks), compared to the state-of-the-art real-world consensus latencies of 3 seconds. Furthermore, it gracefully handles validators crash-recovery and does not suffer visible performance degradation during reconfiguration. + +## zkLogin: Privacy-Preserving Blockchain Authentication with Existing Credentials + +- **Link:** https://arxiv.org/pdf/2401.11735 +- **Publication:** Not published +- **Relevance:** For many users, a private key based wallet serves as the primary entry point to blockchains. Commonly recommended wallet authentication methods, such as mnemonics or hardware wallets, can be cumbersome. This difficulty in user onboarding has significantly hindered the adoption of blockchain-based applications. +We develop zkLogin, a novel technique that leverages identity tokens issued by popular platforms (any OpenID Connect enabled platform e.g. Google, Facebook, etc.) to authenticate transactions. At the heart of zkLogin lies a signature scheme allowing the signer to sign using their existing OpenID accounts and nothing else. This improves the user experience significantly as users do not need to remember a new secret and can reuse their existing accounts. +zkLogin provides strong security and privacy guarantees. By design, zkLogin builds on top of the underlying platform's authentication mechanisms, and derives its security from there. Unlike prior related works however, zkLogin avoids the use of additional trusted parties (e.g., trusted hardware or oracles) for its security guarantees. zkLogin leverages zero-knowledge proofs (ZKP) to ensure that the link between a user's off-chain and on-chain identities is hidden, even from the platform itself. +We have implemented and deployed zkLogin on the Sui blockchain as an alternative to traditional digital signature-based addresses. Due to the ease of web3 on-boarding just with social login, without requiring mnemonics, many hundreds of thousands zkLogin accounts have already been generated in various industries such as gaming, DeFi, direct payments, NFT collections, ride sharing, sports racing and many more. +
+ +## Be Aware of Your Leaders {#awareness} + +- **Link:** https://arxiv.org/abs/2110.00960 +- **Publication:** Financial Cryptography and Data Security (FC), 2022 +- **Relevance:** Provides a performant leader election algorithm for partially-synchronous consensus protocol (such as Bullshark). Sui may want to use it + alongside Bullshark to support shared objects. +- **Summary:** Advances in blockchains have influenced the State-Machine-Replication (SMR) world and many state-of-the-art blockchain-SMR solutions are + based on two pillars: Chaining and Leader-rotation. A predetermined round-robin mechanism used for Leader-rotation, however, has an undesirable behavior: + crashed parties become designated leaders infinitely often, slowing down overall system performance. In this paper, we provide a new Leader-Aware SMR + framework that, among other desirable properties, formalizes a Leader-utilization requirement that bounds the number of rounds whose leaders are faulty + in crash-only executions. We introduce Carousel, a novel, reputation-based Leader-rotation solution to achieve Leader-Aware SMR. The challenge in adaptive + Leader-rotation is that it cannot rely on consensus to determine a leader, since consensus itself needs a leader. Carousel uses the available on-chain + information to determine a leader locally and achieves Liveness despite this difficulty. A HotStuff implementation fitted with Carousel demonstrates + drastic performance improvements: it increases throughput over 2x in faultless settings and provides a 20x throughput increase and 5x latency reduction + in the presence of faults. + +## Bullshark: DAG BFT Protocols Made Practical {#bullshark} + +- **Link:** https://arxiv.org/abs/2201.05677 +- **Publication:** Not published yet (under submission) +- **Relevance:** Provides a partially-synchronous consensus protocol running over Narwhal. Sui may want to use it instead of Tusk. +- **Summary:** We present Bullshark, the first directed acyclic graph (DAG) based Byzantine Fault Tolerant (BFT) protocol that is optimized for partial synchrony. + Bullshark inherits all the desired properties of its predecessor (DAG-Rider) such as optimal amortized complexity, asynchronous liveness, zero-overhead, + and post-quantum safety; but at the same time Bullshark provides a practical low-latency fast-path that exploits synchronous periods. In addition, we introduce + a standalone partially synchronous version of Bullshark and evaluate it against the state of the art. The resulting protocol is embarrassingly simple 20 LOC + on top of a DAG-based mempool implementation) and highly efficient, achieving for example, 125k transactions per second and 2 seconds latency with 50 nodes. + ## FastPay: High-Performance Byzantine Fault Tolerant Settlement {#fastpay} - **Link:** https://arxiv.org/abs/2003.11506 @@ -20,6 +73,23 @@ our latest design inspired by previous works below. (consensus). The resulting system has low-latency for both confirmation and payment finality. Remarkably, each validator can be sharded across many machines to allow unbounded horizontal scalability. +## HammerHead: Score-based Dynamic Leader Selection + +- **Link:** https://arxiv.org/pdf/2309.12713 +- **Publication:** IEEE International Conference on Distributed Computing Systems (ICDCS), 2024 +- **Relevance:** The need for high throughput and censorship resistance in +blockchain technology has led to research on DAG-based consensus. The +Sui blockchain protocol uses a variant of the Bullshark consensus +algorithm due to its lower latency, but this leader-based protocol causes +performance issues when candidate leaders crash. In this paper, we explore the ideas pioneered by Carousel on providing Leader-Utilization +and present HammerHead. Unlike Carousel, which is built with a chained +and pipelined consensus protocol in mind, HammerHead does not need +to worry about chain quality as it is directly provided by the DAG, but +needs to make sure that even though validators might commit blocks +in different views the safety and liveness is preserved. Our implementation of HammerHead shows a slight performance increase in a faultless +setting, and a drastic 2x latency reduction and up to 40% throughput +increase when suffering faults (100 validators, 33 faults). + ## Narwhal and Tusk: A DAG-based Mempool and Efficient BFT Consensus {#narwhal-and-tusk} - **Link:** https://arxiv.org/abs/2105.11827 @@ -37,48 +107,17 @@ our latest design inspired by previous works below. without any latency increase. Tusk achieves 160,000 tx/sec with about 3 seconds latency. Under faults, both protocols maintain high throughput, but Narwhal-HotStuff suffers from increased latency. -## Zef: Low-latency, Scalable, Private Payments {#zef} - -- **Link:** https://arxiv.org/abs/2201.05671 -- **Publication:** Not published yet (under submission) -- **Relevance:** Extends the FastPay design to support objects (rather than accounts), what Sui actually uses. An additional contribution of this paper is - to add strong privacy to FastPay transactions (but Sui does not plan to do this). -- **Summary:** We introduce Zef, the first Byzantine-Fault Tolerant (BFT) protocol to support payments in anonymous digital coins at arbitrary scale. Zef - follows the communication and security model of FastPay: both protocols are asynchronous, low-latency, linearly-scalable, and powered by partially-trusted - sharded validators. Zef further introduces opaque coins represented as off-chain certificates that are bound to user accounts. In order to hide the face - values of coins when a payment operation consumes or creates them, Zef uses random commitments and NIZK proofs. Created coins are made unlinkable using the - blind and randomizable threshold anonymous credentials of [Coconut](https://arxiv.org/pdf/1802.07344.pdf). To control storage costs associated with coin - replay prevention, Zef accounts are designed so that data can be safely removed once an account is deactivated. Besides the specifications and a detailed - analysis of the protocol, we are making available an open source implementation of Zef in Rust. Our extensive benchmarks on AWS confirm textbook linear - scalability and demonstrate a confirmation time under one second at nominal capacity. Compared to existing anonymous payment systems based on a blockchain, - this represents a latency speedup of three orders of magnitude, with no theoretical limit on throughput. - -## Bullshark: DAG BFT Protocols Made Practical {#bullshark} - -- **Link:** https://arxiv.org/abs/2201.05677 -- **Publication:** Not published yet (under submission) -- **Relevance:** Provides a partially-synchronous consensus protocol running over Narwhal. Sui may want to use it instead of Tusk. -- **Summary:** We present Bullshark, the first directed acyclic graph (DAG) based Byzantine Fault Tolerant (BFT) protocol that is optimized for partial synchrony. - Bullshark inherits all the desired properties of its predecessor (DAG-Rider) such as optimal amortized complexity, asynchronous liveness, zero-overhead, - and post-quantum safety; but at the same time Bullshark provides a practical low-latency fast-path that exploits synchronous periods. In addition, we introduce - a standalone partially synchronous version of Bullshark and evaluate it against the state of the art. The resulting protocol is embarrassingly simple 20 LOC - on top of a DAG-based mempool implementation) and highly efficient, achieving for example, 125k transactions per second and 2 seconds latency with 50 nodes. - -## Be Aware of Your Leaders {#awareness} +## SybilQuorum: Open Distributed Ledgers Through Trust Networks {#sybilquorum} -- **Link:** https://arxiv.org/abs/2110.00960 -- **Publication:** Financial Cryptography and Data Security (FC), 2022 -- **Relevance:** Provides a performant leader election algorithm for partially-synchronous consensus protocol (such as Bullshark). Sui may want to use it - alongside Bullshark to support shared objects. -- **Summary:** Advances in blockchains have influenced the State-Machine-Replication (SMR) world and many state-of-the-art blockchain-SMR solutions are - based on two pillars: Chaining and Leader-rotation. A predetermined round-robin mechanism used for Leader-rotation, however, has an undesirable behavior: - crashed parties become designated leaders infinitely often, slowing down overall system performance. In this paper, we provide a new Leader-Aware SMR - framework that, among other desirable properties, formalizes a Leader-utilization requirement that bounds the number of rounds whose leaders are faulty - in crash-only executions. We introduce Carousel, a novel, reputation-based Leader-rotation solution to achieve Leader-Aware SMR. The challenge in adaptive - Leader-rotation is that it cannot rely on consensus to determine a leader, since consensus itself needs a leader. Carousel uses the available on-chain - information to determine a leader locally and achieves Liveness despite this difficulty. A HotStuff implementation fitted with Carousel demonstrates - drastic performance improvements: it increases throughput over 2x in faultless settings and provides a 20x throughput increase and 5x latency reduction - in the presence of faults. +- **Link:** https://arxiv.org/abs/1906.12237 +- **Publication:** Not published +- **Relevance:** Less related to Sui than the other papers, and the paper is in its early stages. It presents an algorithm to strengthen proof-of-Stake systems (like Sui). The paper is, however, theoretical and not on our roadmap. +- **Summary:** The Sybil attack plagues all peer-to-peer systems, and modern open distributed ledgers employ a number of tactics to prevent it from proof + of work, or other resources such as space, stake or memory, to traditional admission control in permissioned settings. With SybilQuorum we propose an + alternative approach to securing an open distributed ledger against Sybil attacks, and ensuring consensus amongst honest participants, leveraging social + network based Sybil defenses. We show how nodes expressing their trust relationships through the ledger can bootstrap and operate a value system, and + general transaction system, and how Sybil attacks are thwarted. We empirically evaluate our system as a secure Federated Byzantine Agreement System, and + extend the theory of those systems to do so. ## Twins: BFT Systems Made Robust {#twins} @@ -98,14 +137,18 @@ our latest design inspired by previous works below. several known attacks against other BFT protocols were materialized as Twins scenarios. In all cases, the target protocols break within fewer than a dozen protocol rounds. Hence it is realistic for the Twins approach to expose the problems. -## SybilQuorum: Open Distributed Ledgers Through Trust Networks {#sybilquorum} +## Zef: Low-latency, Scalable, Private Payments {#zef} -- **Link:** https://arxiv.org/abs/1906.12237 -- **Publication:** Not published -- **Relevance:** Less related to Sui than the other papers, and the paper is in its early stages. It presents an algorithm to strengthen proof-of-Stake systems (like Sui). The paper is, however, theoretical and not on our roadmap. -- **Summary:** The Sybil attack plagues all peer-to-peer systems, and modern open distributed ledgers employ a number of tactics to prevent it from proof - of work, or other resources such as space, stake or memory, to traditional admission control in permissioned settings. With SybilQuorum we propose an - alternative approach to securing an open distributed ledger against Sybil attacks, and ensuring consensus amongst honest participants, leveraging social - network based Sybil defenses. We show how nodes expressing their trust relationships through the ledger can bootstrap and operate a value system, and - general transaction system, and how Sybil attacks are thwarted. We empirically evaluate our system as a secure Federated Byzantine Agreement System, and - extend the theory of those systems to do so. +- **Link:** https://arxiv.org/abs/2201.05671 +- **Publication:** Not published yet (under submission) +- **Relevance:** Extends the FastPay design to support objects (rather than accounts), what Sui actually uses. An additional contribution of this paper is + to add strong privacy to FastPay transactions (but Sui does not plan to do this). +- **Summary:** We introduce Zef, the first Byzantine-Fault Tolerant (BFT) protocol to support payments in anonymous digital coins at arbitrary scale. Zef + follows the communication and security model of FastPay: both protocols are asynchronous, low-latency, linearly-scalable, and powered by partially-trusted + sharded validators. Zef further introduces opaque coins represented as off-chain certificates that are bound to user accounts. In order to hide the face + values of coins when a payment operation consumes or creates them, Zef uses random commitments and NIZK proofs. Created coins are made unlinkable using the + blind and randomizable threshold anonymous credentials of [Coconut](https://arxiv.org/pdf/1802.07344.pdf). To control storage costs associated with coin + replay prevention, Zef accounts are designed so that data can be safely removed once an account is deactivated. Besides the specifications and a detailed + analysis of the protocol, we are making available an open source implementation of Zef in Rust. Our extensive benchmarks on AWS confirm textbook linear + scalability and demonstrate a confirmation time under one second at nominal capacity. Compared to existing anonymous payment systems based on a blockchain, + this represents a latency speedup of three orders of magnitude, with no theoretical limit on throughput. diff --git a/docs/content/concepts/sui-move-concepts/conventions.mdx b/docs/content/concepts/sui-move-concepts/conventions.mdx index 0541a416e7782..32b9a9d5831b1 100644 --- a/docs/content/concepts/sui-move-concepts/conventions.mdx +++ b/docs/content/concepts/sui-move-concepts/conventions.mdx @@ -11,27 +11,27 @@ Use titles in code comments to create sections for your Move code files. Structu ```move module conventions::comments { - // === Imports === + // === Imports === - // === Errors === + // === Errors === - // === Constants === + // === Constants === - // === Structs === + // === Structs === - // === Method Aliases === + // === Method Aliases === - // === Public-Mutative Functions === + // === Public-Mutative Functions === - // === Public-View Functions === + // === Public-View Functions === - // === Admin Functions === + // === Admin Functions === - // === Public-Package Functions === + // === Public-Package Functions === - // === Private Functions === + // === Private Functions === - // === Test Functions === + // === Test Functions === } ``` @@ -58,11 +58,11 @@ Do not use 'potato' in the name of structs. The lack of abilities define it as a ```move module conventions::request { - // ✅ Right - struct Request {} + // ✅ Right + struct Request {} - // ❌ Wrong - struct RequestPotato {} + // ❌ Wrong + struct RequestPotato {} } ``` @@ -73,33 +73,33 @@ Be mindful of the dot syntax when naming functions. Avoid using the object name ```move module conventions::profile { - struct Profile { - age: u64 - } + struct Profile { + age: u64 + } - // ✅ Right - public fun age(self: &Profile): u64 { - self.age - } + // ✅ Right + public fun age(self: &Profile): u64 { + self.age + } - // ❌ Wrong - public fun profile_age(self: &Profile): u64 { - self.age - } + // ❌ Wrong + public fun profile_age(self: &Profile): u64 { + self.age + } } module conventions::defi { - use conventions::profile::{Self, Profile}; + use conventions::profile::{Self, Profile}; - public fun get_tokens(profile: &Profile) { + public fun get_tokens(profile: &Profile) { - // ✅ Right - let name = profile.age(); + // ✅ Right + let name = profile.age(); - // ❌ Wrong - let name2 = profile.profile_age(); - } + // ❌ Wrong + let name2 = profile.profile_age(); + } } ``` @@ -110,15 +110,15 @@ Name the functions that create data structures as `empty`. ```move module conventions::collection { - struct Collection has copy, drop, store { - bits: vector - } + struct Collection has copy, drop, store { + bits: vector + } - public fun empty(): Collection { - Collection { - bits: vector[] + public fun empty(): Collection { + Collection { + bits: vector[] + } } - } } ``` @@ -129,18 +129,18 @@ Name the functions that create objects as `new`. ```move module conventions::object { - use sui::object::{Self, UID}; - use sui::tx_context::TxContext; + use sui::object::{Self, UID}; + use sui::tx_context::TxContext; - struct Object has key, store { - id: UID - } + struct Object has key, store { + id: UID + } - public fun new(ctx:&mut TxContext): Object { - Object { - id: object::new(ctx) + public fun new(ctx:&mut TxContext): Object { + Object { + id: object::new(ctx) + } } - } } ``` @@ -151,23 +151,23 @@ Library modules that share objects should provide two functions: one to create t ```move module conventions::profile { - use sui::object::{Self, UID}; - use sui::tx_context::TxContext; - use sui::transfer::share_object; + use sui::object::{Self, UID}; + use sui::tx_context::TxContext; + use sui::transfer::share_object; - struct Profile has key { - id: UID - } + struct Profile has key { + id: UID + } - public fun new(ctx:&mut TxContext): Profile { - Profile { - id: object::new(ctx) + public fun new(ctx:&mut TxContext): Profile { + Profile { + id: object::new(ctx) + } } - } - public fun share(profile: Profile) { - share_object(profile); - } + public fun share(profile: Profile) { + share_object(profile); + } } ``` @@ -178,25 +178,25 @@ Name the functions that return a reference as `_mut` or ` has key, store { - id: UID, - value: Value - } - - // Value has drop - public fun drop(self: Wallet) { - let Wallet { id, value: _ } = self; - object::delete(id); - } - - // Value doesn't have drop - // Throws if the `wallet.value` is not empty. - public fun destroy_empty(self: Wallet>) { - let Wallet { id, value } = self; - object::delete(id); - balance::destroy_zero(value); - } + use sui::object::{Self, UID}; + use sui::balance::{Self, Balance}; + use sui::sui::SUI; + + struct Wallet has key, store { + id: UID, + value: Value + } + + // Value has drop + public fun drop(self: Wallet) { + let Wallet { id, value: _ } = self; + object::delete(id); + } + + // Value doesn't have drop + // Throws if the `wallet.value` is not empty. + public fun destroy_empty(self: Wallet>) { + let Wallet { id, value } = self; + object::delete(id); + balance::destroy_zero(value); + } } ``` @@ -300,38 +300,38 @@ Keep your functions pure to maintain composability. Do not use `transfer::transf ```move module conventions::amm { - use sui::transfer; - use sui::coin::Coin; - use sui::object::UID; - use sui::tx_context::{Self, TxContext}; - - struct Pool has key { - id: UID - } - - // ✅ Right - // Return the excess coins even if they have zero value. - public fun add_liquidity(pool: &mut Pool, coin_x: Coin, coin_y: Coin): (Coin, Coin, Coin) { - // Implementation omitted. - abort(0) - } - - // ✅ Right - public fun add_liquidity_and_transfer(pool: &mut Pool, coin_x: Coin, coin_y: Coin, recipient: address) { - let (lp_coin, coin_x, coin_y) = add_liquidity(pool, coin_x, coin_y); - transfer::public_transfer(lp_coin, recipient); - transfer::public_transfer(coin_x, recipient); - transfer::public_transfer(coin_y, recipient); - } - - // ❌ Wrong - public fun impure_add_liquidity(pool: &mut Pool, coin_x: Coin, coin_y: Coin, ctx: &mut TxContext): Coin { - let (lp_coin, coin_x, coin_y) = add_liquidity(pool, coin_x, coin_y); - transfer::public_transfer(coin_x, tx_context::sender(ctx)); - transfer::public_transfer(coin_y, tx_context::sender(ctx)); - - lp_coin - } + use sui::transfer; + use sui::coin::Coin; + use sui::object::UID; + use sui::tx_context::{Self, TxContext}; + + struct Pool has key { + id: UID + } + + // ✅ Right + // Return the excess coins even if they have zero value. + public fun add_liquidity(pool: &mut Pool, coin_x: Coin, coin_y: Coin): (Coin, Coin, Coin) { + // Implementation omitted. + abort(0) + } + + // ✅ Right + public fun add_liquidity_and_transfer(pool: &mut Pool, coin_x: Coin, coin_y: Coin, recipient: address) { + let (lp_coin, coin_x, coin_y) = add_liquidity(pool, coin_x, coin_y); + transfer::public_transfer(lp_coin, recipient); + transfer::public_transfer(coin_x, recipient); + transfer::public_transfer(coin_y, recipient); + } + + // ❌ Wrong + public fun impure_add_liquidity(pool: &mut Pool, coin_x: Coin, coin_y: Coin, ctx: &mut TxContext): Coin { + let (lp_coin, coin_x, coin_y) = add_liquidity(pool, coin_x, coin_y); + transfer::public_transfer(coin_x, tx_context::sender(ctx)); + transfer::public_transfer(coin_y, tx_context::sender(ctx)); + + lp_coin + } } ``` @@ -342,24 +342,24 @@ Pass the `Coin` object by value with the right amount directly because it's bett ```move module conventions::amm { - use sui::coin::Coin; - use sui::object::UID; + use sui::coin::Coin; + use sui::object::UID; - struct Pool has key { - id: UID - } + struct Pool has key { + id: UID + } - // ✅ Right - public fun swap(coin_in: Coin): Coin { - // Implementation omitted. - abort(0) - } + // ✅ Right + public fun swap(coin_in: Coin): Coin { + // Implementation omitted. + abort(0) + } - // ❌ Wrong - public fun exchange(coin_in: &mut Coin, value: u64): Coin { - // Implementation omitted. - abort(0) - } + // ❌ Wrong + public fun exchange(coin_in: &mut Coin, value: u64): Coin { + // Implementation omitted. + abort(0) + } } ``` @@ -370,44 +370,44 @@ To maintain composability, use capabilities instead of addresses for access cont ```move module conventions::access_control { - use sui::sui::SUI; - use sui::object::UID; - use sui::balance::Balance; - use sui::coin::{Self, Coin}; - use sui::table::{Self, Table}; - use sui::tx_context::{Self, TxContext}; - - struct Account has key, store { - id: UID, - balance: u64 - } - - struct State has key { - id: UID, - accounts: Table, - balance: Balance - } - - // ✅ Right - // With this function, another protocol can hold the `Account` on behalf of a user. - public fun withdraw(state: &mut State, account: &mut Account, ctx: &mut TxContext): Coin { - let authorized_balance = account.balance; - - account.balance = 0; - - coin::take(&mut state.balance, authorized_balance, ctx) - } - - // ❌ Wrong - // This is less composable. - public fun wrong_withdraw(state: &mut State, ctx: &mut TxContext): Coin { - let sender = tx_context::sender(ctx); - - let authorized_balance = table::borrow_mut(&mut state.accounts, sender); - let value = *authorized_balance; - *authorized_balance = 0; - coin::take(&mut state.balance, value, ctx) - } + use sui::sui::SUI; + use sui::object::UID; + use sui::balance::Balance; + use sui::coin::{Self, Coin}; + use sui::table::{Self, Table}; + use sui::tx_context::{Self, TxContext}; + + struct Account has key, store { + id: UID, + balance: u64 + } + + struct State has key { + id: UID, + accounts: Table, + balance: Balance + } + + // ✅ Right + // With this function, another protocol can hold the `Account` on behalf of a user. + public fun withdraw(state: &mut State, account: &mut Account, ctx: &mut TxContext): Coin { + let authorized_balance = account.balance; + + account.balance = 0; + + coin::take(&mut state.balance, authorized_balance, ctx) + } + + // ❌ Wrong + // This is less composable. + public fun wrong_withdraw(state: &mut State, ctx: &mut TxContext): Coin { + let sender = tx_context::sender(ctx); + + let authorized_balance = table::borrow_mut(&mut state.accounts, sender); + let value = *authorized_balance; + *authorized_balance = 0; + coin::take(&mut state.balance, value, ctx) + } } ``` @@ -418,42 +418,42 @@ If your dApp data has a one to one relationship, it's best to use owned objects. ```move module conventions::vesting_wallet { - use sui::sui::SUI; - use sui::coin::Coin; - use sui::object::UID; - use sui::table::Table; - use sui::balance::Balance; - use sui::tx_context::TxContext; - - struct OwnedWallet has key { - id: UID, - balance: Balance - } - - struct SharedWallet has key { - id: UID, - balance: Balance, - accounts: Table - } - - /* - * A vesting wallet releases a certain amount of coin over a period of time. - * If the entire balance belongs to one user and the wallet has no additional functionalities, it is best to store it in an owned object. - */ - public fun new(deposit: Coin, ctx: &mut TxContext): OwnedWallet { - // Implementation omitted. - abort(0) - } - - /* - * If you wish to add extra functionality to a vesting wallet, it is best to share the object. - * For example, if you wish the issuer of the wallet to be able to cancel the contract in the future. - */ - public fun new_shared(deposit: Coin, ctx: &mut TxContext) { - // Implementation omitted. - // It shares the `SharedWallet`. - abort(0) - } + use sui::sui::SUI; + use sui::coin::Coin; + use sui::object::UID; + use sui::table::Table; + use sui::balance::Balance; + use sui::tx_context::TxContext; + + struct OwnedWallet has key { + id: UID, + balance: Balance + } + + struct SharedWallet has key { + id: UID, + balance: Balance, + accounts: Table + } + + /* + * A vesting wallet releases a certain amount of coin over a period of time. + * If the entire balance belongs to one user and the wallet has no additional functionalities, it is best to store it in an owned object. + */ + public fun new(deposit: Coin, ctx: &mut TxContext): OwnedWallet { + // Implementation omitted. + abort(0) + } + + /* + * If you wish to add extra functionality to a vesting wallet, it is best to share the object. + * For example, if you wish the issuer of the wallet to be able to cancel the contract in the future. + */ + public fun new_shared(deposit: Coin, ctx: &mut TxContext) { + // Implementation omitted. + // It shares the `SharedWallet`. + abort(0) + } } ``` @@ -464,31 +464,31 @@ In admin-gated functions, the first parameter should be the capability. It helps ```move module conventions::social_network { - use std::string::String; + use std::string::String; - use sui::object::UID; + use sui::object::UID; - struct Account has key { - id: UID, - name: String - } + struct Account has key { + id: UID, + name: String + } - struct Admin has key { - id: UID, - } + struct Admin has key { + id: UID, + } - // ✅ Right - // cap.update(&mut account, b"jose"); - public fun update(_: &Admin, account: &mut Account, new_name: String) { - // Implementation omitted. - abort(0) - } + // ✅ Right + // cap.update(&mut account, b"jose"); + public fun update(_: &Admin, account: &mut Account, new_name: String) { + // Implementation omitted. + abort(0) + } - // ❌ Wrong - // account.update(&cap, b"jose"); - public fun set(account: &mut Account, _: &Admin, new_name: String) { - // Implementation omitted. - abort(0) - } + // ❌ Wrong + // account.update(&cap, b"jose"); + public fun set(account: &mut Account, _: &Admin, new_name: String) { + // Implementation omitted. + abort(0) + } } ``` diff --git a/docs/content/concepts/sui-move-concepts/packages/custom-policies.mdx b/docs/content/concepts/sui-move-concepts/packages/custom-policies.mdx index dd646ace1b1f7..fd027049982dd 100644 --- a/docs/content/concepts/sui-move-concepts/packages/custom-policies.mdx +++ b/docs/content/concepts/sui-move-concepts/packages/custom-policies.mdx @@ -338,7 +338,7 @@ sui client publish --gas-budget 100000000
- Toggle output + Console output A successful publish returns the following: @@ -436,7 +436,7 @@ sui client call --gas-budget 10000000 \
- Toggle output + Console output A successful call returns the following: @@ -523,9 +523,8 @@ The instruction that follows publishes this example package and then upgrades it Create a new directory to store a Node.js project. You can use the `npm init` function to create the `package.json`, or manually create the file. Depending on your approach to creating `package.json`, populate or add the following JSON to it: -```JSON +```json { "type": "module" } - ``` Open a terminal or console to the root of your Node.js project. Run the following command to add the Sui TypeScript SDK as a dependency: @@ -554,7 +553,7 @@ import { readFileSync } from 'fs'; import { homedir } from 'os'; import path from 'path'; import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; -import { fromB64 } from '@mysten/sui/utils'; +import { fromBase64 } from '@mysten/sui/utils'; const sender = execSync(`${SUI} client active-address`, { encoding: 'utf8' }).trim(); const signer = (() => { @@ -563,7 +562,7 @@ const signer = (() => { ); for (const priv of keystore) { - const raw = fromB64(priv); + const raw = fromBase64(priv); if (raw[0] !== 0) { continue; } @@ -637,11 +636,9 @@ console.log(result);
- Toggle complete script + `publish.js` -The complete `publish.js` script follows: - ```js import { execSync } from 'child_process'; import { readFileSync } from 'fs'; @@ -651,7 +648,7 @@ import { fileURLToPath } from 'url'; import { getFullnodeUrl, SuiClient } from '@mysten/sui/client'; import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; import { Transaction } from '@mysten/sui/transactions'; -import { fromB64 } from '@mysten/sui/utils'; +import { fromBase64 } from '@mysten/sui/utils'; const SUI = 'sui'; const POLICY_PACKAGE_ID = ''; @@ -662,7 +659,7 @@ const signer = (() => { ); for (const priv of keystore) { - const raw = fromB64(priv); + const raw = fromBase64(priv); if (raw[0] !== 0) { continue; } @@ -720,7 +717,7 @@ node publish.js
- Toggle output + Console output If the script is successful, the console prints the following response: @@ -803,7 +800,7 @@ sui client call --gas-budget 10000000 \
- Toggle output + Console output A successful call responds with the following: @@ -891,7 +888,7 @@ import { fileURLToPath } from 'url'; import { getFullnodeUrl, SuiClient } from '@mysten/sui/client'; import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; import { Transaction, UpgradePolicy } from '@mysten/sui/transactions'; -import { fromB64 } from '@mysten/sui/utils'; +import { fromBase64 } from '@mysten/sui/utils'; const SUI = 'sui'; const POLICY_PACKAGE_ID = ''; @@ -904,7 +901,7 @@ const signer = (() => { ); for (const priv of keystore) { - const raw = fromB64(priv); + const raw = fromBase64(priv); if (raw[0] !== 0) { continue; } @@ -967,7 +964,7 @@ node upgrade.js
- Toggle output + Console output If the script is successful (and today is Tuesday), your console displays the following response: @@ -1046,7 +1043,7 @@ sui client call --gas-budget 10000000 \
- Toggle output + Console output If successful, the console prints the following response: @@ -1126,8 +1123,8 @@ If you attempt the first upgrade before Tuesday or you change the constant again ```bash ... status: { - status: 'failure', - error: 'MoveAbort(MoveLocation { module: ModuleId { address: , name: Identifier("day_of_week") }, function: 1, instruction: 11, function_name: Some("authorize_upgrade") }, 2) in command 0' - }, + status: 'failure', + error: 'MoveAbort(MoveLocation { module: ModuleId { address: , name: Identifier("day_of_week") }, function: 1, instruction: 11, function_name: Some("authorize_upgrade") }, 2) in command 0' +}, ... ``` diff --git a/docs/content/concepts/transactions/prog-txn-blocks.mdx b/docs/content/concepts/transactions/prog-txn-blocks.mdx index ad04053d42bf3..071ee8703a4b7 100644 --- a/docs/content/concepts/transactions/prog-txn-blocks.mdx +++ b/docs/content/concepts/transactions/prog-txn-blocks.mdx @@ -320,7 +320,7 @@ Results: [ Now the command, `MoveCall("some_package", "some_marketplace", "buy_two", [], [Input(1), NestedResult(0, 0)])`. Call the function `some_package::some_marketplace::buy_two` with the arguments `Input(1)` and `NestedResult(0, 0)`. To determine how they are used, you need to look at the function's signature. For this example, assume the signature is -```rust +```move entry fun buy_two( marketplace: &mut Marketplace, coin: Coin, @@ -372,7 +372,7 @@ Results: [ Make another Move call, this one to `sui::tx_context::sender` with the signature -```rust +```move public fun sender(ctx: &TxContext): address ``` diff --git a/docs/content/guides.mdx b/docs/content/guides.mdx index 717410cc223e5..445cecf8a911f 100644 --- a/docs/content/guides.mdx +++ b/docs/content/guides.mdx @@ -36,23 +36,32 @@ Learn the basics of Sui and how they might differ from other blockchains. Transactions on Sui are more powerful than other blockchains. Learn why and how to use them. - - + Monitor the Sui network and programmatically react to on-chain events. +## Coins, tokens, and NFTs + +Learn how to mint various tokens on the Sui blockchain. + + + + + + ## Validating and operating nodes on Sui Processes and guides for validators and node operators on the Sui network. - - + Learn how to operate a Full node on Sui. Optimize your Full node configuration for efficient node operation. + + diff --git a/docs/content/guides/developer.mdx b/docs/content/guides/developer.mdx index 98bfd3ca82af5..a21919b55d268 100644 --- a/docs/content/guides/developer.mdx +++ b/docs/content/guides/developer.mdx @@ -23,6 +23,15 @@ The Sui 101 section introduces the basics of Sui that help you create smart cont Go to [Sui 101](./developer/sui-101.mdx). +## Coins, tokens, and NFTs + +Minting coins, tokens, and NFTs on Sui provides the opportunity to build communities on the Sui network around these objects or to promote and support off-chain businesses. Refer to the appropriate guide to get started. + +Go to +- [Coins and Tokens](./developer/coin.mdx) +- [NFTs](./developer/nft.mdx) +- [Stablecoins](./developer/stablecoins.mdx) + ## Cryptography The Cryptography section demonstrates how to secure your smart contracts with cryptography to ensure authentication for access to sensitive data. diff --git a/docs/content/guides/developer/advanced/custom-indexer.mdx b/docs/content/guides/developer/advanced/custom-indexer.mdx index 7eb800f4108d2..5db814da1e1cd 100644 --- a/docs/content/guides/developer/advanced/custom-indexer.mdx +++ b/docs/content/guides/developer/advanced/custom-indexer.mdx @@ -14,7 +14,7 @@ To use the framework, implement a basic interface: ```rust #[async_trait] trait Worker: Send + Sync { - async fn process_checkpoint(&self, checkpoint: CheckpointData) -> Result<()>; + async fn process_checkpoint(&self, checkpoint: &CheckpointData) -> Result<()>; } ``` @@ -108,6 +108,7 @@ The concurrency parameter specifies how many threads the workflow uses. Having a ### Hybrid mode Specify both a local and remote store as a fallback to ensure constant data flow. The framework always prioritizes locally available checkpoint data over remote data. It's useful when you want to start utilizing your own Full node for data ingestion but need to partially backfill historical data or just have a failover. + ```rust executor.run( PathBuf::from("./chk".to_string()), // path to a local directory @@ -129,9 +130,16 @@ Code for the cargo.toml manifest file for the custom indexer. Find the following source code in the [Sui repo](https://github.com/mystenlabs/sui/tree/main/examples/custom-indexer/rust). +{@inject: examples/custom-indexer/rust/Cargo.toml} + +{@inject: examples/custom-indexer/rust/local_reader.rs} + +{@inject: examples/custom-indexer/rust/remote_reader.rs} + ## Related links - [Sui internal example](https://github.com/MystenLabs/sui/tree/main/crates/sui-data-ingestion/src/): Sui data ingestion daemon that runs internal pipelines. - [Production example](https://github.com/MystenLabs/sui/tree/main/crates/suins-indexer/src): Sui Name Service custom indexer. +- [Using Events](../sui-101/using-events.mdx): Events in Sui enable you to monitor on-chain activity in near-real time when coupled with a custom indexer. diff --git a/docs/content/guides/developer/advanced/graphql-migration.mdx b/docs/content/guides/developer/advanced/graphql-migration.mdx index de39689752c17..db5fd3629ee03 100644 --- a/docs/content/guides/developer/advanced/graphql-migration.mdx +++ b/docs/content/guides/developer/advanced/graphql-migration.mdx @@ -142,8 +142,49 @@ The cursor is now passed in the `after` (or `before`) fields on the connection, +## New features + +There are also things that GraphQL can do, which JSON-RPC cannot: + +### Example 4: Getting objects by type + +This query fetches the latest versions of objects of type `0x2::package::Publisher` that are currently live on-chain. + +```graphql +query { + objects(filter: { type: "0x2::package::Publisher" }) { + nodes { + address + digest + asMoveObject { + contents { json } + } + } + } +} +``` + +### Example 5: Paging through package versions + +The goal is to find all versions of the Sui framework, and list their modules: + +```graphql +query { + packageVersions(address: "0x2") { + nodes { + version + modules { + nodes { + name + } + } + } + } +} +``` + ## Related links - [GraphQL reference](../../../references/sui-graphql.mdx): Auto-generated GraphQL reference for Sui RPC. - [GraphQL quick-start](../getting-started/graphql-rpc.mdx): Querying Sui RPC with GraphQL gets you started using GraphQL to query the Sui RPC for on-chain data. -- [GraphQL concepts](../../../concepts/graphql-rpc.mdx): GraphQL for Sui RPC examines the elements of GraphQL that you should know to get the most from the service. \ No newline at end of file +- [GraphQL concepts](../../../concepts/graphql-rpc.mdx): GraphQL for Sui RPC examines the elements of GraphQL that you should know to get the most from the service. diff --git a/docs/content/guides/developer/advanced/randomness-onchain.mdx b/docs/content/guides/developer/advanced/randomness-onchain.mdx index 61919db7a222a..eae4343a8bf7c 100644 --- a/docs/content/guides/developer/advanced/randomness-onchain.mdx +++ b/docs/content/guides/developer/advanced/randomness-onchain.mdx @@ -20,58 +20,126 @@ Although `Random` is a shared object, it is inaccessible for mutable operations, ::: -Having access to random numbers is only one part of designing secure applications, you should also pay careful attention to how you use that randomness. To securely access randomness: +Having access to random numbers is only one part of designing secure applications, you should also pay careful attention to how you use that randomness. +To securely access randomness: - Define your function as (private) `entry`. - Prefer generating randomness using function-local `RandomGenerator`. - Make sure that the "unhappy path" of your function does not charge more gas than the "happy path". +## Limited resources and `Random` dependent flows + +Be aware that some resources that are available to transactions are limited. +If you are not careful, an attacker can break or exploit your application by deliberately controlling the point where your function runs out of resources. + +Concretely, gas is such a resource. +Consider the following vulnerable code: + +```move +// Insecure implementation, do not use! +entry fun insecure_play(r: &Random, payment: Coin, ...) { + ... + let mut generator = new_generator(r, ctx); + let win = generator.generate_bool(); + if (win) { // happy flow + ... cheap computation ... + } else { + ... very expensive computation ... + } +} +``` + +Observe that the gas costs of a transaction that calls `insecure_play` depends on the value of `win`. +An attacker could call this function with a gas budget that is sufficient for the "happy flow" but not the "unhappy one", resulting in it either winning or reverting the transaction (but never losing the payment). + +:::warning + +The `Random` API does not automatically prevent this kind of attack, and you must be aware of this subtlety when designing your contracts. + +::: + + +Other limited resources per transaction that you should consider are: + +- The number of new objects. +- The number of objects that can be used (including dynamic fields). +- Number of events emitted. +- Number of UIDs generated, or deleted, or transferred. + + + +For many use cases this attack is not an issue, like when selecting a raffle winner, or lottery numbers, as the code running is independent of the randomness. +However, in the cases where it can be problematic, you can consider one of the following: + +- Use two steps: +Split the logic to two functions that must be called by different transactions. +The first function, called by transaction `tx1`, fetches a random value and stores it in an object that is unreadable by other commands in `tx1` (for example, by transferring the object to the caller, or, by storing the tx digest and checking it is different on read). +A second function, called by transaction `tx2`, reads the stored value and completes the operation. +`tx2` might indeed fail, but now the random value is fixed and cannot be modified using repeated calls. +It is important that the inputs to the second function are fixed and cannot be modified after `tx1` (otherwise an attacker can modify them after seeing the randomness committed by `tx1`). +Also, it is important to gracefully handle the case in which the second step is never completed (for example, charge a fee in the first step). +See [this](https://github.com/MystenLabs/sui/blob/main/examples/move/random/random_nft/sources/example.move#L117-L142) for example implementation. +- Write the function in a way that the happy flow consumes more gas than the unhappy one. + - Keep in mind that external functions or native ones can change in the future, potentially resulting in different costs compared to the time you conducted your tests. + - [profile-transaction](../../../references/cli/client.mdx#profile-a-transaction) can be used to profile the costs of a transaction. + + + ## Use (non-public) `entry` functions -While composition is very powerful for smart contracts, it opens the door to attacks on functions that use randomness. Consider for example the next module: +While composition is very powerful for smart contracts, it opens the door to attacks on functions that use randomness. +Consider for example a betting game that uses randomness for rolling dice: ```move module games::dice { -... - struct GuessedCorrectly has drop { ... }; + ... + public enum Ticket has drop { + Lost, + Won, + } + + public fun is_winner(t: &Ticket): bool { + match (t) { + Ticket::Won => true, + Ticket::Lost => false, + } + } - /// If you guess correctly the output you get a GuessedCorrectly object. - public fun play_dice(guess: u8, fee: Coin, r: &Random, ctx: &mut TxContext): Option { + /// If you guess correctly the output, then you get a GuessedCorrectly object. + /// Otherwise you get nothing. + public fun play_dice(guess: u8, fee: Coin, r: &Random, ctx: &mut TxContext): Ticket { // Pay for the turn assert!(coin::value(&fee) == 1000000, EInvalidAmount); transfer::public_transfer(fee, CREATOR_ADDRESS); + // Roll the dice let mut generator = new_generator(r, ctx); - if (guess == random::generate_u8_in_range(&mut generator, 1, 6)) { - option::some(GuessedCorrectly {}) + if (guess == generator.generate_u8_in_range(1, 6)) { + Ticket::Won } else { - option::none() + Ticket::Lost } } -... + ... } ``` An attacker can deploy the next function: ```move -public fun attack(guess: u8, r: &Random, ctx: &mut TxContext): GuessedCorrectly { - let output = dice::play_dice(guess, r, ctx); - option::extract(output) // reverts the transaction if roll_dice returns option::none() +public fun attack(guess: u8, r: &Random, ctx: &mut TxContext): Ticket { + let t = dice::play_dice(guess, r, ctx); + // revert the transaction if play_dice lost + assert!(!dice::is_winner(&t), 0); + t } ``` -The attacker can now call `attack` with a guess, and always revert the fee transfer if the guess is incorrect. +The attacker can now call `attack` with a guess, and **always** revert the fee transfer if the guess is incorrect. -To protect against composition attacks in this example, define `play_dice` as a private `entry` function so functions from other modules cannot call it, such as, +To protect against composition attacks, define your function as a private `entry` function so functions from other modules cannot call it. -```move -entry fun play_dice(guess: u8, fee: Coin, r: &Random, ctx: &mut TxContext): Option { - ... -} -``` - -:::note +:::tip The Move compiler enforces this behavior by rejecting `public` functions with `Random` as an argument. @@ -79,17 +147,22 @@ The Move compiler enforces this behavior by rejecting `public` functions with `R ## Programmable transaction block (PTB) restrictions -A similar attack to the one previously described involves PTBs _even_ when `play_dice` is defined as a private `entry` function. For example, consider the `entry play_dice(guess: u8, fee: Coin, r: &Random, ctx: &mut TxContext): Option { … }` function defined earlier, the attacker can publish the function +A similar attack to the one previously described involves PTBs _even_ when `play_dice` is defined as a private `entry` function. +For example, consider the `entry play_dice(guess: u8, fee: Coin, r: &Random, ctx: &mut TxContext): Ticket { … }` function defined earlier, the attacker can publish the function ```move -public fun attack(output: Option): GuessedCorrectly { - option::extract(output) +public fun attack(t: Ticket): Ticket { + assert!(!dice::is_winner(&t), 0); + t } ``` -and send a PTB with commands `play_dice(...), attack(Result(0))` where `Result(0)` is the output of the first command. As before, the attack takes advantage of the atomic nature of PTBs and always reverts the _entire transaction_ if the guess was incorrect, without paying the fee. Sending multiple transactions can repeat the attck, each one executed with different randomness and reverted if the guess is incorrect. +and send a PTB with commands `play_dice(...), attack(Result(0))` where `Result(0)` is the output of the first command. +As before, the attack takes advantage of the atomic nature of PTBs and always reverts the _entire transaction_ if the +guess was incorrect, without paying the fee. Sending multiple transactions can repeat the attack, each one executed with +different randomness and reverted if the guess is incorrect. -:::note +:::tip To protect against PTB-based composition attacks, Sui rejects PTBs that have commands that are not `TransferObjects` or `MergeCoins` following a `MoveCall` command that uses `Random` as an input. @@ -99,46 +172,12 @@ To protect against PTB-based composition attacks, Sui rejects PTBs that have com `RandomGenerator` is secure as long as it's created by the consuming module. If passed as an argument, the caller might be able to predict the outputs of that `RandomGenerator` instance (for example, by calling `bcs::to_bytes(&generator)` and parsing its internal state). -:::note +:::tip The Move compiler enforces this behavior by rejecting `public` functions with `RandomGenerator` as an argument. ::: -## Limited resources and `Random` dependent flows - -Developers should be aware that some resources that are available to transactions are limited. If a function that reads `Random` consumes more resources in the unhappy path flow than in the happy path flow, an attacker can use that difference to revert the transaction in the unhappy flow as previously demonstrated. Concretely, gas is such a resource. Consider the following code: - -```move -// Insecure implementation, do not use. -entry fun insecure_play(r: &Random, payment: Coin, ...) { - ... - let mut generator = new_generator(r, ctx); - let win = random::generate_bool(&mut generator); - if (win) { // happy flow - ... cheap computation ... - } else { - ... very expensive computation ... - } -} -``` - -Observe that the gas costs of a transaction that calls `insecure_play` depends on the value of `win` - An attacker could call this function with a gas object that has enough balance to cover the happy flow but not the unhappy one, resulting in it either winning or reverting the transaction (but never losing the payment). - -In many cases this is not an issue, like when selecting a raffle winner, lottery numbers, or a random NFT. However, in the cases where it can be problematic, you can do one of the following: - -- Write the function in a way that the happy flow consumes more gas than the unhappy one. - - Keep in mind that external functions or native ones can change in the future, potentially resulting in different costs compared to the time you conducted your tests. - - Use [profile-transaction](../../../references/cli/client.mdx#profile-a-transaction) on Testnet transactions to verify the costs of different flows. -- Split the logic in two: one function that fetches a random value and stores it in an object, and another function that reads that stored value and completes the operation. The latter function might indeed fail, but now the random value is fixed and cannot be modified using repeated calls. - -See [random_nft](https://github.com/MystenLabs/sui/blob/main/examples/move/random/random_nft) for examples. - -Other limited resources per transaction are: - -- The number of new objects. -- The number of objects that can be used (including dynamic fields). - ## Accessing `Random` from TypeScript If you want to call `roll_dice(r: &Random, ctx: &mut TxContext)` in module `example`, use the following code snippet: @@ -158,6 +197,5 @@ txb.moveCall({ - random.move -- [Randomness NFT example](https://github.com/MystenLabs/sui/blob/main/examples/move/random/random_nft) - [Raffle example](https://github.com/MystenLabs/sui/blob/main/examples/move/random/raffles) - [Sui Client CLI](../../../references/cli/client) diff --git a/docs/content/guides/developer/app-examples/coin-flip.mdx b/docs/content/guides/developer/app-examples/coin-flip.mdx index 1989b73f28d87..ef428a5681323 100644 --- a/docs/content/guides/developer/app-examples/coin-flip.mdx +++ b/docs/content/guides/developer/app-examples/coin-flip.mdx @@ -23,7 +23,7 @@ Source code locations for the smart contracts and frontend: - **One-time witnesses:** The guide teaches you how to use [one-time witnesses](concepts/sui-move-concepts.mdx#one-time-witness) to ensure only a single instance of the `HouseData` object ever exists. - **Asserts:** The guide teaches you how to use [asserts](https://move-book.com/move-basics/assert-and-abort.html?highlight=asserts#assert) to abort functions due to certain conditions not being met. - **Address-owned objects:** The guide teaches you how to use [address-owned objects](concepts/object-ownership/address-owned.mdx) when necessary. -- **Events:** The guide teaches you how to emit [events](concepts/events.mdx) in your contracts, which can be used to track off chain. +- **Events:** The guide teaches you how to emit events in your contracts, which can be used to track on-chain activity. For more information on events, see [Using Events](../sui-101/using-events.mdx) for practical usage of events on Sui or [Events in The Move Book](https://move-book.com/programmability/events.html) to learn about event structure and how to emit them in Move. - **Storage rebates:** The guide shows you best practices regarding [storage fee rebates](concepts/tokenomics/storage-fund.mdx#incentives). - **MEV attack protection:** The guide introduces you to [MEV attacks](https://github.com/MystenLabs/satoshi-coin-flip?tab=readme-ov-file#mev-attack-resistant-single-player-satoshi-smart-contract-flow), how to make your contracts MEV-resistant, and the trade-offs between protection and user experience. @@ -372,7 +372,7 @@ module satoshi_flip::single_player_satoshi { This code follows the same pattern as the others. First, you include the respective imports, although this time the imports are not only from the standard library but also include modules created previously in this example. You also create several constants (in upper case), as well as constants used for errors (Pascal case prefixed with `E`). -Lastly in this section, you also create structs for two [events](concepts/events.mdx) to emit. Indexers consume emitted events, which enables you to track these events through API services, or your own indexer. In this case, the events are for when a new game begins (`NewGame`) and for the outcome of a game when it has finished (`Outcome`). +Lastly in this section, you also create structs for two events to emit. Indexers consume emitted events, which enables you to track these events through API services, or your own indexer. In this case, the events are for when a new game begins (`NewGame`) and for the outcome of a game when it has finished (`Outcome`). Add a struct to the module: @@ -1081,7 +1081,13 @@ execCreateGame( One final step remains: settle the game. There are a couple of ways you can use the UI to settle the game: 1. Create a Settle Game button and pass all the necessary arguments to the `single_player_satoshi::finish_game()` Move call. -1. Settle the game automatically through an events subscription. This example uses this path to teache good practices on events and how to subscribe to them. +1. Settle the game automatically through an events subscription. This example uses this path to teach good practices on events and how to subscribe to them. + +:::info + +Event subscriptions are deprecated. To learn future-safe methods to work with events, see [Using Events](../sui-101/using-events.mdx). + +::: All of this logic is in `HouseFinishGame.tsx`: diff --git a/docs/content/guides/developer/app-examples/images/styles.png b/docs/content/guides/developer/app-examples/images/styles.png new file mode 100644 index 0000000000000000000000000000000000000000..6ae76a56b8beb58874044987077e9d633cac0e91 GIT binary patch literal 11641 zcmeHtWmp`+vNrCrxWg_MAh-l~3GVI$3lMy9cSvw|3&Ab8d+^}y?iw`cw>kIRdrr>% z_x-;6^z775mrPe}&s0~x5z311(NTy|prD}8Wuzrkp`c(DAhb9V0_0m{^Q;>R3R=%f zTwGa3TpXzEWN%^h$s7ubF3!~0cuSg*ZqUTU*m&^gTY3~HH`Va)NL6FM?yhd&SQkcc zTg1>0_-JToArTrhR$@Hw*8VP}e3s+DQR_Ls6-VK{kOJ(-pz1=kz2?*-6(}uPYe)9j z7uY_8A$Um|G_*)Q5mylp8R&2WShFv<&}8stN>JaYK_@goSM1<+*m7FxL83t+STj^= zOX@1q#Z8D;~h2Q|*fFjYTo@V&7q-<~U$GfLfG7LlF4{gJOAo zAs&tg^K8C5)YX;v1t5oHY`ky@y(1W$%D`Ig9?ii@8qOrmC>tEcu+5#k#c4`~l;On> z3wvDm`YIg8!CGy@&id5;^7?vy`uh56(BIYNjaqz10(BVq!B0~w(gfmdO>=D-3k3xz z1_+G=1s!Gu1qY#^AqO$!fCMH!6bcb?#)cdcxiJ6T3#*U|``K1qH5MkT^p>eAkSxNZ_8|AX8*yQ+1=LRj|h~2J0FC!H3ypj-EBYF zIrF&-QvQR34?_RB%t8tL2MgFnkWyPg87OY=WDb1C%*xD4DTD$90tKAREcjFY@)e;NEYLG6DD@^EtfyX3$1{HLUbv$>PF zy)DEdSmh}|g}7M=MS$ghLMDV_8o^iu1x24MBO$8p z4t<&hpNczxAHssIABY&g0U(8e(-8}7ZRLjxgX&&oYJv|Faj!>KzyJ{>WlngQu#Eq- z>7Red^ori*{ae*;y~DKKu+80M_TxSnM{j+pA&L zW=?iM(O~VK+RN281qq9??b7$vC2ZQ#H|_}@RWD43$FFi{G|oUVTYv`s3vTV4{Zh52 zkX+i-!%G7JF}E#qtLy2lR+Ms{m`o%g8?|9u(=U%gdDT2`UYTUZl1?Xm(Z2?Fjv7!6 z(wRIDi-|SqeUIQiEX5x%Gk}c%J(D?E+J-wyb?54o)d%qcmO)F0r@P^^j0_!i6 z>jO=L^^g#+yEFK~-+L8$zecJ$hZ|1$;nr$C;|-7a`}$CS=nFp=o7F-5%k}P5Fd&BX zCEsOTZ>JrH(v4~?D)S>^5&Ih@E$^Gz-Z{J>SDkVC)f zd0jUL!ad)HLoPqp7SUd8wJ`Ea(!21SXHrO?sp)qMsoV=f#58*ifUU6{7fO3)S}5Ro zEnOw-d6Iff!K3Zzdj3#pgmL`-FW)J`iRp8u)n+KSzgusZS}V%MR^aI$)z_}ZtzB@)+}zV-7~e+My1-MmzLM%p-#WaK?R#u zmd;A8DEaC4)i8lht?up#>VQ&atU|`Skv%5iW9P5>R~8B!#3d5)(P77=4O(P}!W=s40l|6VkAzQjPTCk*o( ztnck{e-U@GR;!2FA?S5iaxpj&FR1r!DESn1C{B>17xfuy8wDE}a2L>J>pDL0>XwBkx8>-pGIF~73_;3_r+zvMyo1t8#8ub_)j#Flbvqd>KWT-`oS;=X_ zWAROnTZ8eEsMOy}zp%}(A0Ew?v47b4SgWI$BSd90Uy-8mHf8AL8L_};VC;kMTO`Hs zvQt@o#Vi38q5BP3n@)bWGwG0FjhmM_*?RwVOf78YBxmnP@oSgFv@4es-rdp8P`pfs zw?`1#(C;gPo%veJZ_3>;qOS7f;c#N{UEkzV!0%!D^QR4!HX!q)#^=$sL^&_;EOM>h zCZ$6uh1uZYhQD(c8mk*-`)& zCJj<>Qb$?&>V&fWe8<4+`=zV>@o#!}*}otUBjW?Ba=p&H z1mhmhldv5krcPVaq#qBtH3CL{yWnE+FbutQwz(3w^_RK3Cdcj1F-nRNxb*RcZrghb zCHnO({t5icWpL;MN}-+fL9tzxtDdq|Yd$yjyDdJ`drZS$?i1)MLq60K*Jr`u2w13m zm8u^m<@J=eo+&pCA<-pi`t$!C!w%0C7{2IhZO!>Qb$}F<^7uLl3`W2prlgO5lR}^1 zt4t;{^3Cbn=bGL9im7r*xnthF!}=5k9kIDeL!yE}_meIdBh28W%tateQ`n{hsGFPEj zaoTjN-|Qsw1)r(hMVs*5r&P1??B{yhp@cU|M>UufLNkw5dX0*O3K@Ig@~)0tW}CTk z+}QmCAAmg2=kRWQmO_R$jmx>^WW84I;r6V=&%gQVnY@y#R4V~z0Ar+O zw!_Kcl{|uoBa}I)@rLA`?g%UbhLiP2ZtDe++p{ImqwBdk2#mhF(!5??^J)RDb21_B zc{?g&uzr0wr4#o1`IkA~n46SePKg-0-{WapJ-T6T)bZS4TEc+&$w8cFHeGm8ZbKry z8V`-1NEHiO9{K5#8z7;++9c?~N!n3Aw~hAzFO)FE;AI#%uiDit-c=Gq#zmT)Y#uhvp$B}hNaPJQ900F|F-(CTWf z-KT1`+EV{%rAbAzQop4CWIquSic;xsS?51*D3njryMquB>A>t3qs3tyq@#2h`-23%O=p`^0 zz@KScY6ETqF|TpdvZ=~BEk-$pH5Rid&ycCzs)!to?x=om7_s%E;^=g_aa~s^54WDp z-M|xkOiAIFDH2OorI#}H%1c`C>~?z1#yWs2_BNf<&P=gLm~9Wmbt~9NQ0U=+iiq2_ zu0ZxCO(L39CRPB}1sMaCt1Ae#jBXjr6QSCRun=a=^ZoV@mN>$l&LWC(h zV*uJfDJWkSeYNFx>wI`NMIDyXIfh}Ic=Y;%^dpURs*J@O`MovaD8t1=FHie{=74 zPd&ku#}XO+!ext@ou{OA3A?`(N`?5)FG5J``R?>az!uiHGMlk3@sP8L~C zM}y%s(N~4G*I~GaP6iL-&2N-)I+wcnye618UP5EdgZ-PARypb{`8KZSgjF0CUf(9C*JG<0k~YBO;y#R5ju2YF3l*)PA?X*oX#;lkqIJ8G%_tR zbtLfI2`?9-d&5Z-U1IG#*?b85-r9PB4k{71N#HHQ)(jr8ww+_prBh#3PDfxls@-+wNe9kB^^XSrsl-8Jc*E~1H6H+y>3f^hXp zeVszZtkRf>MvI^6C64=Ei8veFKQZRXah)rM~Lvx2mGPH**Gta2!U$Ia2UK79xfUr|C1S758PsT4>3*irvu? zqN>*clgIrIg-kNA+;m+$W;gBuL)=ICEVs1xaGlJA{L$B=a9$FPLRKscn`ujmVG=Ak z%kpTsx!a$W!ftirW5u_mQQoMn_Ebg)H&ZVcLWqgV{66-c;~b-7B9;Y3qY#s~|i33)xs6IqNjL(z!o za*Cy9xfRlN#Nh67?bA8uheECAOXO9=e-$u&vYGRpJiSVw}Z!X0Q@S^Ge zA>_Ar?=v~A-)iT>Bt0pr@!i{lX|Dx*Y?p;Sp0(t1d1@^m0QcF{GT2tj4Hdamh2|s44>@PhGERJ4iYb(t2EG4EtEg>3^#rz zKZyVeV99rThP-ZVIBxelw$7g}yA^0;G}L%p;lC{(we8k=JDx4LUq_UUr6rd}8FF08 z{XouC>Wu$&FplbRnF8cM%K>~xD-*BX&QBs>bfKAc;xVT8`phk0_EVTR`ojXb7BbyQ zs2Oem5ixY-;ru3YjUJr}6Gujf=HmIu9^V=2@`M+PR_phIqyBB{CL$v7(01q2uVD4nJHSzl-m;qh<<3hWKEYl_Co}cVJQCL9_2XXfKRO zvqvH#U&66>IG)^*Mz>&s-FVF)t!6I(Us$p;{A1RylcfErd@v_{4u5IixS+^`nXo%U6OzCIv%Kgb|V@^j(+C)NFskrsSjLaP2 zDC?0gr~M1e;d)~uKF@)12DfGMSjT4^TrI(=@UDgYt;@JPm#btZS?be7JUN20?3HmB zGFAhrp{d=>qv`hd_N&!ttagz?%mS8!FN+oLS?KlppS!FicyjecLg`=y2o$q~%+0++ z!gWir&vWfq!3TJB>grJ>WK2jpuJtkrMWvyO28(Sb6=Uh2PY^iF{ZJDHrl5~1Csfe+1 z9|SDnbxLTn2gw1?a_#Rh6_EGVTQDJsgmNTdG>8;{OW3JaZ>?ctpn^Au4zj+zA-?2z-mN%o2E_ zV4@|Fi^%Sb5Hqw3{VZie_ee|zmHgl%v&(hgK2cCQ&~w7>@uYVA+HxS8%%u)bKoDRO zhDkAZ2mNV)M|I|Hn)TI|uTI@ZmBsusw?_?i%r>h4BEG^+WbyXtGHDRu(zlBSQEC%E zBO{}Ba??SvrA)mnQSaM36;}9QC_lK6FL;ffs~1p%rR2 zKJ+r2%gxe+{z@02t_$Yy9nQ8I&m?KD6lF?v_%zS@ld*~z5y`8LM76Ya>G=_JCW`*E zVHEhd+XV2%)p>vNK`pA2J+$fN{xa*1ZTVgXMpSg8>b>71YU4;EXQt3~m zqsM83-|Pl(^v5_d)_51kK%UY*^m3_yL2U5|673A9NgAb4q=&o`tv1h~CM&hIs%`Ti zZ1W3dNhAWWJ2G8w4dr~4{NsyeOHTtuJmzrLc2k0ExnD8DvCdjKa#><3W^?^v zHCiDkbIgR2Ul=nHV33~)0QQ(UZHb~+=Q2qGaWpoc9oTcTzk*Dv)EpR110AUQ7jyf` zf$9VZ<2ZeZW!+T47hHsV!xK$@*Y|ODeH%hnX9fU1S}XM7y1BwFRKvFv=~r4g$>-5XV{cNwX!^$E#PtF zstd0+Cg1Gu)PE7l8i;&6K{r}egG}y$=W9BgrWA?#qxrlv)~3xEd5ixjpG1pZZICTV zq)2VxR+El@SR`@_FO^0qPMFstONA+`46r5NflVTmb|df05WDN;WxSFZb^89P;xQP~$J^6o&jskYR=a@~VfBxU{g`RW{74|vk0V6( zghHTf`69M6w&#Z%4oSg>V1Sr~NbrH^HAn@2ZgUA#Sbet99rUm?Ar*N4`D>^_x#rrv zbLlqevVF;IRazrQbpmB37Q$we`{jrB$Ue*sXp1VGQqM4BZ`5~_!4q(!?ZN%5Q=dD} z8^pf>xlK^+xAj`BzfB8BUx+keyps~Au(DG?f@804R&4V+xQGLYCP@9HGMB)?CI)iC zZM#OEqJ&1Wgom9k`}y;RkW&T!&N0rh9Ht&TgX?WmwLi9pBYM2p;1rECZxNGE>4cl;Uy;{+_-q8NC4z21x702*zo6QP^O?f?zR~x<|3< zpdyIb>M~omE;4>BZ=nsz)zJE%V)d&8yF@&EFs3-G(3z681ouSQ^@nC(#Cq zbJ7QdvWg-xh1YD$N^~CwvkC=cOR3z*~#cyl~Y;&7VARctNQ%{MGb@3VQ~VjdS#wrAu8W>e=Z(gXj(e~1BUVZ0 z4d+&!o?Be%=rZ|yTF}Ve>61~#5!>i0yisD05_WrZBts5{gGNG-Cn9Yy`Zr0&$vQ$XjX*c zLeXSW%!Ym-Zo`n{v}e#Kqfuc`+DlR`(=5*A8Q%Fm) ztyqY}E@J_dUZoC8Bf&KnJf}oNMp0FsrNOgmOi#>f?x2eP)#S+G1dHgXnr@Q|h9qV^ zMK7N{y30gvCGypYYE+_3x8WHpKHlz$?jY(I*-9L}kc|^{({zCAUdd*v^g^tTxryW; ziP+SHHiK4Pyi52@iR!WX!N>ar5?O#=|A&F-uLF;0ufE~S4z2!ha5XUx50xm@Z%9pL zH`!_}ecJQ)+MMgl<||wA%<00025{ae(gh)0yR9m47hf>?D|6my`h~#gK$D*?*Ux5I zLEd;NzpXt#LkE!(b?DZAA|K3#Z1Qv~L9GFw&P~cyAo*SePQG(N8}TwC=7c{qY+cyL zxFhd?#>H?N_neIq*;q(PVD!9`@I?vG6Uru}kk0uX1AY@BFXR;P4yl_Xw0Z>|6~CCS zWQVUiNasAg>qj|v4nSC%K6(d_HW9E4AV$LAd6R4+EfBB`O5$x>Y8qI?F+LE3ZX>GF z9g3&XxUZw?LF&OYWbk@FdwfLFwfTBw{ThEOOfhv11yFQ%STI6b^rATZQm#{B{q+tl zEhM%ZX$S}iie?l$8(m9?@XL(y_sK1xxCldqVGFNhLCAt zflQ8!cgK_NF9ODLFOk!P_H?Ig@BH^TI)xXqX?$6@Z&hSHOL^k~OCK0rj@0?&d%fQ? zIB~>LzT^=zWT*0AnP$YIH0p;82S&KX-JrbkbRVvkd_^$HAR%5E{5G7ZwGHoi#9KF< zT=lUzaWca+Cv{#Q74lFlKmt0#_Olvv{8o)|BwlAvM)~R7muJ+md%^lm>Ez;saeWuS zT)f=Igh^v{I-*)UeIrtaatR|I4ivjFj>&nZ>KLwtABo+8eMZK_>LY5CA+!u}?#W1^ zsu!16xNff+`Arvti?*Gke9l`+eo;_gg=>&}7hMKV+K1O~4BrR}fFVW^9!szIff5gB zs`atX?iekdR8#TDizUK~`pO-J#B;aIOICBM@D;(65#x--eK1GBW0;SI4=`E6={|Tj z$G38IlErzzD*N>;3t(Fu=8^XmV>?g2s@%n23l$!9haep2KB|wNCN~+NrHx+QK*BeD zl6DDv0uYDO!MlZC%0c$X=6gnGP@AMN2l zU@Vhp9)x~&lT5;RQZ}!WEb5eXd}u1wWG21B1fDL~AY@&T{zgQJ>JLZz?o`lI3>6z3 z-0EMNW22t)O5ufwrKevcaHUA|(2_xLn+oidh>!&q?;xf7W)Hkz4&IqgHX-(RpNGSu zsR}*r)ApY4BxPp&gOnc&i$$~8Xi>LI01X6?Vnuf0pH0Id8>BdA^PlOGmP~x!JH7EPVQi<4eBxsGK#3pDtR?=!IQASlCnTyI|gS?}7 zYiE)qyjdXqo&DkU#;-cX0~HBx9}&$uX%ix$PsM7Hf}SF@N}0e}>zR1S?$9B)_}qG^ zwTO;2@4gfzytz*G(0ZfL#X}Y8s9Vz;XPnoy9}S0z{zXb9?VNlhz$iy10Arq7w4BvC zGmeV1(kZ*EcLPwV1#&afX*$j0ByrY5C5_YyERMhtjGR$5O^Or+FfBYN$F8+GMihIx z?CorVn>L!uFrZV8HA=4!tOcg;@$Pohhf~9RewlwR#*yrL#1l6V$LH#xr$!Siv8)op z){DsVW_V2Cmn&-HJXq2DKBe{AdA{&{di}X;)%Vs#nl4BYpIT1jlW;$VkFdN8A;YFA zn#qu$dM^|iC#r_{o9JH*pdbTY3T&Xmw~&dKeN&jc)`1BlnB+{#~~6&R3% z_{Ck@{_JSIYF)umQe&BZ;oYwAx)Y@p3(hAu> zW#PE5tmssvbE)5Uh3G>0eBGmnET*N&DMe_a(zxG?I=*IJZ-5j*U%4hsmH>I3uf94i z(Kku%ms^Ky*mQ{sQI}ml1Vn0B9gvE^2bv>`qf5)}foOcre1IbI=2E$g>4_LsK%*iQ zeFq3GR>EYh&7(z#@H~=p?LH%<+Xf~ECs-CzimNXwXiCpU&^ZenX3#9Bg>z$p3=MM5Li*1D-wy(~*p|lYEev^RODThlJl^*X zBUCX3c&SvSkOE;~Fz#S6fhwe8M_<4|5x*ZmVDYlT%RpmeIqTe@Bi!WR%bEXpb66n` zn)~cG8O#oON#O{@&b{5W-%}97v=}E}C4E%~0n?RZf@Ry!S2{GF+0JKbdrb2e$JJY; z1PX*7=(2qs@<uAuHK~5kUt?$ zdUz~&K@=r&Pd=I}si6IALt1V~t447>DyIHnwAYYkOMVJtg|x;ePQ!Y(ys}qC-orOu z!~x4c*8NhoeNq0V{$p9XR#cq#Q=`1Lhmo%;{SOlgDGa7{jY#Gj6n;1bgk7fe1-=Hw zCWl{B9H;1ktX9gL>6~<10B+9#s5OMS7!Z~QWw^_5P_X*$pmi9UNBb;X>jf)O?}`dQ zq?&Zt3r-v+LVl>{5Flu_Aon|BTPX0NJ%{f{+$}@<@4Q;2n}s*jVgA}Z+C|kSPkq5c z%B<~4YKwahwgQ!aDZGm9E%h{7m6BjZQhe)Q&YDZaZJ>@*TF-NX}BG5BnR>p=)8HDgia zTtBTP?vrlyte1931Pfud?1$?PJz>Srop*o588SRKURGuZ0`}C30eiC6`2(Bzy(lID zskrl5pv50ELpa}JwsZ=;;a5SaVjjONid zN6a$Ns?OUX^dFTGD0>J6XfIS3(dip5*&ED9@N0{O*+K_hDCCR35Ma}EjhG4}x&{^@ zuz>BB6I6{!hB1iP`%aq_J~fimK(`QA63GBh5H>k1MhVGk!Y#3q%fi=pQN&jK1T!-rSb!P zpJKpDX37+=3ej#aPw+R7z+>Zl?!_o`8DJmMit2VGE23H(p==zSaZL6L)gAc@)1@hb zKCQpOjdcNh3?36Wwh;Ecu9JJ^)>}+%he9IElNN!+(22(PG2}K?U*7D(G6ap$bAl(_ zt4WAimo*Ua1y%tZ{Sa(SrGO&CwOM_Yj=2w|Ebv1gk|qP#rq>340y&r!Up~pd-_SVp zV|eS`8Q(1OE*2z+@J?&k(2<)%zjw8;-&!zs?=(z)eVM|MVA0}J{-qxK|M>rjqZ6P6+)nKtljE6z{v9y&hrW=4$$L@Riy|1a`8zm}P9-E( zfpHvJM)AMEPShLqe`0)Fv>%f2H*n4!f|?upKmPv@|GXeBo{nelSy`3;0ouw)DoRv} H83+D9pC0gr literal 0 HcmV?d00001 diff --git a/docs/content/guides/developer/app-examples/images/trustless-accept-escrow.png b/docs/content/guides/developer/app-examples/images/trustless-accept-escrow.png new file mode 100644 index 0000000000000000000000000000000000000000..8f7a6c6b0f5aa4aae244d93446cda619872f39e2 GIT binary patch literal 72945 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%0AEqwNLtKQQNZyl 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 literal 0 HcmV?d00001 diff --git a/docs/content/guides/developer/app-examples/images/trustless-cancel-escrow.png b/docs/content/guides/developer/app-examples/images/trustless-cancel-escrow.png new file mode 100644 index 0000000000000000000000000000000000000000..832e6bdc544358af293f7bef05af1b283276891a GIT binary patch literal 60283 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 literal 0 HcmV?d00001 diff --git a/docs/content/guides/developer/app-examples/images/trustless-escrow-locked.png b/docs/content/guides/developer/app-examples/images/trustless-escrow-locked.png new file mode 100644 index 0000000000000000000000000000000000000000..6cb2ec3571fb765b4cfd1b720c401023bfade9c3 GIT binary patch literal 43815 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 literal 0 HcmV?d00001 diff --git a/docs/content/guides/developer/app-examples/images/trustless-my-locked.png b/docs/content/guides/developer/app-examples/images/trustless-my-locked.png new file mode 100644 index 0000000000000000000000000000000000000000..4e1898b6313d8798b31176cde68ab25e5ac1ad87 GIT binary patch literal 26789 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� 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 F{{X?fcb5PF literal 0 HcmV?d00001 diff --git a/docs/content/guides/developer/app-examples/images/trustless-new-bear.png b/docs/content/guides/developer/app-examples/images/trustless-new-bear.png new file mode 100644 index 0000000000000000000000000000000000000000..5aaec9ea07f74b81943c2f4a14b8ee4cee4fc247 GIT binary patch literal 37565 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

cOQ8){bNbpT$S|wvHc6BrK=H)n&2Yuy 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 Dmz&=U literal 0 HcmV?d00001 diff --git a/docs/content/guides/developer/app-examples/images/trustless-start-escrow.png b/docs/content/guides/developer/app-examples/images/trustless-start-escrow.png new file mode 100644 index 0000000000000000000000000000000000000000..269f54bdb65f46bf3b25c554a2f7014e2e9379e2 GIT binary patch literal 47171 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 JC>JyG`#

-Toggle full source code +`owned.move` {@inject: examples/tic-tac-toe/move/sources/owned.move}
-## shared.move ([source](https://github.com/MystenLabs/sui/blob/main/examples/tic-tac-toe/move/sources/shared.move)) +## shared.move In the previous version, the admin owned the game object, preventing players from directly changing the gameboard, as well as requiring two transactions for each marker placement. In this version, the game object is a shared object, allowing both players to access and modify it directly, enabling them to place markers in just one transaction. However, using a shared object generally incurs extra costs because Sui needs to sequence the operations from different transactions. In the context of this game, where players are expected to take turns, this shouldn't significantly impact performance. Overall, this shared object approach simplifies the implementation compared to the previous method. @@ -56,7 +57,7 @@ Instead of the game being sent to the game admin, it is instantiated as a shared
-Toggle full source code +`shared.move` {@inject: examples/tic-tac-toe/move/sources/shared.move}
@@ -67,7 +68,7 @@ Multisig tic-tac-toe uses the same Move code as the owned version of the game, b In this implementation of the game, the game is in a 1-of-2 multisig account that acts as the game admin. In this particular case, because there are only two players, the previous example is a more convenient use case. However, this example illustrates that in some cases, a multisig can replace shared objects, thus allowing transactions to bypass consensus when using such an implementation. -### Creating a multisig account ([source](https://github.com/MystenLabs/sui/blob/main/examples/tic-tac-toe/ui/src/MultiSig.ts)) +### Creating a multisig account A multisig account is defined by the public keys of its constituent keypairs, their relative weights, and the threshold -- a signature is valid if the sum of weights of constituent keys having signed the signature exceeds the threshold. In our case, there are at most two constituent keypairs, they each have a weight of 1 and the threshold is also 1. A multisig cannot mention the same public key twice, so keys are deduplicated before the multisig is formed to deal with the case where a player is playing themselves: @@ -89,7 +90,7 @@ export function multiSigPublicKey(keys: PublicKey[]): MultiSigPublicKey {
-Toggle full source code +`MultiSig.ts` {@inject: examples/tic-tac-toe/ui/src/MultiSig.ts}
@@ -122,12 +123,12 @@ newMultiSigGame(player: PublicKey, opponent: PublicKey): Transaction {
-Toggle full source code +`useTransactions.ts` {@inject: examples/tic-tac-toe/ui/src/hooks/useTransactions.ts}
-### Placing a mark ([source](https://github.com/MystenLabs/sui/blob/main/examples/tic-tac-toe/ui/src/pages/Game.tsx)) +### Placing a mark Placing a mark requires two transactions, just like the owned example, but they are both driven by one of the players. The first transaction is executed by the player as themselves, to send the mark to the game, and the second is executed by the player acting as the admin to place the mark they just sent. In the React frontend, this is performed as follows: @@ -198,7 +199,7 @@ function OwnedGame({
-Toggle full source code +`Game.tsx` {@inject: examples/tic-tac-toe/ui/src/pages/Game.tsx}
diff --git a/docs/content/guides/developer/app-examples/trusted-swap.mdx b/docs/content/guides/developer/app-examples/trusted-swap.mdx deleted file mode 100644 index f5bc8beb97bad..0000000000000 --- a/docs/content/guides/developer/app-examples/trusted-swap.mdx +++ /dev/null @@ -1,6 +0,0 @@ ---- -title: Trusted Swap -draft: true ---- - -Content coming soon \ No newline at end of file diff --git a/docs/content/guides/developer/app-examples/trustless-swap.mdx b/docs/content/guides/developer/app-examples/trustless-swap.mdx index ff0eb90f53978..4a6cdb26df057 100644 --- a/docs/content/guides/developer/app-examples/trustless-swap.mdx +++ b/docs/content/guides/developer/app-examples/trustless-swap.mdx @@ -1,27 +1,2084 @@ --- title: Trustless Swap -hide_table_of_contents: true +description: An app that performs atomic swaps on Sui. Atomic swaps are similar to escrows but without requiring a trusted third party. +effort: large --- -This guide and example demonstrate an atomic swap on Sui, which is similar to an escrow but does not require a trusted third party. Instead, this example uses a [shared object](../../../concepts/object-ownership/shared.mdx) to act as the escrow between two Sui users wanting to trade. Shared objects are a unique concept to Sui. Any transaction and any signer can modify it, given the changes meet the requirements set forth by the package that defined the type. +{@include: ../../../snippets/app-examples-swap-source.mdx} + +This guide demonstrates how to make an app that performs atomic swaps on Sui. Atomic swaps are similar to escrows but without requiring a trusted third party. + +There are three main sections to this guide: + +1. [Smart Contracts](#smart-contracts): The Move code that holds the state and perform the swaps. +1. [Backend](#backend): A service that indexes chain state to discover trades, and an API service to read this data. +1. [Frontend](#frontend): A UI that enables users to list objects for sale and to accept trades. + +## What the guide teaches + +- **Shared objects:** The guide teaches you how to use [shared objects](../../../concepts/object-ownership/shared.mdx), in this case to act as the escrow between two Sui users wanting to trade. Shared objects are a unique concept to Sui. Any transaction and any signer can modify it, given the changes meet the requirements set forth by the package that defined the type. +- **Composability:** The guide teaches you how to design your Move code in a way that enables full composability. In this app, the Move code that handles trading is completely unaware of the code that defines the objects it is trading and vice versa. + +The guide also shows how to build an app that: + +- **Is trustless:** Users do not have to trust (or pay) any third parties; the chain manages the swap. +- **Avoids rug-pulls:** Guarantees that the object a user wants to trade for isn't tampered with after initiating the trade. +- **Preserves liveness:** Users are able to pull out of the trade and reclaim their object at any time, in case the other party stops responding. + +## What you need + +Before getting started, make sure you have: + +- [Installed the latest version of Sui](../getting-started/sui-install.mdx). +- [Configured a valid network environment](../../../references/cli/client.mdx#set-current-environment), as the guide has you deploy the module on Testnet. +- [Acquired Devnet or Testnet](../getting-started/get-coins.mdx) tokens for development purposes. +- Read the basics of [shared versus owned objects](../sui-101/shared-owned.mdx). + +## Directory structure + +To begin, create a new folder on your system titled `trading` that holds all your files. Inside that folder, create three more folders: `api`, `contracts`, and `frontend`. It's important to keep this directory structure as some helper scripts in this example target these directories by name. Different projects have their own directory structure, but it's common to split code into functional groups to help with maintenance. + +:::checkpoint + +- You have the latest version of Sui installed. If you run `sui --version` in your terminal or console, it responds with the currently installed version. +- Your active environment is pointing to the expected network. Run `sui client active-env` to make sure. If you receive a warning about a client and server API version mismatch, update Sui using the version in the relevant branch (`mainnet`, `testnet`, `devent`) of the Sui repo. +- Your active address has SUI. Run `sui client balance` in your terminal or console. If there is no balance, [acquire SUI](../getting-started/get-coins.mdx) from the faucet (not available in Mainnet). +- You have a directory to place the files you create in. The suggested names of the directories are important if you use the available helper functions later in the guide. + +::: + +## Smart contracts {#smart-contracts} + +In this part of the guide, you write the Move contracts that perform the trustless swaps. The first step is to [set up a Move package](../first-app/write-package.mdx) for storing your Move modules. The guide describes how to create the package from scratch, but you can use a fork or copy of the example code in the Sui repo to follow along instead. + +### Move.toml + +To begin writing your smart contracts, create an `escrow` folder in your `contracts` folder (if using recommended directory names). Create a file inside the folder named `Move.toml` and copy the following code into it. This is the package manifest file. If you want to learn more about the structure of the file, see [Package Manifest](https://move-book.com/concepts/manifest.html) in The Move Book. :::info -See [Shared versus Owned Objects](../sui-101/shared-owned.mdx) for more information on the differences between object types. +If you are targeting a network other than Testnet, be sure to update the `rev` value for the Sui dependency. ::: -The guide is split into three parts: +{@inject: examples/trading/contracts/escrow/Move.toml} -1. [Backend](./trustless-swap/backend.mdx): The modules that hold the state and perform the swaps. -1. [Indexing and API Service](./trustless-swap/indexer-api.mdx): A service that indexes chain state to discover trades, and an API service to read this data. -1. [Frontend](./trustless-swap/frontend.mdx): Enables users to list objects for sale and to accept trades. +### Locked and Key -{@include: ../../../snippets/app-examples-swap-source.mdx} +With your manifest file in place, it's time to start creating the Move assets for this project. In your `escrow` folder, at the same level as your `Move.toml` file, create a `sources` folder. This is the common file structure of a package in Move. Create a new file inside `sources` titled `lock.move`. This file contains the logic that locks the object involved in a trade. The complete source code for this file follows and the sections that come after detail its components. + +:::tip + +Click the titles at the top of codeblocks to open the relevant source file in GitHub. + +::: + +
+ +`lock.move` + +{@inject: examples/trading/contracts/escrow/sources/lock.move noComments} +
+ +After a trade is initiated, you don't want the trading party to modify the object they agreed to trade. Imagine you're trading in-game items and you agree to trade a weapon with all its attachments, and its owner strips all its attachments just before the trade. + +In a traditional trade, a third party typically holds the items in escrow to make sure they are not tampered with before the trade completes. This requires either trusting that the third party won't tamper with it themselves, paying the third party to ensure that doesn't happen, or both. + +In a trustless swap, however, you can use the safety properties of Move to force an item's owner to prove that they have not tampered with the version of the object that you agreed to trade, without involving anyone else. + +This is done by requiring that an object that is available for trading is **locked** with a **single-use key**, and asking the owner to supply the key when finalizing the trade. + +To tamper with the object would require unlocking it, which consumes the key. Consequently, there would no longer be a key to finish the trade. + +{@inject: examples/trading/contracts/escrow/sources/lock.move#struct=Locked,Key noComments} + +- The `Locked` type stores the `ID` of the key that unlocks it, and its own `id`. The object being locked is added as a dynamic object field, so that it is still readable at its own ID off chain. +- The corresponding `Key` type only stores its own `id`. + +The lock and key are made single-use by the signatures of the `lock` and `unlock` functions. `lock` accepts any object of type `T: store` (the `store` ability is necessary for storing it inside a `Locked`), and creates both the `Locked` and its corresponding `Key`: + +
+ +`lock` function in `lock.move` + +{@inject: examples/trading/contracts/escrow/sources/lock.move#fun=lock noComments} +
+ + +The `unlock` function accepts the `Locked` and `Key` by value (which consumes them), and returns the underlying `T` as long as the correct key has been supplied for the lock: + +
+ +`unlock` function in `lock.move` + +{@inject: examples/trading/contracts/escrow/sources/lock.move#variable=ELockKeyMismatch noComments} +{@inject: examples/trading/contracts/escrow/sources/lock.move#fun=unlock noTitle noComments} +
+ + +Together, they ensure that a lock and key cannot have existed before the lock operation, and will not exist after a successful unlock – it is single use. + +:::tip Additional resources + +- [Move Package](https://move-book.com/concepts/packages.html) defined in The Move Book. +- Concepts: [Wrapped Objects](../../../concepts/versioning.mdx#wrapped-objects) + +::: + +### Testing Locked and Key + +Move's type system guarantees that a given `Key` cannot be re-used (because `unlock` accepts it by value), but there are some properties that need to be confirmed with tests: + +- A locked object can be unlocked with its key. +- Trying to unlock an object with the wrong key fails. + +The test starts with a helper function for creating an object, it doesn't matter what kind of object it is, as long as it has the `store` ability. The test uses `Coin`, because it comes with a `#[test_only]` function for minting: + +{@inject: examples/trading/contracts/escrow/sources/lock.move#fun=test_coin noComments} + +- All test-related functions and imports are annotated with `#[test_only]` to make sure they don't show up in the published package. This can also be done by separating tests into their own module – e.g. `lock_tests.move` – and marking that module as `#[test_only]`. +- The `test_scenario` module is used to provide access to a `&mut TxContext` in the test (necessary for creating new objects). Tests that don't need to simulate multiple transactions but still need access to a `TxContext` can use `sui::tx_context::dummy` to create a test context instead. + +The first test works by creating an object to test, locking it and unlocking it – this should finish executing without aborting. +The last two lines exist to keep the Move compiler happy by cleaning up the test coin and test scenario objects, because values in Move are not implicitly cleaned up unless they have the `drop` ability. + +{@inject: examples/trading/contracts/escrow/sources/lock.move#fun=test_lock_unlock noComments} + +The other test is testing a failure scenario – that an abort happens. It creates two locked objects (this time the values are just `u64`s), and use the key from one to try and unlock the other, which should fail (specified using the `expected_failure` attribute). + +Unlike the previous test, the same clean up is not needed, because the code is expected to terminate. Instead, add another abort after the code that you expect to abort (making sure to use a different code for this second abort). + +{@inject: examples/trading/contracts/escrow/sources/lock.move#fun=test_lock_key_mismatch noComments} + +:::tip Additional resources + +- Concepts: [Test Scenario](../first-app/build-test.mdx#testing-a-package) +- [Drop ability](https://move-book.com/move-basics/drop-ability.html) defined in The Move Book. +- [Testing] Move code discussion in The Move Book. -## Prerequisites +::: + +:::checkpoint + +At this point, you have +- A Move package consisting of a manifest file (`Move.toml`) +- A `lock.move` file in your `sources` folder. + +From your `escrow` folder, run `sui move test` in your terminal or console. If successful, you get a response similar to the following that confirms the package builds and your tests pass: + +``` +INCLUDING DEPENDENCY Sui +INCLUDING DEPENDENCY MoveStdlib +BUILDING escrow +Running Move unit tests +[ PASS ] escrow::lock::test_lock_key_mismatch +[ PASS ] escrow::lock::test_lock_unlock +Test result: OK. Total tests: 2; passed: 2; failed: 0 +``` + +You might notice that the Move compiler creates a `build` subfolder inside `escrow` upon a successful build. This folder contains your package's compiled bytecode, code from your package's dependencies, and various other files necessary for the build. At this point, it's enough to just be aware of these files. You don't need to fully understand the contents in `build`. + +::: + +### The Escrow protocol {#escrow} + +Create a new file in your `escrow` folder titled `shared.move`. The code in this file creates the shared `Escrow` object and completes the trading logic. The complete source code for this file follows and the sections that come after detail its components. + +
+ +`shared.move` + +{@inject: examples/trading/contracts/escrow/sources/shared.move noComments} +
+ +Trading proceeds in three steps: + +1. The first party locks the object they want to trade – this is already handled by the `lock` module you wrote earlier. +1. The second party puts their object up for escrow and registers their interest in the first party's object. This is handled by a new module – `escrow`. +1. The first party completes the trade by providing their locked object and the key to unlock it. Assuming all checks pass, this transfers their object to the second party and makes the second party's object available to them. + +You can start by implementing steps two and three, by defining a new type to hold the escrowed object. It holds the `escrowed` object and an `id: UID` (because it's an object in its own right), but it also records the `sender` and intended `recipient` (to confirm they match when the trade happens), and it registers interest in the first party's object by recording the `ID` of the key that unlocks the `Locked` that contains the object. + +{@inject: examples/trading/contracts/escrow/sources/shared.move#struct=Escrow noComments singleSpace} + +You also need to create a function for creating the `Escrow` object. The object is shared because it needs to be accessed by the address that created it (in case the object needs to be returned) and by the intended recipient (to complete the swap). + +
+ +`create` function in `shared.move` + +{@inject: examples/trading/contracts/escrow/sources/shared.move#noemit noComments} +
+ +If the second party stops responding, the first party can unlock their object. You need to create a function so the second party can recover their object in the symmetric case as well. + +- It needs to check that the caller matches `sender`, because `Escrow` objects are shared and anybody can access them. +- It accepts the `Escrow` by value so that it can clean it up after extracting the escrowed object, reclaiming the storage rebate for the sender and cleaning up an unused object on chain. + +
+ +`return_to_sender` function in `shared.move` + +{@inject: examples/trading/contracts/escrow/sources/shared.move#fun=return_to_sender noComments} +
+ +Finally, you need to add a function to allow the first party to complete the trade. + +- This function also accepts the `Escrow` by value because it consumes it after the swap is complete. +- It checks that the sender of the transaction is the intended recipient (the first party), and that the ID of the key that they provided matches the key specified when the object was escrowed. This ensures no tampering occurs, because this key can be provided only if it had not been used to unlock the object, which proves the object has not left its `Locked` between the call to `create` and to `swap`. You can inspect the `lock` module to see that it cannot be modified while in there. +- The call to `unlock` further checks that the key matches the locked object that was provided. +- Instead of transferring the escrowed object to the recipient address, it is returned by the `swap` function. You can do this because you checked that the transaction sender is the recipient, and it makes this API more composable. Programmable transaction blocks (PTBs) provide the flexibility to decide whether to transfer the object as it is received or do something else with it. + +
+ +`swap` function in `shared.move` + +{@inject: examples/trading/contracts/escrow/sources/shared.move#variable=EMismatchedSenderRecipient,EMismatchedExchangeObject singleSpace noComments} +{@inject: examples/trading/contracts/escrow/sources/shared.move#fun=swap noComments noTitle} +
+ + +:::tip Additional resources + +- [Full source code](https://github.com/MystenLabs/sui/blob/705ee1ed3ce8cfadc4597c6facb6769d7dfb5896/examples/trading/contracts/escrow/sources/shared.move) +- Concepts: [Shared Objects](../../../concepts/object-ownership/shared) +- Concepts: [Shared Object Deletion](https://blog.sui.io/ephemeral-shared-objects/) +- Concepts: [PTBs](../../../concepts/transactions/prog-txn-blocks) + +::: + +### Testing + +Tests for the `escrow` module are more involved than for `lock` – as they take advantage of `test_scenario`'s ability to simulate multiple transactions from different senders, and interact with shared objects. + +The guide focuses on the test for a successful swap, but you can find a link to all the tests later on. + +As with the lock test, start by creating a function to mint a test coin. You also create some constants to represent our transaction senders, `ALICE`, `BOB`, and `DIANE`. + +{@inject: examples/trading/contracts/escrow/sources/shared.move#fun=test_coin noComments} + +The test body starts with a call to `test_scenario::begin` and ends with a call to `test_scenario::end`. It doesn't matter which address you pass to `begin`, because you pick one of `ALICE` or `BOB` at the start of each new transaction you write, so set it to `@0x0`: + +{@inject: examples/trading/contracts/escrow/sources/shared.move#test} + +The first transaction is from `BOB` who creates a coin and locks it. You must remember the ID of the coin and the ID of the key, which you will need later, and then you transfer the locked object and the key itself to `BOB`, because this is what would happen in a real transaction: When simulating transactions in a test, you should only keep around primitive values, not whole objects, which would need to be written to chain between transactions. + +Write these transactions inside the `test_successful_swap` function, between the call to `begin` and `end`. + +{@inject: examples/trading/contracts/escrow/sources/shared.move#variable=i2 noComments} + +Next, `ALICE` comes along and sets up the `Escrow`, which locks their coin. They register their interest for `BOB'`s coin by referencing `BOB`'s key's ID (`ik2`): + +{@inject: examples/trading/contracts/escrow/sources/shared.move#variable=i1 noComments} + +Finally, `BOB` completes the trade by calling `swap`. The `take_shared` function is used to simulate accepting a shared input. It uses type inference to know that the object must be an `Escrow`, and finds the last object of this type that was shared (by `ALICE` in the previous transaction). Similarly, use `take_from_sender` to simulate accepting owned inputs (in this case, `BOB`'s lock and key). The coin returned by `swap` is transferred back to `BOB`, as if it was called as part of a PTB, followed by a transfer command. + +{@inject: examples/trading/contracts/escrow/sources/shared.move#bob noComments} + +The rest of the test is designed to check that `ALICE` has `BOB`'s coin and vice versa. It starts by calling `next_tx` to make sure the effects of the previous transaction have been committed, before running the necessary checks. + +{@inject: examples/trading/contracts/escrow/sources/shared.move#finish noComments} + +:::tip Additional resources + +- Guides: [Test Scenario](../../../guides/developer/first-app/build-test#testing-a-package) + +::: + +### Observability + +The `escrow` Move package is now functional: You could publish it on chain and perform trustless swaps by creating transactions. Creating those transactions requires knowing the IDs of `Locked`, `Key`, and `Escrow` objects. + +`Locked` and `Key` objects are typically owned by the transaction sender, and so can be queried through the Sui RPC, but `Escrow` objects are shared, and it is useful to be able to query them by their sender and recipient (so that users can see the trades they have offered and received). + +Querying `Escrow` objects by their sender or recipient requires custom indexing, and to make it easy for the indexer to spot relevant transactions, add the following **events** to `escrow.move`: + +{@inject: examples/trading/contracts/escrow/sources/shared.move#struct=EscrowCreated,EscrowSwapped,EscrowCancelled noComments} + +Functions responsible for various aspects of the escrow's lifecycle emit these events. The custom indexer can then subscribe to transactions that emit these events and process only those, rather than the entire chain state: + +
+ +`emit` events included in functions from `shared.move` + +{@inject: examples/trading/contracts/escrow/sources/shared.move#use=sui::event} +{@inject: examples/trading/contracts/escrow/sources/shared.move#fun=create,swap,return_to_sender noTitle noComments} +
+ +:::tip Additional resources + +- Concepts: [Events in The Move Book](https://move-book.com/programmability/events.html) +- Guide: [Using Events](../sui-101/using-events.mdx) + +::: + +:::checkpoint + +You now have `shared.move` and `locked.move` files in your `sources` folder. From the parent `escrow` folder, run `sui move test` in your terminal or console. If successful, you get a response similar to the following that confirms the package builds and your tests pass: + +``` +INCLUDING DEPENDENCY Sui +INCLUDING DEPENDENCY MoveStdlib +BUILDING escrow +Running Move unit tests +[ PASS ] escrow::lock::test_lock_key_mismatch +[ PASS ] escrow::shared::test_mismatch_object +[ PASS ] escrow::lock::test_lock_unlock +[ PASS ] escrow::shared::test_mismatch_sender +[ PASS ] escrow::shared::test_object_tamper +[ PASS ] escrow::shared::test_return_to_sender +[ PASS ] escrow::shared::test_return_to_sender_failed_swap +[ PASS ] escrow::shared::test_successful_swap +Test result: OK. Total tests: 8; passed: 8; failed: 0 +``` + +::: + +### Next steps + +Well done. You have written the Move package! 🚀 + +To turn this into a complete dApp, you need to create a frontend. However, for the frontend to be updated, it has to listen to the blockchain as escrows are made and swaps are fulfilled. + +To achieve this, in the next step you create an indexing service. + +## Backend indexer {#backend} + +With the contract adapted to emit events, you can now write an indexer that keeps track of all active `Escrow` objects and exposes an API for querying objects by sender or recipient. + +The indexer is backed by a Prisma DB with the following schema: + +
+ +`schema.prisma` + +{@inject: examples/trading/api/prisma/schema.prisma} +
+ +The core of the indexer is an event loop, initialized in a function called `setupListeners`. + +{@inject: examples/trading/api/indexer.ts} + +The indexer queries events related to the `escrow` module, using a `queryEvent` filter, and keeps track of a cursor representing the latest event it has processed so it can resume indexing from the right place even if it is restarted. The filter is looking for any events whose type is from the `escrow` module of the Move package (see the `event-indexer.ts` code that follows). + +The core event job works by polling: It queries RPC for events following its latest cursor and sends them to a callback for processing. If it detects more than one page of new events, it immediately requests the next page. Otherwise, the job waits for the next polling interval before checking again. + +
+ +`event-indexer.ts` + +{@inject: examples/trading/api/indexer/event-indexer.ts} +
+ +The callback is responsible for reading the event and updating the database accordingly. For demo purposes, SQLite is being used, and so you need to issue a separate `UPSERT` to the database for each escrowed object. In a production setting, however, you would want to batch requests to the database to optimize data flow. + +
+ +`escrow-handler.ts` + +{@inject: examples/trading/api/indexer/escrow-handler.ts} +
+ +:::tip Additional resources + +- [Full source code](https://github.com/MystenLabs/sui/tree/705ee1ed3ce8cfadc4597c6facb6769d7dfb5896/examples/trading/api) +- Reference: [JSON-RPC](https://docs.sui.io/sui-api-ref) + +::: + + +### API service + +The data that the indexer captures can then be served over an API, so that a frontend can read it. Follow the next section to implement the API in TypeScript, to run on Node, using Express. + +#### Query parameters + +You want your API to accept the query string in the URL as the parameters for database `WHERE` query. Hence, you want a utility that can extract and parse the URL query string into valid query parameters for Prisma. With the `parseWhereStatement()` function, the callers filter the set of keys from the URL query string and transforms those corresponding key-value pairs into the correct format for Prisma. + +
+ +`parseWhereStatement` in `api-queries.ts` + +{@inject: examples/trading/api/utils/api-queries.ts#enum=WhereParamTypes} +{@inject: examples/trading/api/utils/api-queries.ts#type=WhereParam noTitle} +{@inject: examples/trading/api/utils/api-queries.ts#variable=parseWhereStatement noTitle} +
+ + +#### Query pagination + +Pagination is another crucial part to ensure your API returns sufficient and/or ordered chunk of information instead of all the data that might be the vector for a DDOS attack. Similar to **WHERE parameters**, define a set of keys in the URL query string to be accepted as valid pagination parameters. The `parsePaginationForQuery()` utility function helps to achieve this by filtering the pre-determined keys `sort`, `limit`, `cursor` and parsing corresponding key-value pairs into `ApiPagination` that Prisma can consume. + +In this example, the `id` field of the model in the database as the cursor that allows clients to continue subsequent queries with the next page. + +
+ +`parsePaginationForQuery` in `api-queries.ts` + +{@inject: examples/trading/api/utils/api-queries.ts#type=ApiPagination} +{@inject: examples/trading/api/utils/api-queries.ts#variable=parsePaginationForQuery noTitle} +
+ +#### API endpoints + +All the endpoints are defined in `server.ts`, particularly, there are two endpoints: + +- `/locked` to query `Locked` objects. +- `/escrows` to query `Escrow` objects. + +You define a list of valid query keys, such as `deleted`, `creator`, `keyId`, and `objectId` for `Locked` data and `cancelled`, `swapped`, `recipient`, and `sender` for `Escrow` data. Pass the URL query string into the pre-defined utilities to output the correct parameters that Prisma can use. + +
+ +`server.ts` + +{@inject: examples/trading/api/server.ts} +
+ +### Deployment {#deployment} + +Now that you have an indexer and an API service, you can deploy your move package and start the indexer and API service. + +1. Install dependencies by running `pnpm install --ignore-workspace` or `yarn install --ignore-workspace`. + +2. Setup the database by running `pnpm db:setup:dev` or `yarn db:setup:dev`. + +3. Deploy the Sui package + +
+ + Deployment instructions + + {@include: ../../../snippets/initialize-sui-client-cli.mdx} + + Next, configure the Sui CLI to use `testnet` as the active environment. + + Use the following command to list your available environments: + + ```bash + sui client envs + ``` + + If you haven't already set up a `testnet` environment, do so by running the following command in a terminal or console: + + ```bash + sui client new-env --alias testnet --rpc https://fullnode.testnet.sui.io:443 + ``` + + Run the following command to activate the `testnet` environment: + + ```bash + sui client switch --env testnet + ``` + + Before being able to publish your package to Testnet, you need Testnet SUI tokens. To get some, run the following command: + + ```bash + sui client faucet + ``` + + For other ways to get SUI in your Testnet account, see [Get SUI Tokens](/guides/developer/getting-started/get-coins). + + Now that you have an account with some Testnet SUI, you can deploy your contracts. + + There are some helper functions to publish the smart contracts so you can create some demo data (for Testnet). The helper function to publish the smart contrqcts expects built smart contracts in both the `escrow` and `demo` directories. Run `sui move build` in both directories, if necessary. Be sure to update the Sui dependency in the manifest to point to the correct source based on your environment. + + To publish the smart contracts and produce demo data: + + 1. Publish the smart contracts by running the following command from your `api` folder: + + ``` + npx ts-node helpers/publish-contracts.ts + ``` + + If successful, `demo-contract.json` and `escrow-contract.json` are created in the backend root directory. These files contain the contract addresses and are used by the backend and frontend to interact with the contracts. + + 2. Produce demo non-locked and locked objects + + ``` + npx ts-node helpers/create-demo-data.ts + ``` + + 3. Produce demo escrows + + ``` + npx ts-node helpers/create-demo-escrows.ts + ``` + + If you want to reset the database (for a clean demo, for example), run `pnpm db:reset:dev && pnpm db:setup:dev` or `yarn db:reset:dev && yarn db:setup:dev`. +
+ +4. Run both the API and the indexer by running `pnpm dev` or `yarn dev`. + +5. Visit [http://localhost:3000/escrows](http://localhost:3000/escrows) or [http://localhost:3000/locked](http://localhost:3000/locked) + +:::checkpoint + +You should now have an indexer running. +- If you visit `localhost:3000`, you get a message that the service is running: `{"message":"🚀 API is functional 🚀"}`. +- If you visit `localhost:3000/escrows`, you see the demo escrow data the helper scripts created for you. Likewise, visiting `http://localhost:3000/locked` displays the raw JSON the script created for demo objects. + +::: + + +### Next steps + +With the code successfully deployed on Testnet, you can now [create a frontend](#frontend) to display the trading data and to allow users to interact with the Move modules. + +## Frontend {#frontend} + +In this final part of the app example, you build a frontend (UI) that allows end users to discover trades and interact with listed escrows. + +### Prerequisites + +{@include: ../../../snippets/app-examples-swap-source.mdx} Before getting started, make sure you have: -- [Installed the latest version of Sui](../getting-started/sui-install.mdx). -- [Configured a valid network environment](../../../references/cli/client.mdx#set-current-environment), as you will be deploying the module on Testnet. +- [Completed the smart contracts](#smart-contracts) and understand their design. +- [Implemented the backend](#backend) to learn how to index on-chain data and expose it through an API. +- [Deployed your smart contracts and started the backend indexer](#deployment). +- Installed [`pnpm`](https://pnpm.io/installation) or [`yarn`](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable) to use as the package manager. + +:::tip Additional resources + +- Tooling: [Sui Typescript SDK](https://sdk.mystenlabs.com/typescript). For basic usage on how to interact with Sui with TypeScript. +- Tooling: [Sui dApp Kit](https://sdk.mystenlabs.com/dapp-kit). To learn basic building blocks for developing a dApp in the Sui ecosystem with React.js. +- Tooling: [`@mysten/dapp`](https://sdk.mystenlabs.com/dapp-kit/create-dapp). This is used within this project to quickly scaffold a React-based Sui dApp. + +::: + +### Overview + +The UI design consists of three parts: + +- A header containing the button allowing users to connect their wallet and navigate to other pages. +- A place for users to manage their owned objects to be ready for escrow trading called `Manage Objects`. +- A place for users to discover, create, and execute trades called `Escrows`. + +### Scaffold a new app + +The first step is to set up the client app. Run the following command to scaffold a new app from your `frontend` folder. + + + + ```bash + pnpm create @mysten/dapp --template react-client-dapp + ``` + + + ```bash + yarn create @mysten/dapp --template react-client-dapp + ``` + + + +When asked for a name for your dApp, provide one of your liking. The dApp scaffold gets created in a new directory with the name you provide. This is convenient to keep your working code separate from the example source code that might already populate this folder. The codeblocks that follow point to the code in the default example location. Be aware the path to your own code includes the dApp name you provide. + +### Setting up import aliases + +First, set up import aliases to make the code more readable and maintainable. This allows you to import files using `@/` instead of relative paths. + +
+ + Replace the content of `tsconfig.json` with the following: + + {@inject: examples/trading/frontend/tsconfig.json} +
+ +The paths option under `compilerOptions` is what defines the aliasing for TypeScript. Here, the alias `@/*` is mapped to the `./src/*` directory, meaning that any time you use `@/`, TypeScript resolves it as a reference to the `src` folder. This setup reduces the need for lengthy relative paths when importing files in your project. + +
+ + Replace the content of `vite.config.ts` with the following: + + {@inject: examples/trading/frontend/vite.config.ts} +
+ +Vite also needs to be aware of the aliasing to resolve imports correctly during the build process. In the `resolve.alias` configuration of `vite.config.ts`, we map the alias `@` to the `/src` directory. + +### Adding Tailwind CSS + +To streamline the styling process and keep the codebase clean and maintainable, this guide uses Tailwind CSS, which provides utility-first CSS classes to rapidly build custom designs. Run the following command from the base of your dApp project to add Tailwind CSS and its dependencies: + + + + ```bash + pnpm add tailwindcss@latest postcss@latest autoprefixer@latest + ``` + + + ```bash + yarn add tailwindcss@latest postcss@latest autoprefixer@latest + ``` + + + +Next, generate the Tailwind CSS configuration file by running the following: + +```bash +npx tailwindcss init -p +``` + +
+ + Replace the content of `tailwind.config.js` with the following: + + {@inject: examples/trading/frontend/tailwind.config.js} +
+ +
+ + Add the `src/styles/` directory and add `base.css`: + + {@inject: examples/trading/frontend/src/styles/base.css} +
+ +### Connecting your deployed package + +First, deploy your package via the [scripts in the api directory](#deployment). + +
+ + Then, create a `src/constants.ts` file and fill it with the following: + + {@inject: examples/trading/frontend/src/constants.ts} +
+ +:::warning + +If you create a dApp using a project name so that your `src` files are in a subfolder of `frontend`, be sure to add another nesting level (`../`) to the import statements. + +::: + +### Add helper functions and UI components + +
+ + Create a `src/utils/` directory and add the following file: + + {@inject: examples/trading/frontend/src/utils/helpers.ts} +
+ +Create a `src/components/` directory and add the following components: + +
+ + `ExplorerLink.tsx` + + {@inject: examples/trading/frontend/src/components/ExplorerLink.tsx} +
+ +
+ + `InfiniteScrollArea.tsx` + + {@inject: examples/trading/frontend/src/components/InfiniteScrollArea.tsx} +
+ +
+ + `Loading.tsx` + + {@inject: examples/trading/frontend/src/components/Loading.tsx} +
+ +
+ + `SuiObjectDisplay.tsx` + + {@inject: examples/trading/frontend/src/components/SuiObjectDisplay.tsx} +
+ +Install the necessary dependencies: + + + + ```bash + pnpm add react-hot-toast + ``` + + + ```bash + yarn add react-hot-toast + ``` + + + + +### Set up routing {#routing} + +The imported template only has a single page. To add more pages, you need to set up routing. + +First, install the necessary dependencies: + + + + ```bash + pnpm add react-router-dom + ``` + + + ```bash + yarn add react-router-dom + ``` + + + +
+ + Then, create a `src/routes/` directory and add `index.tsx`. This file contains the routing configuration: + + {@inject: examples/trading/frontend/src/routes/index.tsx} +
+ +Add the following respective files to the `src/routes/` directory: + +
+ + `root.tsx`. This file contains the root component that is rendered on every page: + + {@inject: examples/trading/frontend/src/routes/root.tsx} +
+ +
+ + `LockedDashboard.tsx`. This file contains the component for the `Manage Objects` page. + +```tsx +export function LockedDashboard() { + return ( +
+

Locked Dashboard

+
+ ) +} +``` +
+ +
+ + `EscrowDashboard.tsx`. This file contains the component for the `Escrows` page. + +```tsx +export function EscrowDashboard() { + return ( +
+

Escrow Dashboard

+
+ ) +} +``` +
+ +
+ + Update `src/main.tsx` by replacing the `App` component with the `RouterProvider` and replace `"dark"` with `"light"` in the `Theme` component: + + {@inject: examples/trading/frontend/src/main.tsx} +
+ +Note that `dApp Kit` provides a set of hooks for making query and mutation calls to the Sui blockchain. These hooks are thin wrappers around query and mutation hooks from `@tanstack/react-query`. + +:::tip Additional resources + +- Docs: [React Router](https://reactrouter.com/en/main). Used to navigate between different routes in the website. +- Docs: [TanStack Query](https://tanstack.com/query/latest/docs/framework/react/overview). + +::: + +
+ + Create `src/components/Header.tsx`. This file contains the navigation links and the connect wallet button: + +```tsx +import { ConnectButton } from "@mysten/dapp-kit"; +import { SizeIcon } from "@radix-ui/react-icons"; +import { Box, Container, Flex, Heading } from "@radix-ui/themes"; +import { NavLink } from "react-router-dom"; + +const menu = [ + { + title: "Escrows", + link: "/escrows", + }, + { + title: "Manage Objects", + link: "/locked", + }, +]; + +export function Header() { + return ( + + + + + + Trading Demo + + + + + {menu.map((item) => ( + + `cursor-pointer flex items-center gap-2 ${ + isPending + ? "pending" + : isActive + ? "font-bold text-blue-600" + : "" + }` + } + > + {item.title} + + ))} + + + + + + + + ); +} +``` +
+ +The dApp Kit comes with a pre-built React.js component called `ConnectButton` displaying a button to connect and disconnect a wallet. The connecting and disconnecting wallet logic is handled seamlessly so you don't need to worry about repeating yourself doing the same logic all over again. + +:::checkpoint + +At this point, you have a basic routing setup. Run your app and ensure you can: + +- Navigate between the `Manage Objects` and `Escrows` pages. +- Connect and disconnect your wallet. + +Note, the styles should be applied. The `Header` component should look like this: + +![Header component](./images/styles.png) + +::: + +### Type definitions + +
+ + All the type definitions are in `src/types/types.ts`. Create this file and add the following: + + {@inject: examples/trading/frontend/src/types/types.ts} +
+ +`ApiLockedObject` and `ApiEscrowObject` represent the `Locked` and `Escrow` indexed data model the indexing and API service return. + +`EscrowListingQuery` and `LockedListingQuery` are the query parameters model to provide to the API service to fetch from the endpoints `/escrow` and `/locked` accordingly. + +### Display owned objects + +Now, display the objects owned by the connected wallet address. This is the `Manage Objects` page. + +
+ + First add this file `src/components/locked/LockOwnedObjects.tsx`: + +```tsx +import { useCurrentAccount, useSuiClientInfiniteQuery } from "@mysten/dapp-kit"; +import { SuiObjectDisplay } from "@/components/SuiObjectDisplay"; +import { InfiniteScrollArea } from "@/components/InfiniteScrollArea"; + +/** + * A component that fetches all the objects owned by the connected wallet address + * and allows the user to lock them, so they can be used in escrow. + */ +export function LockOwnedObjects() { + const account = useCurrentAccount(); + + const { data, fetchNextPage, isFetchingNextPage, hasNextPage, refetch } = + useSuiClientInfiniteQuery( + "getOwnedObjects", + { + owner: account?.address!, + options: { + showDisplay: true, + showType: true, + }, + }, + { + enabled: !!account, + select: (data) => + data.pages + .flatMap((page) => page.data) + .filter( + // we're filtering out objects that don't have Display or image_url + // for demo purposes. The Escrow contract works with all objects. + (x) => !!x.data?.display && !!x.data?.display?.data?.image_url, + ), + }, + ); + + return ( + fetchNextPage()} + hasNextPage={hasNextPage} + loading={isFetchingNextPage} + > + {data?.map((obj) => ( + + + ))} + + ); +} +``` +
+ +Fetch the owned objects directly from Sui blockchain using the `useSuiClientInfiniteQuery()` hook from `dApp Kit`. This hook is a thin wrapper around Sui blockchain RPC calls, reference the documentation to learn more about these [RPC hooks](https://sdk.mystenlabs.com/dapp-kit/rpc-hooks). Basically, supply the RPC endpoint you want to execute, in this case it's the [`getOwnedObjects` endpoint](https://docs.sui.io/sui-api-ref#suix_getownedobjects). Supply the connected wallet account as the `owner`. The returned data is stored inside the cache at query key `getOwnedObjects`. In a future step you invalidate this cache after a mutation succeeds, so the data will be re-fetched automatically. + + +
+ + Next, update `src/routes/LockedDashboard.tsx` to include the `LockOwnedObjects` component: + +```tsx +import { useState } from "react"; +import { Tabs } from "@radix-ui/themes"; +import { LockOwnedObjects } from "@/components/locked/LockOwnedObjects"; + +export function LockedDashboard() { + const tabs = [ + { + name: "Lock Owned objects", + component: () => , + }, + ]; + + const [tab, setTab] = useState(tabs[0].name); + + return ( + + + {tabs.map((tab, index) => { + return ( + + {tab.name} + + ); + })} + + {tabs.map((tab, index) => { + return ( + + {tab.component()} + + ); + })} + + ); +} +``` +
+ +:::checkpoint + +Run your app and ensure you can: + +- View the owned objects of the connected wallet account. + +If you don't see any objects, you might need to create some demo data or connect your wallet. You can mint objects after completing the next steps. + +![Owned objects](./images/trustless-objects.png) + +::: + +### Execute transaction hook {#execute-transaction-hook} + +In the frontend, you might need to execute a transaction block in multiple places, hence it's better to extract the transaction execution logic and reuse it everywhere. Let's create and examine the execute transaction hook. + + +
+ + Create `src/hooks/useTransactionExecution.ts`: + + {@inject: examples/trading/frontend/src/hooks/useTransactionExecution.ts} +
+ +A `Transaction` is the input, sign it with the current connected wallet account, execute the transaction block, return the execution result, and finally display a basic toast message to indicate whether the transaction is successful or not. + +Use the `useSuiClient()` hook from `dApp Kit` to retrieve the Sui client instance configured in `src/main.tsx`. The `useSignTransaction()` function is another hook from `dApp kit` that helps to sign the transaction block using the currently connected wallet. It displays the UI for users to review and sign their transactions with their selected wallet. To execute a transaction block, use the `executeTransaction()` on the Sui client instance of the Sui TypeScript SDK. + +### Generate demo data + +:::info + +The full source code of the demo bear smart contract is available at [Trading Contracts Demo directory](https://github.com/MystenLabs/sui/tree/main/examples/trading/contracts/demo) + +::: + +You need a utility function to create a dummy object representing a real world asset so you can use it to test and demonstrate escrow users flow on the UI directly. + +
+ + Create `src/mutations/demo.ts`: + + {@inject: examples/trading/frontend/src/mutations/demo.ts} +
+ +As previously mentioned, this example uses `@tanstack/react-query` to query, cache, and mutate server state. Server state is data only available on remote servers, and the only way to retrieve or update this data is by interacting with these remote servers. In this case, it could be from an API or directly from Sui blockchain RPC. + +When you execute a transaction call to mutate data on the Sui blockchain, use the `useMutation()` hook. The `useMutation()` hook accepts several inputs, however, you only need two of them for this example. The first parameter, `mutationFn`, accepts the function to execute the main mutating logic, while the second parameter, `onSuccess`, is a callback that runs when the mutating logic succeeds. + +The main mutating logic includes executing a Move call of a package named `demo_bear::new` to create a dummy bear object and transferring it to the connected wallet account, all within the same `Transaction`. This example reuses the `executeTransaction()` hook from the [Execute Transaction Hook](#execute-transaction-hook) step to execute the transaction. + +Another benefit of wrapping the main mutating logic inside `useMutation()` is that you can access and manipulate the cache storing server state. This example fetches the cache from remote servers by using query call in an appropriate callback. In this case, it is the `onSuccess` callback. When the transaction succeeds, invalidate the cache data at the cache key called `getOwnedObjects`, then `@tanstack/react-query` handles the re-fetching mechanism for the invalidated data automatically. Do this by using `invalidateQueries()` on the `@tanstack/react-query` configured client instance retrieved by `useQueryClient()` hook in the [Set up Routing](#routing) step. + +Now the logic to create a dummy bear object exists. You just need to attach it into the button in the header. + +
+ + `Header.tsx` + + {@inject: examples/trading/frontend/src/components/Header.tsx} +
+ +:::checkpoint + +Run your app and ensure you can: + +- Mint a demo bear object and view it in the `Manage Objects` tab. + +![New bear](./images/trustless-new-bear.png) + +::: + +### Locking owned objects + +To lock the object, execute the `lock` Move function identified by `{PACKAGE_ID}::lock::lock`. The implementation is similar to what's in previous mutation functions, use `useMutation()` from `@tanstack/react-query` to wrap the main logic inside it. The lock function requires an object to be locked and its type because our smart contract `lock` function is generic and requires type parameters. After creating a `Locked` object and its `Key` object, transfer them to the connected wallet account within the same transaction block. + +It's beneficial to extract logic of locking owned objects into a separated mutating function to enhance discoverability and encapsulation. + +
+ + Create `src/mutations/locked.ts`: + +```tsx +import { CONSTANTS } from "@/constants"; +import { useTransactionExecution } from "@/hooks/useTransactionExecution"; +import { useCurrentAccount } from "@mysten/dapp-kit"; +import { SuiObjectData } from "@mysten/sui/client"; +import { Transaction } from "@mysten/sui/transactions"; +import { useMutation } from "@tanstack/react-query"; +/** + * Builds and executes the PTB to lock an object. + */ +export function useLockObjectMutation() { + const account = useCurrentAccount(); + const executeTransaction = useTransactionExecution(); + + return useMutation({ + mutationFn: async ({ object }: { object: SuiObjectData }) => { + if (!account?.address) + throw new Error("You need to connect your wallet!"); + const txb = new Transaction(); + + const [locked, key] = txb.moveCall({ + target: `${CONSTANTS.escrowContract.packageId}::lock::lock`, + arguments: [txb.object(object.objectId)], + typeArguments: [object.type!], + }); + + txb.transferObjects([locked, key], txb.pure.address(account.address)); + + return executeTransaction(txb); + }, + }); +} +``` +
+ +Update `src/components/locked/LockOwnedObjects.tsx` to include the `useLockObjectMutation` hook: + +
+ + `LockOwnedObjects.tsx` + + {@inject: examples/trading/frontend/src/components/locked/LockOwnedObjects.tsx} +
+ +:::checkpoint + +Run your app and ensure you can: + +- Lock an owned object. + +The object should disappear from the list of owned objects. You view and unlock locked objects in later steps. + +![Lock bear](./images/trustless-lock-bear.png) + +::: + +### Display owned locked objects + +Let's take a look at the **My Locked Objects** tab by examining `src/components/locked/OwnedLockedList.tsx`. Focus on the logic on how to retrieve this list. + +
+ + `OwnedLockedList.tsx` + + {@inject: examples/trading/frontend/src/components/locked/OwnedLockedList.tsx} +
+ +This instance of `useSuiClientInfiniteQuery()` is similar to the one in the `LockOwnedObjects` component. The difference is that it fetches the locked objects instead of the owned objects. The `Locked` object is a struct type in the smart contract, so you need to supply the struct type to the query call as a `filter`. The struct type is usually identified by the format of `{PACKAGE_ID}::{MODULE_NAME}::{STRUCT_TYPE}`. + +##### `LockedObject` and `Locked` component + +The `` (`src/components/locked/LockedObject.tsx`) component is mainly responsible for mapping an on-chain `SuiObjectData` `Locked` object to its corresponding `ApiLockedObject`, which is finally delegated to the `` component for rendering. The `` fetches the locked item object ID if the prop `itemId` is not supplied by using `dApp Kit` `useSuiClientQuery()` hook to call the `getDynamicFieldObject` RPC endpoint. Recalling that in this smart contract, the locked item is put into a dynamic object field. + +
+ + `LockedObject.tsx` + + {@inject: examples/trading/frontend/src/components/locked/LockedObject.tsx} +
+ +The `` (`src/components/locked/partials/Locked.tsx`) component is mainly responsible for rendering the `ApiLockedObject`. Later on, it will also consist of several on-chain interactions: unlock the locked objects and create an escrow out of the locked object. + +
+ + `Locked.tsx` + +```tsx +import { useCurrentAccount, useSuiClientQuery } from "@mysten/dapp-kit"; +import { SuiObjectDisplay } from "@/components/SuiObjectDisplay"; +import { ExplorerLink } from "../../ExplorerLink"; +import { ApiLockedObject } from "@/types/types"; + +/** + * Prefer to use the `Locked` component only through `LockedObject`. + * + * This can also render data directly from the API, but we prefer + * to also validate ownership from on-chain state (as objects are transferrable) + * and the API cannot track all the ownership changes. + */ +export function Locked({ + locked, + hideControls, +}: { + locked: ApiLockedObject; + hideControls?: boolean; +}) { + const account = useCurrentAccount(); + + const suiObject = useSuiClientQuery( + "getObject", + { + id: locked.itemId, + options: { + showDisplay: true, + showType: true, + showOwner: true, + }, + }, + { + select: (data) => data.data, + }, + ); + + const getLabel = () => { + if (locked.deleted) return "Deleted"; + if (hideControls) { + if (locked.creator === account?.address) return "You offer this"; + return "You'll receive this if accepted"; + } + return undefined; + }; + + const getLabelClasses = () => { + if (locked.deleted) + return "bg-red-50 rounded px-3 py-1 text-sm text-red-500"; + if (hideControls) { + if (!!locked.creator && locked.creator === account?.address) + return "bg-blue-50 rounded px-3 py-1 text-sm text-blue-500"; + return "bg-green-50 rounded px-3 py-1 text-sm text-green-700"; + } + return undefined; + }; + + return ( + +
+ { +

+ +

+ } +
+
+ ); +} +``` +
+ +Update `src/routes/LockedDashboard.tsx` to include the `OwnedLockedList` component: + +
+ + `LockedDashboard.tsx` + + {@inject: examples/trading/frontend/src/routes/LockedDashboard.tsx} +
+ +:::checkpoint + +Run your app and ensure you can: + +- View the locked objects of the connected wallet account. + +![My locked bears](./images/trustless-my-locked.png) + +::: + +### Unlocking owned objects + +To unlock the object, execute the `unlock` Move function identified by `{PACKAGE_ID}::lock::unlock`. Call the `unlock` function supplying the `Locked` object, its corresponding `Key`, the struct type of the original object, and transfer the unlocked object to the current connected wallet account. Also, implement the `onSuccess` callback to invalidate the cache data at query key `locked` after one second to force `react-query` to re-fetch the data at corresponding query key automatically. + +Unlocking owned objects is another crucial and complex on-chain action in this application. Hence, it's beneficial to extract its logic into separated mutating functions to enhance discoverability and encapsulation. + +
+ + `src/mutations/locked.ts` + + {@inject: examples/trading/frontend/src/mutations/locked.ts} +
+ +Update `src/components/locked/partials/Locked.tsx` to include the `useUnlockObjectMutation` hook: + +
+ + `Locked.tsx` + +```tsx +import { useCurrentAccount, useSuiClientQuery } from "@mysten/dapp-kit"; +import { SuiObjectDisplay } from "@/components/SuiObjectDisplay"; +import { Button } from "@radix-ui/themes"; +import { + ArrowDownIcon, + ArrowUpIcon, + LockOpen1Icon, +} from "@radix-ui/react-icons"; +import { ExplorerLink } from "../../ExplorerLink"; +import { useState } from "react"; +import { ApiLockedObject } from "@/types/types"; +import { useUnlockMutation } from "@/mutations/locked"; + +/** + * Prefer to use the `Locked` component only through `LockedObject`. + * + * This can also render data directly from the API, but we prefer + * to also validate ownership from on-chain state (as objects are transferrable) + * and the API cannot track all the ownership changes. + */ +export function Locked({ + locked, + hideControls, +}: { + locked: ApiLockedObject; + hideControls?: boolean; +}) { + const [isToggled, setIsToggled] = useState(false); + const account = useCurrentAccount(); + const { mutate: unlockMutation, isPending } = useUnlockMutation(); + + const suiObject = useSuiClientQuery( + "getObject", + { + id: locked.itemId, + options: { + showDisplay: true, + showType: true, + showOwner: true, + }, + }, + { + select: (data) => data.data, + }, + ); + + const isOwner = () => { + return !!locked.creator && account?.address === locked.creator; + }; + + const getLabel = () => { + if (locked.deleted) return "Deleted"; + if (hideControls) { + if (locked.creator === account?.address) return "You offer this"; + return "You'll receive this if accepted"; + } + return undefined; + }; + + const getLabelClasses = () => { + if (locked.deleted) + return "bg-red-50 rounded px-3 py-1 text-sm text-red-500"; + if (hideControls) { + if (!!locked.creator && locked.creator === account?.address) + return "bg-blue-50 rounded px-3 py-1 text-sm text-blue-500"; + return "bg-green-50 rounded px-3 py-1 text-sm text-green-700"; + } + return undefined; + }; + + return ( + +
+ { +

+ +

+ } + {!hideControls && isOwner() && ( + + )} +
+
+ ); +} +``` +
+ +:::checkpoint + +Run your app and ensure you can: + +- Unlock a locked object. + +![Unlock my locked bears](./images/trustless-unlock-bear.png) + +::: + +### Display locked objects to escrow + +Update `src/routes/EscrowDashboard.tsx` to include the `LockedList` component: + +
+ + `EscrowDashboard.tsx` + +```tsx +import { useState } from "react"; +import { Tabs, Tooltip } from "@radix-ui/themes"; +import { LockedList } from "../components/locked/ApiLockedList"; +import { InfoCircledIcon } from "@radix-ui/react-icons"; + +export function EscrowDashboard() { + const tabs = [ + { + name: "Browse Locked Objects", + component: () => ( + + ), + tooltip: "Browse locked objects you can trade for.", + }, + ]; + + const [tab, setTab] = useState(tabs[0].name); + + return ( + + + {tabs.map((tab, index) => { + return ( + + {tab.name} + + + + + ); + })} + + {tabs.map((tab, index) => { + return ( + + {tab.component()} + + ); + })} + + ); +} +``` +
+ +Add `src/components/locked/ApiLockedList.tsx`: + +
+ + `ApiLockedList.tsx` + + {@inject: examples/trading/frontend/src/components/locked/ApiLockedList.tsx} +
+ +This hook fetches all the non-deleted system `Locked` objects from the API in a paginated fashion. Then, it proceeds into fetching the on-chain state, to ensure the latest state of the object is displayed. + +This component uses tanstack's `useInfiniteQuery` instead of `useSuiClientInfiniteQuery` since the data is being fetched from the example's API rather than Sui. + +
+ + Add `src/hooks/useGetLockedObject.ts` + + {@inject: examples/trading/frontend/src/hooks/useGetLockedObject.ts} +
+ +:::checkpoint + +Run your app and ensure you can: + +- View the locked objects in the `Browse Locked Objects` tab in the `Escrows` page. + +![Browse locked bears](./images/trustless-escrow-locked.png) + +::: + +### Create escrows + +To create escrows, include a mutating function through the `useCreateEscrowMutation` hook in `src/mutations/escrow.ts`. It accepts the escrowed item to be traded and the `ApiLockedObject` from another party as parameters. Then, call the `{PACKAGE_ID}::shared::create` Move function and provide the escrowed item, the key id of the locked object to exchange, and the recipient of the escrow (locked object's owner). + +
+ + `escrow.ts` + +```tsx +import { CONSTANTS } from "@/constants"; +import { useTransactionExecution } from "@/hooks/useTransactionExecution"; +import { ApiLockedObject } from "@/types/types"; +import { useCurrentAccount } from "@mysten/dapp-kit"; +import { SuiObjectData } from "@mysten/sui/client"; +import { Transaction } from "@mysten/sui/transactions"; +import { useMutation } from "@tanstack/react-query"; + +/** + * Builds and executes the PTB to create an escrow. + */ +export function useCreateEscrowMutation() { + const currentAccount = useCurrentAccount(); + const executeTransaction = useTransactionExecution(); + + return useMutation({ + mutationFn: async ({ + object, + locked, + }: { + object: SuiObjectData; + locked: ApiLockedObject; + }) => { + if (!currentAccount?.address) + throw new Error("You need to connect your wallet!"); + + const txb = new Transaction(); + txb.moveCall({ + target: `${CONSTANTS.escrowContract.packageId}::shared::create`, + arguments: [ + txb.object(object.objectId!), + txb.pure.id(locked.keyId), + txb.pure.address(locked.creator!), + ], + typeArguments: [object.type!], + }); + + return executeTransaction(txb); + }, + }); +} +``` +
+ +
+ + Update `src/components/locked/partials/Locked.tsx` to include the `useCreateEscrowMutation` hook + + {@inject: examples/trading/frontend/src/components/locked/partials/Locked.tsx} +
+ +
+ + Add `src/components/escrows/CreateEscrow.tsx` + + {@inject: examples/trading/frontend/src/components/escrows/CreateEscrow.tsx} +
+ +:::checkpoint + +Run your app and ensure you can: + +- Create an escrow. + +The object should disappear from the list of locked objects in the `Browse Locked Objects` tab in the `Escrows` page. You view and accept or cancel escrows in later steps. + +![Start escrow](./images/trustless-start-escrow.png) + +::: + +### Cancel escrows + +To cancel the escrow, create a mutation through the `useCancelEscrowMutation` hook in `src/mutations/escrow.ts`. The cancel function accepts the escrow `ApiEscrowObject` and its on-chain data. The `{PACKAGE_ID}::shared::return_to_sender` Move call is generic, thus it requires the type parameters of the escrowed object. Next, execute `{PACKAGE_ID}::shared::return_to_sender` and transfer the returned escrowed object to the creator of the escrow. + +
+ + `escrow.ts` + +```tsx +import { CONSTANTS, QueryKey } from "@/constants"; +import { useTransactionExecution } from "@/hooks/useTransactionExecution"; +import { ApiEscrowObject, ApiLockedObject } from "@/types/types"; +import { useCurrentAccount } from "@mysten/dapp-kit"; +import { SuiObjectData } from "@mysten/sui/client"; +import { Transaction } from "@mysten/sui/transactions"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +/** + * Builds and executes the PTB to create an escrow. + */ +export function useCreateEscrowMutation() { + const currentAccount = useCurrentAccount(); + const executeTransaction = useTransactionExecution(); + + return useMutation({ + mutationFn: async ({ + object, + locked, + }: { + object: SuiObjectData; + locked: ApiLockedObject; + }) => { + if (!currentAccount?.address) + throw new Error("You need to connect your wallet!"); + + const txb = new Transaction(); + txb.moveCall({ + target: `${CONSTANTS.escrowContract.packageId}::shared::create`, + arguments: [ + txb.object(object.objectId!), + txb.pure.id(locked.keyId), + txb.pure.address(locked.creator!), + ], + typeArguments: [object.type!], + }); + + return executeTransaction(txb); + }, + }); +} + +/** + * Builds and executes the PTB to cancel an escrow. + */ +export function useCancelEscrowMutation() { + const currentAccount = useCurrentAccount(); + const executeTransaction = useTransactionExecution(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({ + escrow, + suiObject, + }: { + escrow: ApiEscrowObject; + suiObject: SuiObjectData; + }) => { + if (!currentAccount?.address) + throw new Error("You need to connect your wallet!"); + const txb = new Transaction(); + + const item = txb.moveCall({ + target: `${CONSTANTS.escrowContract.packageId}::shared::return_to_sender`, + arguments: [txb.object(escrow.objectId)], + typeArguments: [suiObject?.type!], + }); + + txb.transferObjects([item], txb.pure.address(currentAccount?.address!)); + + return executeTransaction(txb); + }, + + onSuccess: () => { + setTimeout(() => { + queryClient.invalidateQueries({ queryKey: [QueryKey.Escrow] }); + }, 1_000); + }, + }); +} +``` +
+ +
+ + Add `src/components/escrows/Escrow.tsx` + +```tsx +import { useCurrentAccount, useSuiClientQuery } from "@mysten/dapp-kit"; +import { SuiObjectDisplay } from "@/components/SuiObjectDisplay"; +import { Button } from "@radix-ui/themes"; +import { + ArrowDownIcon, + ArrowUpIcon, + Cross1Icon, +} from "@radix-ui/react-icons"; +import { CONSTANTS, QueryKey } from "@/constants"; +import { ExplorerLink } from "../ExplorerLink"; +import { useQuery } from "@tanstack/react-query"; +import { useState } from "react"; +import { ApiEscrowObject } from "@/types/types"; +import { + useCancelEscrowMutation, +} from "@/mutations/escrow"; +import { useGetLockedObject } from "@/hooks/useGetLockedObject"; +import { LockedObject } from "../locked/LockedObject"; + +/** + * A component that displays an escrow and allows the user to accept or cancel it. + * Accepts an `escrow` object as returned from the API. + */ +export function Escrow({ escrow }: { escrow: ApiEscrowObject }) { + const account = useCurrentAccount(); + const [isToggled, setIsToggled] = useState(true); + const { mutate: cancelEscrowMutation, isPending: pendingCancellation } = + useCancelEscrowMutation(); + + const suiObject = useSuiClientQuery("getObject", { + id: escrow?.itemId, + options: { + showDisplay: true, + showType: true, + }, + }); + + const lockedData = useQuery({ + queryKey: [QueryKey.Locked, escrow.keyId], + queryFn: async () => { + const res = await fetch( + `${CONSTANTS.apiEndpoint}locked?keyId=${escrow.keyId}`, + ); + return res.json(); + }, + select: (data) => data.data[0], + enabled: !escrow.cancelled, + }); + + const { data: suiLockedObject } = useGetLockedObject({ + lockedId: lockedData.data?.objectId, + }); + + const getLabel = () => { + if (escrow.cancelled) return "Cancelled"; + if (escrow.swapped) return "Swapped"; + if (escrow.sender === account?.address) return "You offer this"; + if (escrow.recipient === account?.address) return "You'll receive this"; + return undefined; + }; + const getLabelClasses = () => { + if (escrow.cancelled) return "text-red-500"; + if (escrow.swapped) return "text-green-500"; + if (escrow.sender === account?.address) + return "bg-blue-50 rounded px-3 py-1 text-sm text-blue-500"; + if (escrow.recipient === account?.address) + return "bg-green-50 rounded px-3 py-1 text-sm text-green-700"; + return undefined; + }; + + return ( + +
+ { +

+ +

+ } + + {!escrow.cancelled && + !escrow.swapped && + escrow.sender === account?.address && ( + + )} + {isToggled && lockedData.data && ( +
+ {suiLockedObject?.data && ( + + )} + + {!lockedData.data.deleted && + escrow.recipient === account?.address && ( +
+

+ When accepting the exchange, the escrowed item is + transferred to you and your locked item is transferred + to the sender. +

+
+ )} + {lockedData.data.deleted && + !escrow.swapped && + escrow.recipient === account?.address && ( +
+

+ + The locked object has been deleted so you can't accept this + anymore. +

+
+ )} +
+ )} +
+
+ ); +} +``` +
+ +
+ + Add `src/components/escrows/EscrowList.tsx` + + {@inject: examples/trading/frontend/src/components/escrows/EscrowList.tsx} +
+ +
+ + Update `src/routes/EscrowDashboard.tsx` to include the `EscrowList` component + +```tsx +import { useState } from "react"; +import { Tabs, Tooltip } from "@radix-ui/themes"; +import { LockedList } from "../components/locked/ApiLockedList"; +import { EscrowList } from "../components/escrows/EscrowList"; +import { InfoCircledIcon } from "@radix-ui/react-icons"; +import { useCurrentAccount } from "@mysten/dapp-kit"; + +export function EscrowDashboard() { + const account = useCurrentAccount(); + const tabs = [ + { + name: "Browse Locked Objects", + component: () => ( + + ), + tooltip: "Browse locked objects you can trade for.", + }, + { + name: "My Pending Requests", + component: () => ( + + ), + tooltip: "Escrows you have initiated for third party locked objects.", + }, + ]; + + const [tab, setTab] = useState(tabs[0].name); + + return ( + + + {tabs.map((tab, index) => { + return ( + + {tab.name} + + + + + ); + })} + + {tabs.map((tab, index) => { + return ( + + {tab.component()} + + ); + })} + + ); +} +``` +
+ +:::checkpoint + +Run your app and ensure you can: + +- View the escrows in the `My Pending Requests` tab in the `Escrows` page. +- Cancel an escrow that you requested. + +![Cancel escrow](./images/trustless-cancel-escrow.png) + +::: + +### Accept escrows + +To accept the escrow, create a mutation through the `useAcceptEscrowMutation` hook in `src/mutations/escrow.ts`. The implementation should be fairly familiar to you now. The accept function accepts the escrow `ApiEscrowObject` and the locked object `ApiLockedObject`. The `{PACKAGE_ID}::shared::swap` Move call is generic, thus it requires the type parameters of the escrowed and locked objects. Query the objects details by using `multiGetObjects` on Sui client instance. Lastly, execute the `{PACKAGE_ID}::shared::swap` Move call and transfer the returned escrowed item to the connected wallet account. When the mutation succeeds, invalidate the cache to allow automatic re-fetch of the data. + +
+ + `escrow.ts` + +```tsx +import { CONSTANTS, QueryKey } from "@/constants"; +import { useTransactionExecution } from "@/hooks/useTransactionExecution"; +import { ApiEscrowObject, ApiLockedObject } from "@/types/types"; +import { useCurrentAccount, useSuiClient } from "@mysten/dapp-kit"; +import { SuiObjectData } from "@mysten/sui/client"; +import { Transaction } from "@mysten/sui/transactions"; +import { useMutation, useQueryClient } from "@tanstack/react-query"; + +/** + * Builds and executes the PTB to create an escrow. + */ +export function useCreateEscrowMutation() { + const currentAccount = useCurrentAccount(); + const executeTransaction = useTransactionExecution(); + + return useMutation({ + mutationFn: async ({ + object, + locked, + }: { + object: SuiObjectData; + locked: ApiLockedObject; + }) => { + if (!currentAccount?.address) + throw new Error("You need to connect your wallet!"); + + const txb = new Transaction(); + txb.moveCall({ + target: `${CONSTANTS.escrowContract.packageId}::shared::create`, + arguments: [ + txb.object(object.objectId!), + txb.pure.id(locked.keyId), + txb.pure.address(locked.creator!), + ], + typeArguments: [object.type!], + }); + + return executeTransaction(txb); + }, + }); +} + +/** + * Builds and executes the PTB to cancel an escrow. + */ +export function useCancelEscrowMutation() { + const currentAccount = useCurrentAccount(); + const executeTransaction = useTransactionExecution(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({ + escrow, + suiObject, + }: { + escrow: ApiEscrowObject; + suiObject: SuiObjectData; + }) => { + if (!currentAccount?.address) + throw new Error("You need to connect your wallet!"); + const txb = new Transaction(); + + const item = txb.moveCall({ + target: `${CONSTANTS.escrowContract.packageId}::shared::return_to_sender`, + arguments: [txb.object(escrow.objectId)], + typeArguments: [suiObject?.type!], + }); + + txb.transferObjects([item], txb.pure.address(currentAccount?.address!)); + + return executeTransaction(txb); + }, + + onSuccess: () => { + setTimeout(() => { + queryClient.invalidateQueries({ queryKey: [QueryKey.Escrow] }); + }, 1_000); + }, + }); +} + +/** + * Builds and executes the PTB to accept an escrow. + */ +export function useAcceptEscrowMutation() { + const currentAccount = useCurrentAccount(); + const client = useSuiClient(); + const executeTransaction = useTransactionExecution(); + const queryClient = useQueryClient(); + + return useMutation({ + mutationFn: async ({ + escrow, + locked, + }: { + escrow: ApiEscrowObject; + locked: ApiLockedObject; + }) => { + if (!currentAccount?.address) + throw new Error("You need to connect your wallet!"); + const txb = new Transaction(); + + const escrowObject = await client.multiGetObjects({ + ids: [escrow.itemId, locked.itemId], + options: { + showType: true, + }, + }); + + const escrowType = escrowObject.find( + (x) => x.data?.objectId === escrow.itemId, + )?.data?.type; + + const lockedType = escrowObject.find( + (x) => x.data?.objectId === locked.itemId, + )?.data?.type; + + if (!escrowType || !lockedType) { + throw new Error("Failed to fetch types."); + } + + const item = txb.moveCall({ + target: `${CONSTANTS.escrowContract.packageId}::shared::swap`, + arguments: [ + txb.object(escrow.objectId), + txb.object(escrow.keyId), + txb.object(locked.objectId), + ], + typeArguments: [escrowType, lockedType], + }); + + txb.transferObjects([item], txb.pure.address(currentAccount.address)); + + return executeTransaction(txb); + }, + + onSuccess: () => { + setTimeout(() => { + queryClient.invalidateQueries({ queryKey: [QueryKey.Escrow] }); + }, 1_000); + }, + }); +} +``` +
+ +
+ + Update `src/components/escrows/Escrow.tsx` to include the `useAcceptEscrowMutation` hook + + {@inject: examples/trading/frontend/src/components/escrows/Escrow.tsx} +
+ +
+ + Update `src/routes/EscrowDashboard.tsx` to include the `EscrowList` component + + {@inject: examples/trading/frontend/src/routes/EscrowDashboard.tsx} +
+ +:::checkpoint + +Run your app and ensure you can: + +- Accept an escrow that someone else requested. + +![Accept escrow](./images/trustless-accept-escrow.png) + +::: + +### Finished frontend + +At this point, you have a fully functional frontend that allows users to discover trades and interact with listed escrows. The UI is designed to be user-friendly and intuitive, allowing users to easily navigate and interact with the application. Have fun exploring the app and testing out the different features! diff --git a/docs/content/guides/developer/app-examples/trustless-swap/backend.mdx b/docs/content/guides/developer/app-examples/trustless-swap/backend.mdx deleted file mode 100644 index a8ddfc59d22af..0000000000000 --- a/docs/content/guides/developer/app-examples/trustless-swap/backend.mdx +++ /dev/null @@ -1,163 +0,0 @@ ---- -title: Trustless Swap Backend -sidebar_label: Backend ---- - -:::note Multi-Page Guide - -This is the first in a [three-part guide](../trustless-swap.mdx) on how to build a trustless atomic swap on Sui. - -::: - -This particular protocol consists of three phases: - -1. One party `lock`s their object, obtaining a `Locked` object and its `Key`. This party can `unlock` their object to preserve liveness if the other party stalls before completing the second stage. -2. The other party registers a publicly accessible, shared `Escrow` object. This effectively locks their object at a particular version as well, waiting for the first party to complete the swap. The second party is able to request their object is returned to them, to preserve liveness as well. -3. The first party sends their locked object and its key to the shared `Escrow` object. This completes the swap, as long as all conditions are met: The sender of the swap transaction is the recipient of the `Escrow`, the key of the desired object (`exchange_key`) in the escrow matches the key supplied in the swap, and the key supplied in the swap unlocks the `Locked`. - -Let's create a Sui project using the terminal command `sui move new escrow`. Create a file in the sources directory named `shared.move`, and let's go through the code together. - -## shared.move - -{@include: ../../../../snippets/app-examples-swap-source.mdx} - -Let's go through the code line by line: - -### Structs - -#### `Escrow` - -This struct represents an object held in escrow. It contains the following fields: - -- `id`: Unique identifier for the escrow object. -- `sender`: Address of the owner of the `escrowed` object. -- `recipient`: Intended recipient of the `escrowed` object. -- `exchange_key`: ID of the key that opens the lock on the object sender wants from the recipient. -- `escrowed`: The actual object held in escrow. - -```move -struct Escrow has key, store { - id: UID, - sender: address, - recipient: address, - exchange_key: ID, - escrowed: T, -} -``` - -The ID of the key is used as the `exchange_key`, rather than the ID of the object, to ensure that the object is not modified after a trade has been initiated. - -### Error codes - -Two constants are defined to represent potential errors during the execution of the swap: - -- `EMismatchedSenderRecipient`: The `sender` and `recipient` of the two escrowed objects do not match. -- `EMismatchedExchangeObject`: The `exchange_for` fields of the two escrowed objects do not match. - -```move -const EMismatchedSenderRecipient: u64 = 0; -const EMismatchedExchangeObject: u64 = 1; -``` - -### Public functions - -#### `create` - -This function is used to create a new escrow object. It takes four arguments: the object to be escrowed, the ID of the key that opens the lock on the object the sender wants from the recipient, the intended recipient, and the transaction context. - -```move -public fun create( - escrowed: T, - exchange_key: ID, - recipient: address, - ctx: &mut TxContext -) { - let mut escrow = Escrow { - id: object::new(ctx), - sender: ctx.sender(), - recipient, - exchange_key, - }; - - dof::add(&mut escrow.id, EscrowedObjectKey {}, escrowed); - - transfer::public_share_object(escrow); -} -``` - -#### `swap` - -This function is used to perform the swap operation. It takes four arguments: the escrow object, the key, the locked object, and the transaction context. - -```move -public fun swap( - mut escrow: Escrow, - key: Key, - locked: Locked, - ctx: &TxContext, -): T { - let escrowed = dof::remove(&mut escrow.id, EscrowedObjectKey {}); - - let Escrow { - id, - sender, - recipient, - exchange_key, - } = escrow; - - assert!(recipient == ctx.sender(), EMismatchedSenderRecipient); - assert!(exchange_key == object::id(&key), EMismatchedExchangeObject); - - // Do the actual swap - transfer::public_transfer(locked.unlock(key), sender); - id.delete(); - - escrowed -} -``` - -The `id.delete()` function call is used to delete the shared `Escrow` object. Previously, Move supported only the deletion of owned objects, but [shared-object deletion has since been enabled](https://github.com/MystenLabs/sui/pull/16008). - -#### `return_to_sender` - -This function is used to cancel the escrow and return the escrowed item to the sender. It takes two arguments: the escrow object and the transaction context. - -```move -public fun return_to_sender( - mut escrow: Escrow, - ctx: &TxContext -): T { - let escrowed = dof::remove(&mut escrow.id, EscrowedObjectKey {}); - - let Escrow { - id, - sender, - recipient: _, - exchange_key: _, - } = escrow; - - assert!(sender == ctx.sender(), EMismatchedSenderRecipient); - id.delete(); - escrowed -} -``` - -Once again, the shared `Escrow` object is deleted after the escrowed item is returned to the sender. - -### Tests - -The code includes several tests to ensure the correct functioning of the atomic swap process. These tests cover successful swaps, mismatches in sender or recipient, mismatches in the exchange object, tampering with the object, and returning the object to the sender. - -In conclusion, this code provides a robust and secure way to perform atomic swaps of objects in a decentralized system, without the need for a trusted third party. It uses shared objects and a series of checks to ensure that the swap only occurs if all conditions are met. - -## Deployment - -{@include: ../../../../snippets/initialize-sui-client-cli.mdx} - -{@include: ../../../../snippets/publish-to-devnet-with-coins.mdx} - -## Next steps - -You have written and deployed the Move package. To turn this into a complete dApp with frontend, you need to create a frontend. For the frontend to be updated, create an indexer that listens to the blockchain as escrows are made and swaps are fulfilled. - -For the next step, you [create the indexing service](./indexer-api.mdx). diff --git a/docs/content/guides/developer/app-examples/trustless-swap/frontend.mdx b/docs/content/guides/developer/app-examples/trustless-swap/frontend.mdx deleted file mode 100644 index f15de97bdd1d4..0000000000000 --- a/docs/content/guides/developer/app-examples/trustless-swap/frontend.mdx +++ /dev/null @@ -1,817 +0,0 @@ ---- -title: Trustless Swap Frontend -sidebar_label: Frontend -toc_max_heading_level: 2 ---- - -:::note Multi-Page Guide - -This is the third in a [three-part guide](../trustless-swap.mdx) on how to build a trustless atomic swap on Sui. - -::: - -In this final part of the app example, you build a frontend (UI) that allows end-users to discover trades and interact with listed escrows. - -## Prerequisites - -{@include: ../../../../snippets/app-examples-swap-source.mdx} - -Before getting started, make sure you: - -- Understand [the mechanism behind the Escrow smart contract backend](../trustless-swap.mdx). -- Check out [indexing service guide](./indexer-api.mdx) to learn how we index on-chain data and API endpoints exposed to serve data query requests. -- Install [`pnpm` through this guide](https://pnpm.io/installation) as we will use it as our package manager. -- Check out [Sui Typescript SDK](https://sdk.mystenlabs.com/typescript) for basic usage on how to interact with Sui with Typescript. -- Check out [Sui dApp Kit](https://sdk.mystenlabs.com/dapp-kit) to learn basic building blocks for developing a dApp in the Sui ecosystem with React.js. -- Check out [React Router](https://reactrouter.com/en/main) as we use it to navigate between different routes in our UI website. -- `dApp Kit` provides a set of hooks for making query and mutation calls to Sui blockchain. These hooks are thin wrappers around query and mutation hooks from `@tanstack/react-query`. Please check out [@tanstack/react-query](https://tanstack.com/query/latest/docs/framework/react/overview) to learn the basic usage for managing, caching, mutating server state. -- This project is bootstrapped through `pnpm create @mysten/dapp`. Please check out [@mysten/create-dapp](https://sdk.mystenlabs.com/dapp-kit/create-dapp) for how to scaffold a React.js Sui dApp project quickly. - -## Overview - -The UI design consists of three parts: - -- A header containing the button allowing users to connect their wallet and navigate to other pages. -- A place for users to manage their owned objects to be ready for escrow trading called `Manage Objects`. -- A place for users to discover, create, and execute trades called `Escrows`. - -:::warning - -The following code snippets are not the full source code. The snippets are meant to focus on relevant logic important to the functionality of the example and features of Sui. - -::: - -## Set up providers {#set-up-providers} - -Set up and configure several providers at the root of your React.js tree to ensure different libraries including `dApp Kit`, `@tanstack/react-query`, `react-router-dom` work as expected. - -```ts title='src/main.tsx' -import { createNetworkConfig, SuiClientProvider, WalletProvider } from '@mysten/dapp-kit'; -import { getFullnodeUrl } from '@mysten/sui/client'; -import { Theme } from '@radix-ui/themes'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { RouterProvider } from 'react-router-dom'; - -import { router } from '@/routes/index.tsx'; - -const queryClient = new QueryClient(); - -const { networkConfig } = createNetworkConfig({ - localnet: { url: getFullnodeUrl('localnet') }, - devnet: { url: getFullnodeUrl('devnet') }, - testnet: { url: getFullnodeUrl('testnet') }, - mainnet: { url: getFullnodeUrl('mainnet') }, -}); - -ReactDOM.createRoot(document.getElementById('root')!).render( - - - - - - - - - - - , -); -``` - -## Connect wallet - -The dApp Kit comes with a pre-built React.js component called `ConnectButton` displaying a button to connect and disconnect a wallet. The connecting and disconnecting wallet logic is handled seamlessly so you don't need to worry about repeating yourself doing the same logic all over again. - -Place the `ConnectButton` in the header: - -```ts title='src/components/Header.tsx' -import { ConnectButton } from '@mysten/dapp-kit'; -import { Box, Button, Container, Flex, Heading } from '@radix-ui/themes'; - -export function Header() { - return ( - - - - - - ); -} -``` - -## Type definitions - -All the type definitions are in `src/types/types.ts`. - -`ApiLockedObject` and `ApiEscrowObject` represent the `Locked` and `Escrow` indexed data model the indexing and API service return. - -`EscrowListingQuery` and `LockedListingQuery` are the query parameters model to provide to the API service to fetch from the endpoints `/escrow` and `/locked` accordingly. - -```ts title='src/types/types.ts' -export type ApiLockedObject = { - id?: string; - objectId: string; - keyId: string; - creator?: string; - itemId: string; - deleted: boolean; -}; - -export type ApiEscrowObject = { - id: string; - objectId: string; - sender: string; - recipient: string; - keyId: string; - itemId: string; - swapped: boolean; - cancelled: boolean; -}; - -export type EscrowListingQuery = { - escrowId?: string; - sender?: string; - recipient?: string; - cancelled?: string; - swapped?: string; - limit?: string; -}; - -export type LockedListingQuery = { - deleted?: string; - keyId?: string; - limit?: string; -}; -``` - -## Execute transaction hook - -In the frontend, you might need to execute a transaction block in multiple places, hence it's better to extract the transaction execution logic and reuse it everywhere. Let's examine the execute transaction hook. - -```ts title='src/hooks/useTransactionExecution.ts' -import { useSignTransaction, useSuiClient } from '@mysten/dapp-kit'; -import { SuiTransactionBlockResponse } from '@mysten/sui/client'; -import { Transaction } from '@mysten/sui/transactions'; -import toast from 'react-hot-toast'; - -export function useTransactionExecution() { - const client = useSuiClient(); - const { mutateAsync: signTransaction } = useSignTransaction(); - - const executeTransaction = async ( - txb: Transaction, - ): Promise => { - try { - const signature = await signTransaction({ - transaction: txb, - }); - - const res = await client.executeTransactionBlock({ - transactionBlock: signature.bytes, - signature: signature.signature, - options: { - showEffects: true, - showObjectChanges: true, - }, - }); - - toast.success('Successfully executed transaction!'); - return res; - } catch (e: any) { - toast.error(`Failed to execute transaction: ${e.message as string}`); - } - }; - - return executeTransaction; -} -``` - -The hook logic is straightforward. A `Transaction` is the input, sign it with the current connected wallet account, execute the transaction block, return the execution result, and finally display a basic toast message to indicate whether the transaction is successful or not. - -Use the `useSuiClient()` hook from `dApp Kit` to retrieve the Sui client instance configured in the [**Set up providers**](#set-up-providers) step. The `useSignTransaction()` function is another hook from `dApp kit` that helps to sign the transaction block using the currently connected wallet. It displays the UI for users to review and sign their transactions with their selected wallet. To execute a transaction block, the `executeTransaction()` on the Sui client instance of the Sui TypeScript SDK. Use `react-hot-toast` as another dependency to toast transaction status to users. - -## Generate demo data - -:::info - -The full source code of the demo bear smart contract is available at [Trading Contracts Demo directory](https://github.com/MystenLabs/sui/tree/main/examples/trading/contracts/demo) - -::: - -You need a utility function to create a dummy object representing a real world asset so you can use it to test and demonstrate escrow users flow on the UI directly. - -```ts title='src/mutations/demo.ts' -import { useCurrentAccount } from '@mysten/dapp-kit'; -import { Transaction } from '@mysten/sui/transactions'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; - -import { CONSTANTS } from '@/constants'; -import { useTransactionExecution } from '@/hooks/useTransactionExecution'; - -// SPDX-License-Identifier: Apache-2.0 -export function useGenerateDemoData() { - const account = useCurrentAccount(); - const executeTransaction = useTransactionExecution(); - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async () => { - if (!account?.address) throw new Error('You need to connect your wallet!'); - const txb = new Transaction(); - - const bear = txb.moveCall({ - target: `${CONSTANTS.demoContract.packageId}::demo_bear::new`, - arguments: [txb.pure.string(`A happy bear`)], - }); - - txb.transferObjects([bear], txb.pure.address(account.address)); - - return executeTransaction(txb); - }, - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: ['getOwnedObjects'], - }); - }, - }); -} -``` - -As previously mentioned, the example uses `@tanstack/react-query` to query, cache, and mutate server state. Server state is data only available on remote servers, and the only way to retrieve or update this data is by interacting with these remote servers. In this case, it could be from an API or directly from Sui blockchain RPC. - -When you execute a transaction call to mutate data on the Sui blockchain, use the `useMutation()` hook. The `useMutation()` hook accepts several inputs, however, you only need two of them for this example. The first parameter, `mutationFn`, accepts the function to execute the main mutating logic, while the second parameter, `onSuccess`, is a callback that runs when the mutating logic succeeds. - -The main mutating logic is fairly straightforward, executing a Move call of a package named `demo_bear::new` to create a dummy bear object and transfer it to the connected wallet account, all within the same `Transaction`. The example reuses the `executeTransaction()` hook from the **Execute Transaction Hook** step to execute the transaction. - -Another benefit of wrapping the main mutating logic inside `useMutation()` is that you can access and manipulate the cache storing server state. The example fetches the cache from remote servers by using query call in an appropriate callback. In this case, it is the `onSuccess` callback. When the transaction succeeds, invalidate the cache data at the cache key called `getOwnedObjects`, then `@tanstack/react-query` handles the re-fetching mechanism for the invalidated data automatically. Do this by using `invalidateQueries()` on the `@tanstack/react-query` configured client instance retrieved by `useQueryClient()` hook in the **Setup Providers** step. - -Now the logic to create a dummy bear object exists. You just need to attach it into the button in the header. - -```ts title='src/components/Header.tsx' -import { useGenerateDemoData } from '@/mutations/demo'; - -export function Header() { - const { mutate: demoBearMutation, isPending } = useGenerateDemoData(); - return ( - - - - - - ); -} -``` - -## Lock/unlock owned-object mutation - -Locking and unlocking of owned objects are two crucial on-chain actions in this application and are very likely to be used all over. Hence, it's beneficial to extract their logic into separated mutating functions to enhance reusability and encapsulation. - -### Lock owned objects - -To lock the object, execute the `lock` Move function identified by `{PACKAGE_ID}::lock::lock`. The implementation is similar to what's in previous mutation functions, use `useMutation()` from `@tanstack/react-query` to wrap the main logic inside it. The lock function requires an object to be locked and its type because our smart contract `lock` function is generic and requires type parameters. After creating a `Locked` object and its `Key` object, transfer them to the connected wallet account within the same transaction block. - -```ts title='src/mutations/locked.ts' -export function useLockObjectMutation() { - const account = useCurrentAccount(); - const executeTransaction = useTransactionExecution(); - - return useMutation({ - mutationFn: async ({ object }: { object: SuiObjectData }) => { - if (!account?.address) throw new Error('You need to connect your wallet!'); - const txb = new Transaction(); - - const [locked, key] = txb.moveCall({ - target: `${CONSTANTS.escrowContract.packageId}::lock::lock`, - arguments: [txb.object(object.objectId)], - typeArguments: [object.type!], - }); - - txb.transferObjects([locked, key], txb.pure.address(account.address)); - - return executeTransaction(txb); - }, - }); -} -``` - -### Unlock owned objects - -To unlock the object, execute the `unlock` Move function identified by `{PACKAGE_ID}::lock::unlock`. The implementation is straightforward, call the `unlock` function supplying the `Locked` object, its corresponding `Key`, the struct type of the original object, and transfer the unlocked object to the current connected wallet account. Also, implement the `onSuccess` callback to invalidate the cache data at query key `locked` after one second to force `react-query` to re-fetch the data at corresponding query key automatically. - -```ts title='src/mutations/locked.ts' -export function useUnlockMutation() { - const account = useCurrentAccount(); - const executeTransaction = useTransactionExecution(); - const client = useSuiClient(); - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async ({ - lockedId, - keyId, - suiObject, - }: { - lockedId: string; - keyId: string; - suiObject: SuiObjectData; - }) => { - if (!account?.address) throw new Error('You need to connect your wallet!'); - const key = await client.getObject({ - id: keyId, - options: { - showOwner: true, - }, - }); - - if ( - !key.data?.owner || - typeof key.data.owner === 'string' || - !('AddressOwner' in key.data.owner) || - key.data.owner.AddressOwner !== account.address - ) { - toast.error('You are not the owner of the key'); - return; - } - - const txb = new Transaction(); - - const item = txb.moveCall({ - target: `${CONSTANTS.escrowContract.packageId}::lock::unlock`, - typeArguments: [suiObject.type!], - arguments: [txb.object(lockedId), txb.object(keyId)], - }); - - txb.transferObjects([item], txb.pure.address(account.address)); - - return executeTransaction(txb); - }, - onSuccess: () => { - setTimeout(() => { - // invalidating the queries after a small latency - // because the indexer works in intervals of 1s. - // if we invalidate too early, we might not get the latest state. - queryClient.invalidateQueries({ - queryKey: [QueryKey.Locked], - }); - }, 1_000); - }, - }); -} -``` - -## Create/accept/cancel escrow mutations - -To create, accept, or cancel escrows, it's better to implement mutations for each of these actions to allow reusability and encapsulation. - -### Create escrows - -To create escrows, include a mutating function through the `useCreateEscrowMutation` hook in `src/mutations/escrow.ts`. The mutation implementation is pretty straightforward. It accepts the escrowed item to be traded and the `ApiLockedObject` from another party as parameters. Then, call the `{PACKAGE_ID}::shared::create` Move function and provide the escrowed item, the key id of the locked object to exchange, and the recipient of the escrow (locked object's owner). - -```ts title='src/mutations/escrow.ts' -export function useCreateEscrowMutation() { - const currentAccount = useCurrentAccount(); - const executeTransaction = useTransactionExecution(); - - return useMutation({ - mutationFn: async ({ object, locked }: { object: SuiObjectData; locked: ApiLockedObject }) => { - if (!currentAccount?.address) throw new Error('You need to connect your wallet!'); - - const txb = new Transaction(); - txb.moveCall({ - target: `${CONSTANTS.escrowContract.packageId}::shared::create`, - arguments: [ - txb.object(object.objectId!), - txb.pure.id(locked.keyId), - txb.pure.address(locked.creator!), - ], - typeArguments: [object.type!], - }); - - return executeTransaction(txb); - }, - }); -} -``` - -### Accept escrows - -To accept the escrow, create a mutation through the `useAcceptEscrowMutation` hook in `src/mutations/escrow.ts`. The implementation should be fairly familiar to you now. The accept function accepts the escrow `ApiEscrowObject` and the locked object `ApiLockedObject`. The `{PACKAGE_ID}::shared::swap` Move call is generic, thus it requires the type parameters of the escrowed and locked objects. Query the objects details by using `multiGetObjects` on Sui client instance. Lastly, execute the `{PACKAGE_ID}::shared::swap` Move call and transfer the returned escrowed item to the connected wallet account. When the mutation succeeds, invalidate the cache to allow automatic re-fetch of the data. - -```ts title='src/mutations/escrow.ts' -import { ApiEscrowObject, ApiLockedObject } from '@/types/types'; - -export function useAcceptEscrowMutation() { - const currentAccount = useCurrentAccount(); - const client = useSuiClient(); - const executeTransaction = useTransactionExecution(); - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async ({ - escrow, - locked, - }: { - escrow: ApiEscrowObject; - locked: ApiLockedObject; - }) => { - if (!currentAccount?.address) throw new Error('You need to connect your wallet!'); - const txb = new Transaction(); - - const escrowObject = await client.multiGetObjects({ - ids: [escrow.itemId, locked.itemId], - options: { - showType: true, - }, - }); - - const escrowType = escrowObject.find((x) => x.data?.objectId === escrow.itemId)?.data?.type; - - const lockedType = escrowObject.find((x) => x.data?.objectId === locked.itemId)?.data?.type; - - if (!escrowType || !lockedType) { - throw new Error('Failed to fetch types.'); - } - - const item = txb.moveCall({ - target: `${CONSTANTS.escrowContract.packageId}::shared::swap`, - arguments: [ - txb.object(escrow.objectId), - txb.object(escrow.keyId), - txb.object(locked.objectId), - ], - typeArguments: [escrowType, lockedType], - }); - - txb.transferObjects([item], txb.pure.address(currentAccount.address)); - - return executeTransaction(txb); - }, - - onSuccess: () => { - setTimeout(() => { - queryClient.invalidateQueries({ queryKey: [QueryKey.Escrow] }); - }, 1_000); - }, - }); -} -``` - -### Cancel escrows - -To cancel the escrow, create a mutation through the `useCancelEscrowMutation` hook in `src/mutations/escrow.ts`. The cancel function accepts the escrow `ApiEscrowObject` and its on-chain data. The `{PACKAGE_ID}::shared::return_to_sender` Move call is generic, thus it requires the type parameters of the escrowed object. Next, execute `{PACKAGE_ID}::shared::return_to_sender` and transfer the returned escrowed object to the creator of the escrow. - -```ts ts title='src/mutations/escrow.ts' -export function useCancelEscrowMutation() { - const currentAccount = useCurrentAccount(); - const executeTransaction = useTransactionExecution(); - const queryClient = useQueryClient(); - - return useMutation({ - mutationFn: async ({ - escrow, - suiObject, - }: { - escrow: ApiEscrowObject; - suiObject: SuiObjectData; - }) => { - if (!currentAccount?.address) throw new Error('You need to connect your wallet!'); - const txb = new Transaction(); - - const item = txb.moveCall({ - target: `${CONSTANTS.escrowContract.packageId}::shared::return_to_sender`, - arguments: [txb.object(escrow.objectId)], - typeArguments: [suiObject?.type!], - }); - - txb.transferObjects([item], txb.pure.address(currentAccount?.address!)); - - return executeTransaction(txb); - }, - - onSuccess: () => { - setTimeout(() => { - queryClient.invalidateQueries({ queryKey: [QueryKey.Escrow] }); - }, 1_000); - }, - }); -} -``` - -## Locked dashboard - -The UI has a tab for users to manage their owned objects to be ready for escrow trading. The code of this tab lives in the file `src/routes/LockedDashBoard.tsx`. In this tab, there are two sub-tabs: - -- **My Locked Objects** tab to list out all of owned `Locked` objects. -- **Lock Own Objects** tab to lock owned objects. - -### My Locked Objects tab - -Let's take a look at the **My Locked Objects** tab by examining `src/components/locked/OwnedLockedList.tsx`. Focus on the logic on how to retrieve this list. - -```ts title='src/components/locked/OwnedLockedList.tsx' -import { useCurrentAccount, useSuiClientInfiniteQuery } from '@mysten/dapp-kit'; - -import { InfiniteScrollArea } from '@/components/InfiniteScrollArea'; -import { CONSTANTS } from '@/constants'; - -import { LockedObject } from './LockedObject'; - -/** - * Similar to the `ApiLockedList` but fetches the owned locked objects - * but fetches the objects from the on-chain state, instead of relying on the indexer API. - */ -export function OwnedLockedList() { - const account = useCurrentAccount(); - const { data, isLoading, fetchNextPage, hasNextPage, isFetchingNextPage } = - useSuiClientInfiniteQuery( - 'getOwnedObjects', - { - filter: { - StructType: CONSTANTS.escrowContract.lockedType, - }, - owner: account?.address!, - options: { - showContent: true, - showOwner: true, - }, - }, - { - enabled: !!account?.address, - select: (data) => data.pages.flatMap((page) => page.data), - }, - ); - return ( - <> - fetchNextPage()} - hasNextPage={hasNextPage} - loading={isFetchingNextPage || isLoading} - > - {data?.map((item) => )} - - - ); -} -``` - -Fetch the owned `Locked` objects directly from Sui blockchain using the `useSuiClientInfiniteQuery()` hook from `dApp Kit`. This hook is a thin wrapper around Sui blockchain RPC calls, reference the documentation to learn more about these [RPC hooks](https://sdk.mystenlabs.com/dapp-kit/rpc-hooks). Basically, supply the RPC endpoint you want to execute, in this case it's the [`getOwnedObjects` endpoint](https://docs.sui.io/sui-api-ref#suix_getownedobjects). Supply the connected wallet account and the `Locked` object struct type to the call. The struct type is usually identified by the format of `{PACKAGE_ID}::{{MODULE_NAME}}::{{STRUCT_TYPE}}`. The returned data is stored inside the cache at query key `getOwnedObjects`. Recall the previous section where you invalidate the data at this key after the mutation succeeds, the `useSuiClientInfiniteQuery()` hook automatically re-fetches the data, thus you don't have to worry about the out-dated data living in your frontend application. - -#### `LockedObject` and `Locked` component - -The `` (`src/components/locked/LockedObject.tsx`) component is mainly responsible for mapping an on-chain `SuiObjectData` `Locked` object to its corresponding `ApiLockedObject`, which is finally delegated to the `` component for rendering. The `` fetches the locked item object ID if the prop `itemId` is not supplied by using `dApp Kit` `useSuiClientQuery()` hook to call the `getDynamicFieldObject` RPC endpoint. Recalling that in this smart contract, the locked item is put into a dynamic object field. - -The `` (`src/components/locked/partials/Locked.tsx`) component is mainly responsible for rendering the `ApiLockedObject`. It also consists of several on-chain interactions: unlock the locked objects and create an escrow out of the locked object. - -```ts title='src/components/locked/LockedObject.tsx' -/** - * Acts as a wrapper between the `Locked` object fetched from API - * and the on-chain object state. - * - * Accepts an `object` of type `::locked::Locked`, fetches the itemID (though the DOF) - * and then renders the `Locked` component. - * - * ItemId is optional because we trust the API to return the correct itemId for each Locked. - */ -export function LockedObject({ - object, - itemId, - hideControls, -}: { - object: SuiObjectData; - itemId?: string; - hideControls?: boolean; -}) { - const owner = () => { - if (!object.owner || typeof object.owner === 'string' || !('AddressOwner' in object.owner)) - return undefined; - return object.owner.AddressOwner; - }; - - const getKeyId = (item: SuiObjectData) => { - if (!(item.content?.dataType === 'moveObject') || !('key' in item.content.fields)) return ''; - return item.content.fields.key as string; - }; - - // Get the itemID for the locked object (We've saved it as a DOF on the SC). - const suiObjectId = useSuiClientQuery( - 'getDynamicFieldObject', - { - parentId: object.objectId, - name: { - type: CONSTANTS.escrowContract.lockedObjectDFKey, - value: { - dummy_field: false, - }, - }, - }, - { - select: (data) => data.data, - enabled: !itemId, - }, - ); - - return ( - - ); -} -``` - -### Lock owned object - -You have all the logic you need to implement this UI. Use the same `useSuiClientInfiniteQuery()` hook to query all the owned objects of the connected wallet. Filter out objects that do not exist in the Object Display `display.data.image_url` as you can assume the valid NFTs conform to the Object Display and have an image in the metadata. Lastly, use the lock mutation from `useLockObjectMutation()` hook whenever the user clicks the lock button. - -```ts title='src/components/locked/LockOwnedObjects.tsx' -export function LockOwnedObjects() { - const account = useCurrentAccount(); - - const { mutate: lockObjectMutation, isPending } = useLockObjectMutation(); - - const { data, fetchNextPage, isFetchingNextPage, hasNextPage, refetch } = - useSuiClientInfiniteQuery( - 'getOwnedObjects', - { - owner: account?.address!, - options: { - showDisplay: true, - showType: true, - }, - }, - { - enabled: !!account, - select: (data) => - data.pages - .flatMap((page) => page.data) - .filter((x) => !!x.data?.display && !!x.data?.display?.data?.image_url), - }, - ); - - return ( - fetchNextPage()} - hasNextPage={hasNextPage} - loading={isFetchingNextPage} - > - {data?.map((obj) => ( - -
-

Lock the item so it can be used for escrows.

- -
-
- ))} -
- ); -} -``` - -## Escrow dashboard - -The UI has a place for users to discover, create, and execute trades. The code of this tab lives in the file `src/routes/EscrowDashboard.tsx`. In this tab, there are three sub-tabs: - -- **Requested Escrows** tab to list out all of the escrow requested for locked objects. -- **Browse Locked Objects** tab to browse locked objects to trade for. -- **My Pending Requests** tab to browse escrows you have initiated for third-party locked objects. - -### Requested escrows - -Let's take a look at the **Requested Escrows** tab by examining `src/components/escrows/EscrowList.tsx`. This time, the data is retrieved by using `useInfiniteQuery` directly from `react-query`. Fetch the data by calling the API service that you already implemented in the [Escrow Indexing and API Service Guide](./indexer-api.mdx). Call the `/escrows` endpoint to fetch all the escrows requested to you. The rationale behind using an API service to fetch the data is because the indexed data includes additional information that allows query efficiency and flexibility. You can fetch specific escrows satisfying different configured query clauses rather than limited query features of Sui blockchain RPC endpoints. - -```ts title='src/components/escrows/EscrowList.tsx' -import { constructUrlSearchParams, getNextPageParam } from '@/utils/helpers'; - -const { data, fetchNextPage, hasNextPage, isLoading, isFetchingNextPage } = useInfiniteQuery({ - initialPageParam: null, - queryKey: [QueryKey.Escrow, params, escrowId], - queryFn: async ({ pageParam }) => { - const data = await fetch( - CONSTANTS.apiEndpoint + - 'escrows' + - constructUrlSearchParams({ - ...params, - ...(pageParam ? { cursor: pageParam as string } : {}), - ...(escrowId ? { objectId: escrowId } : {}), - }), - ); - return data.json(); - }, - select: (data) => data.pages.flatMap((page) => page.data), - getNextPageParam, -}); -``` - -The `Escrow` component renders the details of an escrow by providing `ApiEscrowObject` as a rendering property. There is some data you need to fetch to gather necessary escrow information for display in the UI: - -- Query the escrowed item object directly from Sui blockchain by using `useSuiClientQuery('getObject')` as this is the only way to have its Object Display metadata. -- Fetch the `ApiLockedObject` corresponding to the escrow's key ID from the API service as this is the most efficient way to fetch the locked object in a complex query. -- Fetch the on-chain `Locked` object corresponding to the returned `ApiLockedObject` to pass it onto ``. - -```ts title='src/components/escrows/Escrow.tsx' -export function Escrow({ escrow }: { escrow: ApiEscrowObject }) { - const account = useCurrentAccount(); - const [isToggled, setIsToggled] = useState(true); - const { mutate: acceptEscrowMutation, isPending } = useAcceptEscrowMutation(); - const { mutate: cancelEscrowMutation, isPending: pendingCancellation } = - useCancelEscrowMutation(); - - const suiObject = useSuiClientQuery('getObject', { - id: escrow?.itemId, - options: { - showDisplay: true, - showType: true, - }, - }); - - const lockedData = useQuery({ - queryKey: [QueryKey.Locked, escrow.keyId], - queryFn: async () => { - const res = await fetch(`${CONSTANTS.apiEndpoint}locked?keyId=${escrow.keyId}`); - return res.json(); - }, - select: (data) => data.data[0], - enabled: !escrow.cancelled, - }); - - const { data: suiLockedObject } = useGetLockedObject({ - lockedId: lockedData.data?.objectId, - }); - - ... -} -``` - -As the last step, reuse the `accept` and `cancel` escrow mutations in corresponding buttons. - -### Browse locked objects - -The `src/components/locked/ApiLockedList.tsx` component renders all the on-chain locked objects based on the `LockedListingQuery` property. Call the API service to fetch the `ApiLockedObject` data using the provided query parameters. One caveat around the API service is that the `creator` field of the `ApiLockedObject` could be stale because the `Locked` object has the `store` ability. This means that the object can be transferred freely, hence, the ownership might not be correctly tracked by the API service. That's why you still fetch from the Sui blockchain as an additional step to define the object with latest on-chain information to ensure its data correctness in regards to ownership. - -```ts title='src/components/locked/ApiLockedList.tsx' -const { data, fetchNextPage, hasNextPage, isLoading, isFetchingNextPage } = useInfiniteQuery({ - initialPageParam: null, - queryKey: [QueryKey.Locked, params, lockedId], - queryFn: async ({ pageParam }) => { - const data = await ( - await fetch( - CONSTANTS.apiEndpoint + - 'locked' + - constructUrlSearchParams({ - deleted: 'false', - ...(pageParam ? { cursor: pageParam as string } : {}), - ...(params || {}), - }), - ) - ).json(); - - const objects = await suiClient.multiGetObjects({ - ids: data.data.map((x: ApiLockedObject) => x.objectId), - options: { - showOwner: true, - showContent: true, - }, - }); - - return { - suiObjects: objects.map((x) => x.data), - api: data, - }; - }, - select: (data) => data.pages, - getNextPageParam, - enabled: !lockedId, -}); -``` - -### My Pending Requests tab - -The **My Pending Requests** tab uses the same `` component as **Requested Escrows** tab as they're both trying to display the escrows. The only difference is that the former fetches all the escrows with current wallet as recipient, while the latter fetches all the escrows with current wallet as sender. diff --git a/docs/content/guides/developer/app-examples/trustless-swap/indexer-api.mdx b/docs/content/guides/developer/app-examples/trustless-swap/indexer-api.mdx deleted file mode 100644 index 80a276895c40e..0000000000000 --- a/docs/content/guides/developer/app-examples/trustless-swap/indexer-api.mdx +++ /dev/null @@ -1,632 +0,0 @@ ---- -title: Escrow Indexing and API Service -sidebar_label: Indexing and API Service ---- - -:::note Multi-Page Guide - -This is the second in a [three-part guide](../trustless-swap.mdx) on how to build a trustless atomic swap on Sui. - -::: - -In most cases where you want to enhance a dApp and ensure it is production ready, you need to have an indexing service (indexer) listening to the blockchain for on-chain events, shaping the data to fit your application needs, and storing the transformed data into the local off-chain database so you can query them in the most efficient way. Joining hands with an indexer, you expose an API allowing the frontend to query the indexed data and update the screen as escrows are made and swaps are fulfilled. - -Architecturally, the service does the heavy lifting of indexing the data, while the other service exposes the data through an API convention for external consumption. - -## Prerequisites - -{@include: ../../../../snippets/app-examples-swap-source.mdx} - -Before getting started, make sure you: - -- Understand [the mechanism behind the Trading backend](../trustless-swap.mdx). -- Install [`pnpm` through this guide](https://pnpm.io/installation) as this example uses it as the package manager. -- Check out the [indexer's README](https://github.com/MystenLabs/sui/tree/main/examples/trading/api) to setup the development environment. -- Check out [Prisma](https://www.prisma.io/) to get an overall sense of the technology that facilitates all the database interactions. -- Check out [Express](https://expressjs.com/) to learn how to set up a web server application in Node.js. This server bootstraps your API service. -- Check out [Sui TypeScript SDK](https://sdk.mystenlabs.com/typescript) for basic usage on how to interact with Sui with TypeScript. - -## Indexing service - -The indexing service fetches `Escrow` objects by `sender` and `recipient`. - -### Data model - -In most cases, when you're working with a database directly from a backend service, you might want to use some sort of database libraries and toolings to abstract away database creation and management complexity. In this case, the example uses [Prisma](https://www.prisma.io/) to manage all the database interactions, such as defining data models and database migrations. - -First of all, design what data to index: - -```ts title='prisma/schema.prisma' -/// Our `Locked` objects list -model Locked { - // Keeping an ID to use as a pagination cursor - // There's an issue with BigInt for sqlite, so use a plain ID. - id Int @id @default(autoincrement()) - objectId String @unique - keyId String? - creator String? - itemId String? - deleted Boolean @default(false) - - @@index([creator]) - @@index([deleted]) -} - -/// Swap objects list -model Escrow { - // Keeping an ID to use as a pagination cursor - // There's an issue with BigInt for sqlite, so use a plain ID. - id Int @id @default(autoincrement()) - objectId String @unique - sender String? - recipient String? - keyId String? - itemId String? - swapped Boolean @default(false) - cancelled Boolean @default(false) - - @@index([recipient]) - @@index([sender]) -} -``` - -These data models represent the `Locked` and `Escrow` object. Compared to their on-chain version, which contains much less attributes due to initial smart contract design, they have additional fields providing extra information that helps to facilitate any queries at a later stage. - -```ts title='prisma/schema.prisma' -/// Saves the latest cursor for a given key. -model Cursor { - id String @id - eventSeq String - txDigest String -} -``` - -Most indexing services need to implement some sort of checkpoint mechanism to ensure it picks up the progress where it left off even after it returns from a crash. `Cursor` is the checkpoint that you store in your persistent database to ensure the data remains and is unaffected by incidents. - -Next, let's explore the logic keeping the service listening to blockchain signals and reacting accordingly. - -### `event-indexer.ts` - -Let's first examine `event-indexer.ts`: - -#### Imports - -```ts title='event-indexer.ts' -import { EventId, SuiClient, SuiEvent, SuiEventFilter } from '@mysten/sui/client'; - -import { CONFIG } from '../config'; -import { prisma } from '../db'; -import { getClient } from '../sui-utils'; -import { handleEscrowObjects } from './escrow-handler'; -import { handleLockObjects } from './locked-handler'; -``` - -These lines import the necessary modules and dependencies for the script. The `EventId`, `SuiClient`, `SuiEvent`, and `SuiEventFilter` types are imported from the `@mysten/sui/client` package. The `CONFIG` constant is imported from the local `config` module, `prisma` from the local `db` module, `getClient` from the local `sui-utils` module, and the `handleEscrowObjects` and `handleLockObjects` functions from the local `escrow-handler` and `locked-handler` modules respectively. - -#### Type definitions - -```ts title='event-indexer.ts' -type SuiEventsCursor = EventId | null | undefined; - -type EventExecutionResult = { - cursor: SuiEventsCursor; - hasNextPage: boolean; -}; - -type EventTracker = { - // The module that defines the type, with format `package::module` - type: string; - filter: SuiEventFilter; - callback: (events: SuiEvent[], type: string) => any; -}; -``` - -Three custom types are defined here: `SuiEventsCursor`, `EventExecutionResult`, and `EventTracker`. `SuiEventsCursor` is a type alias for `EventId | null | undefined`, representing the possible states of a cursor pointing to events on the Sui network. `EventExecutionResult` represents the result of executing an event job, including the updated cursor and a flag indicating whether there are more pages of events to process. `EventTracker` represents an event tracker, which includes the type of the event, a filter for the event, and a callback function to handle the event. - -#### Constants - -```ts title='event-indexer.ts' -const EVENTS_TO_TRACK: EventTracker[] = [ - { - type: `${CONFIG.SWAP_CONTRACT.packageId}::lock`, - filter: { - MoveEventModule: { - module: 'lock', - package: CONFIG.SWAP_CONTRACT.packageId, - }, - }, - callback: handleLockObjects, - }, - { - type: `${CONFIG.SWAP_CONTRACT.packageId}::shared`, - filter: { - MoveEventModule: { - module: 'shared', - package: CONFIG.SWAP_CONTRACT.packageId, - }, - }, - callback: handleEscrowObjects, - }, -]; -``` - -The `EVENTS_TO_TRACK` constant is an array of `EventTracker` objects. Each `EventTracker` specifies a type of event to track, a filter for the event, and a callback function to handle the event. In this case, the script tracks two types of events: lock events and shared events. The filter for each event specifies the module and package ID for the event. The callback function for each event is either `handleLockObjects` or `handleEscrowObjects`, depending on the type of event. - -#### Functions - -```ts title='event-indexer.ts' -const executeEventJob = async ( - client: SuiClient, - tracker: EventTracker, - cursor: SuiEventsCursor, -): Promise => { - try { - // Get the events from the chain. - // This implementation goes from start to finish. - // This also allows filling in a database from scratch! - const { data, hasNextPage, nextCursor } = await client.queryEvents({ - query: tracker.filter, - cursor, - order: 'ascending', - }); - - // Handle the data transformations defined for each event. - await tracker.callback(data, tracker.type); - - // Only update the cursor if extra data is fetched (which means there was a change). - if (nextCursor && data.length > 0) { - await saveLatestCursor(tracker, nextCursor); - - return { - cursor: nextCursor, - hasNextPage, - }; - } - } catch (e) { - console.error(e); - } - // By default, return the same cursor as passed in. - return { - cursor, - hasNextPage: false, - }; -}; -``` - -This function executes an event job. It takes a `SuiClient`, an `EventTracker`, and a `SuiEventsCursor` as arguments, and returns a promise that resolves to an `EventExecutionResult`. The function tries to get the events from the chain according to the filter defined in the `EventTracker`. If successful, it handles the data transformations defined for each event and updates the cursor if there were changes. If an error occurs during execution, it logs the error and returns the original cursor without updating it. - -```ts title='event-indexer.ts' -const runEventJob = async (client: SuiClient, tracker: EventTracker, cursor: SuiEventsCursor) => { - const result = await executeEventJob(client, tracker, cursor); - - // Trigger a timeout. Depending on the result, we either wait 0ms or the polling interval. - setTimeout( - () => { - runEventJob(client, tracker, result.cursor); - }, - result.hasNextPage ? 0 : CONFIG.POLLING_INTERVAL_MS, - ); -}; -``` - -This function runs an event job. It takes a `SuiClient`, an `EventTracker`, and a `SuiEventsCursor` as arguments. It calls `executeEventJob` and schedules another call to `runEventJob` based on the result of the execution. If there are more pages of events to process, it waits for the polling interval defined in `CONFIG.POLLING_INTERVAL_MS` before making the next call. Otherwise, it makes the call immediately. - -```ts title='event-indexer.ts' -const getLatestCursor = async (tracker: EventTracker) => { - const cursor = await prisma.cursor.findUnique({ - where: { - id: tracker.type, - }, - }); - - return cursor || undefined; -}; -``` - -This function gets the latest cursor for an event tracker. It takes an `EventTracker` as an argument and returns a promise that resolves to the cursor. If the cursor is undefined, it retrieves the cursor from the database. - -```ts title='event-indexer.ts' -const saveLatestCursor = async (tracker: EventTracker, cursor: EventId) => { - const data = { - eventSeq: cursor.eventSeq, - txDigest: cursor.txDigest, - }; - - return prisma.cursor.upsert({ - where: { - id: tracker.type, - }, - update: data, - create: { id: tracker.type, ...data }, - }); -}; -``` - -This function saves the latest cursor for an event tracker to the database. It takes an `EventTracker` and a `SuiEventsCursor` as arguments and returns a promise that resolves to the saved cursor. If the cursor already exists in the database, it updates the existing entry. Otherwise, it creates a new entry. - -```ts title='event-indexer.ts' -export const setupListeners = async () => { - for (const event of EVENTS_TO_TRACK) { - runEventJob(getClient(CONFIG.NETWORK), event, await getLatestCursor(event)); - } -}; -``` - -This function sets up all the listeners for the events to track. It iterates over the `EVENTS_TO_TRACK` array and calls `runEventJob` for each event tracker, passing the `SuiClient`, the event tracker, and the latest cursor for the event tracker as arguments. - -Now let’s take a look at `escrow-handler.ts`: - -### `escrow-handler.ts` - -#### Imports - -```ts title='escrow-handler.ts' -import { SuiEvent } from '@mysten/sui/client'; -import { Prisma } from '@prisma/client'; - -import { prisma } from '../db'; -``` - -These lines import the necessary modules and dependencies for the script. The `SuiEvent` type is imported from the `@mysten/sui/client` package. The `Prisma` namespace is imported from the `@prisma/client package`. The `prisma` instance is imported from the local `db` module. - -#### Type definitions - -```ts title='escrow-handler.ts' -type EscrowEvent = EscrowCreated | EscrowCancelled | EscrowSwapped; - -type EscrowCreated = { - sender: string; - recipient: string; - escrow_id: string; - key_id: string; - item_id: string; -}; - -type EscrowSwapped = { - escrow_id: string; -}; - -type EscrowCancelled = { - escrow_id: string; -}; -``` - -Four custom types are defined here: `EscrowEvent`, `EscrowCreated`, `EscrowSwapped`, and `EscrowCancelled`. `EscrowEvent` is a union type that can be any of `EscrowCreated`, `EscrowCancelled`, or `EscrowSwapped`. `EscrowCreated` represents the data associated with an escrow creation event. `EscrowSwapped` represents the data associated with an escrow swap event. `EscrowCancelled` represents the data associated with an escrow cancellation event. - -#### Functions - -```ts title='escrow-handler.ts' -export const handleEscrowObjects = async (events: SuiEvent[], type: string) => { - const updates: Record = {}; - - for (const event of events) { - if (!event.type.startsWith(type)) throw new Error('Invalid event module origin'); - const data = event.parsedJson as EscrowEvent; - - if (!Object.hasOwn(updates, data.escrow_id)) { - updates[data.escrow_id] = { - objectId: data.escrow_id, - }; - } - - // Escrow cancellation case - if (event.type.endsWith('::EscrowCancelled')) { - const data = event.parsedJson as EscrowCancelled; - updates[data.escrow_id].cancelled = true; - continue; - } - - // Escrow swap case - if (event.type.endsWith('::EscrowSwapped')) { - const data = event.parsedJson as EscrowSwapped; - updates[data.escrow_id].swapped = true; - continue; - } - - const creationData = event.parsedJson as EscrowCreated; - - // Handle creation event - updates[data.escrow_id].sender = creationData.sender; - updates[data.escrow_id].recipient = creationData.recipient; - updates[data.escrow_id].keyId = creationData.key_id; - updates[data.escrow_id].itemId = creationData.item_id; - } - - // As part of the demo and to avoid having external dependencies, we use SQLite as our database. - // Prisma + SQLite does not support bulk insertion & conflict handling, so we have to insert these 1 by 1 - // (resulting in multiple round-trips to the database). - // Always use a single `bulkInsert` query with proper `onConflict` handling in production databases (e.g Postgres) - const promises = Object.values(updates).map((update) => - prisma.escrow.upsert({ - where: { - objectId: update.objectId, - }, - create: update, - update, - }), - ); - await Promise.all(promises); -}; -``` - -This function handles all events emitted by the `escrow` module. It takes an array of `SuiEvent` objects and a string representing the type of the events as arguments. The function processes each event and updates the corresponding escrow object in the database accordingly. If an event indicates that an escrow was canceled or swapped, the function marks the corresponding escrow object as canceled or swapped. If an event indicates that an escrow was created, the function creates a new escrow object with the details from the event. - -### `locked-handler.ts` - -#### Imports - -```ts title='locked-handler.ts' -import { SuiEvent } from '@mysten/sui/client'; -import { Prisma } from '@prisma/client'; - -import { prisma } from '../db'; -``` - -These lines import the necessary modules and dependencies for the script. The `SuiEvent` type is imported from the `@mysten/sui/client` package. The `Prisma` namespace is imported from the `@prisma/client package`. The `prisma` instance is imported from the local `db` module. - -#### Type definitions - -```ts title='locked-handler.ts' -type LockEvent = LockCreated | LockDestroyed; - -type LockCreated = { - creator: string; - lock_id: string; - key_id: string; - item_id: string; -}; - -type LockDestroyed = { - lock_id: string; -}; -``` - -Three custom types are defined here: `LockEvent`, `LockCreated`, and `LockDestroyed`. `LockEvent` is a union type that can be either `LockCreated` or `LockDestroyed`. `LockCreated` represents the data associated with a lock creation event. `LockDestroyed` represents the data associated with a lock destruction event. - -#### Functions - -```ts title='locked-handler.ts' -export const handleLockObjects = async (events: SuiEvent[], type: string) => { - const updates: Record = {}; - - for (const event of events) { - if (!event.type.startsWith(type)) throw new Error('Invalid event module origin'); - const data = event.parsedJson as LockEvent; - const isDeletionEvent = !('key_id' in data); - - if (!Object.hasOwn(updates, data.lock_id)) { - updates[data.lock_id] = { - objectId: data.lock_id, - }; - } - - // Handle deletion - if (isDeletionEvent) { - updates[data.lock_id].deleted = true; - continue; - } - - // Handle creation event - updates[data.lock_id].keyId = data.key_id; - updates[data.lock_id].creator = data.creator; - updates[data.lock_id].itemId = data.item_id; - } - - // As part of the demo and to avoid having external dependencies, we use SQLite as our database. - // Prisma + SQLite does not support bulk insertion & conflict handling, so we have to insert these 1 by 1 - // (resulting in multiple round-trips to the database). - // Always use a single `bulkInsert` query with proper `onConflict` handling in production databases (e.g Postgres) - const promises = Object.values(updates).map((update) => - prisma.locked.upsert({ - where: { - objectId: update.objectId, - }, - create: { - ...update, - }, - update, - }), - ); - await Promise.all(promises); -}; -``` - -This function handles all events emitted by the `lock` module. It takes an array of `SuiEvent` objects and a string representing the type of the events as arguments. The function processes each event and updates the corresponding locked object in the database accordingly. If an event indicates that a lock was destroyed, the function marks the corresponding locked object as deleted. If an event indicates that a lock was created, the function creates a new locked object with the details from the event. - -## API service - -As we mentioned earlier, we should expose the indexed data for external consumption through an API service. Particularly, the example uses [Express](https://expressjs.com/) to build a Node.js HTTP API. - -### API design - -#### Query parameters - -You want your API to accept the query string in the URL as the parameters for database `WHERE` query. Hence, you want a utility that can extract and parse the URL query string into valid query parameters for Prisma. With the `parseWhereStatement()` function, the callers filter the set of keys from the URL query string and transforms those corresponding key-value pairs into the correct format for Prisma. - -```ts title='utils/api-queries.ts' -export enum WhereParamTypes { - STRING, - NUMBER, - BOOLEAN, -} - -export type WhereParam = { - key: string; - type: WhereParamTypes; -}; - -/** Parses a where statement based on the query params. */ -export const parseWhereStatement = (query: Record, acceptedParams: WhereParam[]) => { - const params: Record = {}; - for (const key of Object.keys(query)) { - const whereParam = acceptedParams.find((x) => x.key === key); - if (!whereParam) continue; - - const value = query[key]; - if (whereParam.type === WhereParamTypes.STRING) { - params[key] = value; - } - if (whereParam.type === WhereParamTypes.NUMBER) { - const number = Number(value); - if (isNaN(number)) throw new Error(`Invalid number for ${key}`); - - params[key] = number; - } - - // Handle boolean expected values. - if (whereParam.type === WhereParamTypes.BOOLEAN) { - let boolValue; - if (value === 'true') boolValue = true; - else if (value === 'false') boolValue = false; - else throw new Error(`Invalid boolean for ${key}`); - - params[key] = boolValue; - } - } - return params; -}; -``` - -#### Query pagination - -Pagination is another crucial part to ensure your API returns sufficient and/or ordered chunk of information instead of all the data that might be the vector for a DDOS attack. Similar to **WHERE parameters**, define a set of keys in the URL query string to be accepted as valid pagination parameters. The `parsePaginationForQuery()` utility function helps to achieve this by filtering the pre-determined keys `sort`, `limit`, `cursor` and parsing corresponding key-value pairs into `ApiPagination` that Prisma can consume. - -In this example, the `id` field of the model in the database as the cursor that allows clients to continue subsequent queries with the next page. - -```ts title='utils/api-queries.ts' -export type ApiPagination = { - take?: number; - orderBy: { - id: 'asc' | 'desc'; - }; - cursor?: { - id: number; - }; - skip?: number; -}; - -/** - * A helper to prepare pagination based on `req.query`. - * Only primary key cursor + ordering for this example. - */ -export const parsePaginationForQuery = (body: Record) => { - const pagination: ApiPagination = { - orderBy: { - id: Object.hasOwn(body, 'sort') && ['asc', 'desc'].includes(body.sort) ? body.sort : 'desc', - }, - }; - - // Prepare pagination limit (how many items to return) - if (Object.hasOwn(body, 'limit')) { - const requestLimit = Number(body.limit); - - if (isNaN(requestLimit)) throw new Error('Invalid limit value'); - - pagination.take = requestLimit > CONFIG.DEFAULT_LIMIT ? CONFIG.DEFAULT_LIMIT : requestLimit; - } else { - pagination.take = CONFIG.DEFAULT_LIMIT; - } - - // Prepare cursor pagination (which page to return) - if (Object.hasOwn(body, 'cursor')) { - const cursor = Number(body.cursor); - if (isNaN(cursor)) throw new Error('Invalid cursor'); - pagination.skip = 1; - pagination.cursor = { - id: cursor, - }; - } - - return pagination; -}; -``` - -### API endpoints - -All the endpoints are defined in `server.ts`, particularly, there are two endpoints: - -- `/locked` to query `Locked` objects. -- `/escrows` to query `Escrow` objects. - -The implementation for both endpoints is pretty straightforward. You define a list of valid query keys, such as `deleted`, `creator`, `keyId`, and `objectId` for `Locked` data and `cancelled`, `swapped`, `recipient`, and `sender` for `Escrow` data. Pass the URL query string into the pre-defined utilities to output the correct parameters that Prisma can use. - -```ts title='server.ts' -import { prisma } from './db'; -import { - formatPaginatedResponse, - parsePaginationForQuery, - parseWhereStatement, - WhereParam, - WhereParamTypes, -} from './utils/api-queries'; - -app.get('/locked', async (req, res) => { - const acceptedQueries: WhereParam[] = [ - { - key: 'deleted', - type: WhereParamTypes.BOOLEAN, - }, - { - key: 'creator', - type: WhereParamTypes.STRING, - }, - { - key: 'keyId', - type: WhereParamTypes.STRING, - }, - { - key: 'objectId', - type: WhereParamTypes.STRING, - }, - ]; - - try { - const locked = await prisma.locked.findMany({ - where: parseWhereStatement(req.query, acceptedQueries)!, - ...parsePaginationForQuery(req.query), - }); - - return res.send(formatPaginatedResponse(locked)); - } catch (e) { - console.error(e); - return res.status(400).send(e); - } -}); - -app.get('/escrows', async (req, res) => { - const acceptedQueries: WhereParam[] = [ - { - key: 'cancelled', - type: WhereParamTypes.BOOLEAN, - }, - { - key: 'swapped', - type: WhereParamTypes.BOOLEAN, - }, - { - key: 'recipient', - type: WhereParamTypes.STRING, - }, - { - key: 'sender', - type: WhereParamTypes.STRING, - }, - ]; - - try { - const escrows = await prisma.escrow.findMany({ - where: parseWhereStatement(req.query, acceptedQueries)!, - ...parsePaginationForQuery(req.query), - }); - - return res.send(formatPaginatedResponse(escrows)); - } catch (e) { - console.error(e); - return res.status(400).send(e); - } -}); -``` - -## Next steps - -With the code successfully deployed on Testnet, you can now [create a frontend](./frontend.mdx) to display the trading data and to allow users to interact with the Move modules. diff --git a/docs/content/guides/developer/app-examples/weather-oracle.mdx b/docs/content/guides/developer/app-examples/weather-oracle.mdx index 3a40d1b7f7a48..92b823191a85f 100644 --- a/docs/content/guides/developer/app-examples/weather-oracle.mdx +++ b/docs/content/guides/developer/app-examples/weather-oracle.mdx @@ -430,7 +430,9 @@ First, initialize your backend project. To do this, you need to follow these ste - Create a new file named `init.ts` ```typescript title='init.ts' -import { Connection, Ed25519Keypair, JsonRpcProvider, RawSigner, Transaction } from '@mysten/sui'; +import { SuiClient } from '@mysten/sui/client'; +import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; +import { Transaction } from '@mysten/sui/transactions'; import * as dotenv from 'dotenv'; import { City } from './city'; @@ -444,12 +446,9 @@ dotenv.config({ path: '../.env' }); const phrase = process.env.ADMIN_PHRASE; const fullnode = process.env.FULLNODE!; const keypair = Ed25519Keypair.deriveKeypair(phrase!); -const provider = new JsonRpcProvider( - new Connection({ - fullnode: fullnode, - }), -); -const signer = new RawSigner(keypair, provider); +const client = new SuiClient({ + url: fullnode, +}); const packageId = process.env.PACKAGE_ID; const adminCap = process.env.ADMIN_CAP_ID!; @@ -462,7 +461,7 @@ async function addCityWeather() { const cities: City[] = await getCities(); const thousandGeoNameIds = await get1000Geonameids(); - const weatherOracleDynamicFields = await getWeatherOracleDynamicFields(provider, weatherOracleId); + const weatherOracleDynamicFields = await getWeatherOracleDynamicFields(client, weatherOracleId); const geonames = weatherOracleDynamicFields.map(function (obj) { return obj.name; }); @@ -502,8 +501,9 @@ async function addCityWeather() { async function signAndExecuteTransaction(transaction: Transaction) { transaction.setGasBudget(5000000000); - await signer + await client .signAndExecuteTransaction({ + signer: keypair, transaction, requestType: 'WaitForLocalExecution', options: { @@ -521,12 +521,11 @@ addCityWeather(); The code of init.ts does the following: -- Imports the necessary modules and classes from the library, such as `Connection`, `Ed25519Keypair`, `JsonRpcProvider`, `RawSigner`, and `Transaction`. +- Imports the necessary modules and classes from the library, such as `Ed25519Keypair`, `SuiClient`, and `Transaction`. - Imports the `dotenv` module to load environment variables from a .env file. - Imports some custom modules and functions from the local files, such as `City`, `get1000Geonameids`, `getCities`, `getWeatherOracleDynamicFields`, and `logger`. - Derives a key pair from a phrase stored in the `ADMIN_PHRASE` environment variable. -- Creates a provider object that connects to a Full node specified by the `FULLNODE` environment variable. -- Creates a signer object that uses the key pair and the provider to sign and execute transactions on the blockchain. +- Creates a sui client object that connects to a Full node specified by the `FULLNODE` environment variable. - Reads some other environment variables, such as `PACKAGE_ID`, `ADMIN_CAP_ID`, `WEATHER_ORACLE_ID`, and `MODULE_NAME`, which are used to identify the weather oracle contract and its methods. - Defines a constant `NUMBER_OF_CITIES`, which is the number of cities to be added to the weather oracle in each batch. - Defines an async function `addCityWeather`, which does the following: @@ -542,7 +541,9 @@ The code of init.ts does the following: You have now initialized the `WeatherOracle` shared object. The next step is to learn how to update them every 10 minutes with the latest weather data from the OpenWeatherMap API. ```typescript title='index.ts' -import { Connection, Ed25519Keypair, JsonRpcProvider, RawSigner, Transaction } from '@mysten/sui'; +import { SuiClient } from '@mysten/sui/client'; +import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; +import { Transaction } from '@mysten/sui/transactions'; import * as dotenv from 'dotenv'; import { City } from './city'; @@ -556,12 +557,9 @@ dotenv.config({ path: '../.env' }); const phrase = process.env.ADMIN_PHRASE; const fullnode = process.env.FULLNODE!; const keypair = Ed25519Keypair.deriveKeypair(phrase!); -const provider = new JsonRpcProvider( - new Connection({ - fullnode: fullnode, - }), -); -const signer = new RawSigner(keypair, provider); +const client = new SuiClient({ + url: fullnode, +}); const packageId = process.env.PACKAGE_ID; const adminCap = process.env.ADMIN_CAP_ID!; @@ -593,7 +591,8 @@ async function performUpdates( let transaction = await getTransaction(chunk); try { - await signer.signAndExecuteTransaction({ + await client.signAndExecuteTransaction({ + signer: keypair, transaction, }); } catch (e) { @@ -649,7 +648,7 @@ async function run() { const weatherOracleDynamicFields: { name: number; objectId: string; - }[] = await getWeatherOracleDynamicFields(provider, weatherOracleId); + }[] = await getWeatherOracleDynamicFields(client, weatherOracleId); performUpdates(cities, weatherOracleDynamicFields); } @@ -658,12 +657,12 @@ run(); The code in index.ts does the following: -- Uses `dotenv` to load some environment variables from a `.env` file, such as `ADMIN_PHRASE`, `FULLNODE`, `PACKAGE_ID`, `ADMIN_CAP_ID`, `WEATHER_ORACLE_ID`, `APPID`, and `MODULE_NAME`. These variables are used to configure some parameters for the code, such as the key pair, the provider, the signer, and the target package and module. +- Uses `dotenv` to load some environment variables from a `.env` file, such as `ADMIN_PHRASE`, `FULLNODE`, `PACKAGE_ID`, `ADMIN_CAP_ID`, `WEATHER_ORACLE_ID`, `APPID`, and `MODULE_NAME`. These variables are used to configure some parameters for the code, such as the key pair, the client, and the target package and module. - Defines some constants, such as `CHUNK_SIZE`, `MS`, `MINUTE`, and `TEN_MINUTES`. These constants are used to control the frequency and size of the updates that the code performs. - Defines an async function called `performUpdates`, which takes two arguments: `cities` and `weatherOracleDynamicFields`. This function is the main logic of the code, and it does the following: - Filters the `cities` array based on the `weatherOracleDynamicFields` array, which contains the names and object IDs of the weather oracle dynamic fields that the code needs to update. - Loops through the filtered cities in chunks of `CHUNK_SIZE`, and for each chunk, it calls another async function called `getTransaction`, which returns a transaction block that contains the Move calls to update the weather oracle dynamic fields with the latest weather data from the OpenWeatherMap API. - - Tries to sign and execute the transaction block using the signer, and catches any errors that may occur. + - Tries to sign and execute the transaction block using the client and keypair, and catches any errors that may occur. - Calculates the time it took to perform the updates, and sets a timeout to call itself again after `TEN_MINUTES` minus the elapsed time. - The code defines another async function called `getTransaction`, which takes one argument: `cities`. This function does the following: - Creates a new transaction block object. @@ -672,7 +671,7 @@ The code in index.ts does the following: - Returns the transaction block object. - An async `run` function is defined, which does the following: - Calls another async function called `getCities`, which returns an array of city objects that contain information such as name, geoname id, latitude, and longitude. - - Calls another async function called `getWeatherOracleDynamicFields`, which takes the package id, the module name, and the signer as arguments, and returns an array of weather oracle dynamic field objects that contain information such as name and object id. + - Calls another async function called `getWeatherOracleDynamicFields`, which takes the package id, the module name, and the client as arguments, and returns an array of weather oracle dynamic field objects that contain information such as name and object id. - Calls the `performUpdates` function with the cities and weather oracle dynamic fields arrays as arguments. Congratulations, you completed the Sui Weather Oracle tutorial. You can carry the lessons learned here forward when building your next Sui project. diff --git a/docs/content/guides/developer/coin.mdx b/docs/content/guides/developer/coin.mdx index 6f695e506ecc7..1157cf8861a7c 100644 --- a/docs/content/guides/developer/coin.mdx +++ b/docs/content/guides/developer/coin.mdx @@ -1,5 +1,6 @@ --- title: Create Coins and Tokens +description: Learn how to mint coins and tokens on the Sui network. --- Coins and tokens on Sui are similar. In practice, the terms are used interchangeably, but there are some differences in their implementation. You can learn about these differences in the respective standard documentation, [Closed-Loop Token](../../standards/closed-loop-token.mdx) and [Coin](../../standards/coin.mdx). @@ -68,4 +69,5 @@ See [Closed-Loop Token](../../standards/closed-loop-token.mdx) standard for comp - [Regulated Coin and Deny List](./coin/regulated.mdx): Create a regulated coin and add or remove names from the deny list. - [Loyalty Token](./coin/loyalty.mdx): Create a token to reward user loyalty. - [In-Game Token](./coin/in-game-token.mdx): Create tokens that can be used only within a mobile game. +- [Stablecoins](./stablecoins): The Sui network has native stablecoins, including USDC. - [One Time Witness](https://move-book.com/programmability/one-time-witness.html): The Move Book documentation of the one time witness pattern. diff --git a/docs/content/guides/developer/coin/in-game-token.mdx b/docs/content/guides/developer/coin/in-game-token.mdx index 800fd0f8cbe34..d7242f7330fa5 100644 --- a/docs/content/guides/developer/coin/in-game-token.mdx +++ b/docs/content/guides/developer/coin/in-game-token.mdx @@ -32,7 +32,7 @@ Use the following toggle to control the display of the complete module.
-Toggle complete module code +`examples::gem` module in `gems.move` {@inject: examples/move/token/sources/gems.move#module=examples::gem noComments} @@ -45,7 +45,7 @@ Toggle display of the complete source for this example, including comments, or u
-Toggle complete source code +`gems.move` {@inject: examples/move/token/sources/gems.move} diff --git a/docs/content/guides/developer/coin/loyalty.mdx b/docs/content/guides/developer/coin/loyalty.mdx index dbd553a3d2b46..6bf48ef1342c6 100644 --- a/docs/content/guides/developer/coin/loyalty.mdx +++ b/docs/content/guides/developer/coin/loyalty.mdx @@ -34,7 +34,7 @@ Toggle display of the complete source for this example, including comments, or u
-Toggle complete source code +`loyalty.move` {@inject: examples/move/token/sources/loyalty.move} diff --git a/docs/content/guides/developer/first-app/build-test.mdx b/docs/content/guides/developer/first-app/build-test.mdx index e90cdeb109780..c14b79057a8d5 100644 --- a/docs/content/guides/developer/first-app/build-test.mdx +++ b/docs/content/guides/developer/first-app/build-test.mdx @@ -205,7 +205,7 @@ You can refer to the source code for the package (with all the tests and functio
-Toggle complete source code +`example.move` {@inject: examples/move/first_package/sources/example.move} diff --git a/docs/content/guides/developer/getting-started/get-coins.mdx b/docs/content/guides/developer/getting-started/get-coins.mdx index 26bc17f2da942..f9d9a128e32ce 100644 --- a/docs/content/guides/developer/getting-started/get-coins.mdx +++ b/docs/content/guides/developer/getting-started/get-coins.mdx @@ -31,7 +31,7 @@ You can request test tokens within [Sui Wallet](https://github.com/MystenLabs/my Use the following cURL command to request tokens directly from the faucet server: ``` -curl --location --request POST 'https://faucet.devnet.sui.io/gas' \ +curl --location --request POST 'https://faucet.devnet.sui.io/v1/gas' \ --header 'Content-Type: application/json' \ --data-raw '{ "FixedAmountRequest": { @@ -40,7 +40,7 @@ curl --location --request POST 'https://faucet.devnet.sui.io/gas' \ }' ``` -If you're working with a local network, replace `'https://faucet.devnet.sui.io/gas'` with the appropriate value based on which package runs your network: +If you're working with a local network, replace `'https://faucet.devnet.sui.io/v1/gas'` with the appropriate value based on which package runs your network: - `sui-faucet`: `http://127.0.0.1:5003/gas` - `sui`: `http://127.0.0.1:9123/gas` diff --git a/docs/content/guides/developer/getting-started/graphql-rpc.mdx b/docs/content/guides/developer/getting-started/graphql-rpc.mdx index 93f1b805765bd..428d141db5cf8 100644 --- a/docs/content/guides/developer/getting-started/graphql-rpc.mdx +++ b/docs/content/guides/developer/getting-started/graphql-rpc.mdx @@ -161,7 +161,7 @@ This example finds the balance changes of all the transactions where a given add query ($address: SuiAddress!) { transactionBlocks(filter: { function: "0x3::sui_system::request_add_stake" - signAddress: $address + sentAddress: $address }) { nodes { digest @@ -370,7 +370,7 @@ Examples in the repository are designed to work with the version of GraphQL buil ## Related links - [GraphQL migration](../advanced/graphql-migration.mdx): Migrating to GraphQL guides you through migrating Sui RPC projects from JSON-RPC to GraphQL. -- [GraphQL concepts](../../../concepts/graphql-rpc.mdx): GraphQL for Sui RPC examines the elements of GraphQL that you should know to get the most from the service. +- [GraphQL concepts](../../../concepts/graphql-rpc.mdx): GraphQL for Sui RPC examines the elements of GraphQL that you should know to get the most from the service. - [GraphQL reference](../../../references/sui-graphql.mdx): Auto-generated GraphQL reference for Sui RPC. -- [Sui Testnet GraphiQL](https://sui-testnet.mystenlabs.com/graphql): Sui GraphiQL IDE for the Testnet network. +- [Sui Testnet GraphiQL](https://sui-testnet.mystenlabs.com/graphql): Sui GraphiQL IDE for the Testnet network. - [Sui Mainnet GraphiQL](https://sui-mainnet.mystenlabs.com/graphql): Sui GraphiQL IDE for the Mainnet network. diff --git a/docs/content/guides/developer/images/stablecoinsuccess.png b/docs/content/guides/developer/images/stablecoinsuccess.png new file mode 100644 index 0000000000000000000000000000000000000000..2c8262ffb9516793a9fa370d07badf8f99a91327 GIT binary patch literal 27336 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 literal 0 HcmV?d00001 diff --git a/docs/content/guides/developer/images/stablecoinui.png b/docs/content/guides/developer/images/stablecoinui.png new file mode 100644 index 0000000000000000000000000000000000000000..6e4887505f200fb0a6c99c0bda5932bee0f5f1c8 GIT binary patch literal 20020 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 b6MyV^SAKWARXD#4bYh#QtDnm{r-UW|d$jim literal 0 HcmV?d00001 diff --git a/docs/content/guides/developer/nft.mdx b/docs/content/guides/developer/nft.mdx index adebeee1cbe81..451574e4f13f6 100644 --- a/docs/content/guides/developer/nft.mdx +++ b/docs/content/guides/developer/nft.mdx @@ -1,6 +1,7 @@ --- title: Create a Non-Fungible Token keywords: [ERC-721, NFT] +description: On Sui, everything is an object. Moreover, everything is a non-fungible token (NFT) as its objects are unique, non-fungible, and owned. --- On Sui, everything is an object. Moreover, everything is a non-fungible token (NFT) as its objects are unique, non-fungible, and owned. @@ -27,7 +28,7 @@ The module includes functions to return NFT metadata, too. Referencing the hypot
-Toggle complete source code +`testnet_nft.move` {@inject: examples/move/nft/sources/testnet_nft.move} diff --git a/docs/content/guides/developer/nft/asset-tokenization.mdx b/docs/content/guides/developer/nft/asset-tokenization.mdx index 9621edb3039ff..4c27ec6ea3a25 100644 --- a/docs/content/guides/developer/nft/asset-tokenization.mdx +++ b/docs/content/guides/developer/nft/asset-tokenization.mdx @@ -527,7 +527,7 @@ User->>User's Kiosk: Resolve Promise by returning Tokenized Asset X in user's Ki The following sequence diagram shows the burn flow and assumes that: -- Tokenized asset has already been minted by the creator of its type. +- Tokenized asset has already been minted by the creator of its type. - Tokenized asset is already placed and locked inside the user's Kiosk. - Everything is executed in the same PTB. @@ -808,7 +808,7 @@ xxd -c 0 -p build/template/bytecode_modules/template.mv | head -n 1
- Toggle response + Console response The response you should receive looks similar to the following: @@ -908,7 +908,7 @@ Lastly, to deploy the changed template module, build and publish: const tx = new Transaction(); tx.setGasBudget(100000000); const [upgradeCap] = tx.publish({ - modules: [[...fromHEX(bytesToPublish)], [...fromHEX(genesis_bytecode)]], + modules: [[...fromHex(bytesToPublish)], [...fromHex(genesis_bytecode)]], dependencies: [ normalizeSuiObjectId("0x1"), normalizeSuiObjectId("0x2"), diff --git a/docs/content/guides/developer/stablecoins.mdx b/docs/content/guides/developer/stablecoins.mdx new file mode 100644 index 0000000000000..2457d8d872c43 --- /dev/null +++ b/docs/content/guides/developer/stablecoins.mdx @@ -0,0 +1,234 @@ +--- +title: Stablecoins on Sui +sidebar_label: Stablecoins +description: Stablecoins are a type of cryptocurrency that are designed to maintain a stable value relative to a fiat currency or a basket of assets. +--- + +Stablecoins are a type of cryptocurrency that are designed to maintain a stable value relative to a fiat currency or a basket of assets. They are widely used for trading, lending, and as a store of value. + +## Available stablecoins + +On Sui, you can interact with various stablecoins such as USDC, USDT, Agora, and Ondo USDY. + +### USDC (USD Coin) + +USDC is a fully collateralized US dollar stablecoin issued by regulated financial institutions. Each USDC token is backed by one US dollar held in reserve. USDC is widely used for trading, payments, and as a stable store of value. + +For more detailed information on how to interact with USDC on Sui, refer to the [USDC guide](#usdc-guide). + +**Site:** [Circle](https://www.circle.com/en/usdc) + +### USDT (Tether) + +USDT, also known as Tether, is one of the oldest and most widely used stablecoins. It is pegged to the US dollar and is backed by a mix of reserves, including cash, cash equivalents, and other assets. + +USDT is currently not issued natively on Sui. For more information on bridging USDT to Sui, refer to [SUI Bridging](../../concepts/tokenomics/sui-bridging.mdx). + +**Site:** [Tether](https://tether.to/) + +### Agora + +AUSD is a fully collateralized US dollar stablecoin issued by Agora Finance. + +**Site:** [Agora Finance](https://www.agora.finance/) + +### Ondo USDY +USDY is a fully collateralized US dollar stablecoin issued by Ondo Finance, allowing users to earn yield from US Treasury Bills. + +**Site:** [Ondo Finance](https://ondo.finance/) + +## How to transfer USDC on Sui {#usdc-guide} + +USDC provides the ability to transfer dollars over public blockchains using smart contracts. The smart contract allows users to send, receive, and store dollars on chain with a wallet. + +Here's a quickstart guide for developers to build an app to perform their first USDC transfer on Sui. + +### Contract addresses + +- [Testnet](https://suiscan.xyz/testnet/coin/0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC) + ``` + 0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC + ``` +- [Mainnet](https://suiscan.xyz/coin/0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC) + ``` + 0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC + ``` + +### Prerequisites + +Before you start building the sample app to perform a USDC transfer, ensure you meet the following prerequisites: + +- [Node.js](https://nodejs.org/en) and [npm](https://www.npmjs.com/): Ensure that you have Node.js and npm installed on your machine. You can download and install Node.js from [nodejs.org](http://nodejs.org). npm comes with Node.js. +- [Sui Wallet](https://chromewebstore.google.com/detail/sui-wallet/opcgpfmipidbgpenhmajoajpbobppdil): Install the Sui Wallet browser extension, set up your wallet, and connect it to the appropriate network. Ensure that your wallet is funded with: + - Some SUI tokens to cover transaction fees. (**Request Testnet SUI Tokens** from the **Wallet Settings** page) + - USDC tokens for the transfer. ([USDC Testnet Faucet](https://faucet.circle.com/)) + +### Installation + +To begin, create a new project directory (`usdc-transfer-app`, for example) and initialize it with pnpm: + +```bash +pnpm init +``` + +To keep a small footprint, the instruction uses [Parcel](https://parceljs.org/) to render the example React-based app. You can use a more robust framework, like Next.js, or a different package manager, but you must modify the steps appropriately. + +```bash +pnpm add react react-dom parcel +``` + +Install Sui dependencies next: + +```bash +pnpm add @mysten/dapp-kit @mysten/sui @tanstack/react-query +``` + +Finally, update your `package.json` to include the following script: + +```json +"scripts": { + "start": "parcel index.html" + }, +``` + +### Create site structure + +At the root of your project directory, create an `index.html` file and save the following content to it: + +
+ +`index.html` file + +{@inject: examples/usdc-transfer-app/index.html} +
+ +Next, create and save an `index.js` file in the same location with the following code: + +
+ +`index.js` file + +{@inject: examples/usdc-transfer-app/index.js} +
+ +To style the output for the site, create a `global.css` file in the root of your project. Copy and paste the following minimal styling into the file and save: + +
+ +`global.css` file + +{@inject: examples/usdc-transfer-app/global.css} +
+ +Finally, create an `App.js` file at the project root. When complete, this file will hold the logic for the USDC transfer. For now, just create the file to display some basic text. This example does not use TypeScript to keep the instruction focused, but you can certainly use it in your own projects. + +
+ +`App.js` file + +{@inject: examples/usdc-transfer-app/lib/App-stub.js noComments noTitle} +
+ +Start the development server to make sure the default app runs using the following command in the root of your project: + +```bash +pnpm start +``` + +Open a browser to `localhost:1234` to make sure the Hello World text appears. + +### Import code and setup + +Open the `App.js` file and make the changes that follow. + +First, import necessary libraries and set up the network configuration. + +{@inject: examples/usdc-transfer-app/App.js#setup} + +This component handles the main functionality of the application, including wallet connection, defining token, and token transfer. + +#### Define USDC Testnet token contract + +{@inject: examples/usdc-transfer-app/App.js#variable=USDC_TYPE} + +#### State management + +Create a `HomeContent()` function that is going to be responsible for setting up and displaying the UI. At the top of this function, define state variables to manage the connection status, amount, recipient address, and transaction status. + +{@inject: examples/usdc-transfer-app/App.js#state} + +**Effect Hook for Connection Status** + +Use `useEffect` to update the connection status whenever the `currentAccount` value changes. + +{@inject: examples/usdc-transfer-app/App.js#useeffect} + +#### Token sending logic + +Define the function to handle sending tokens, including validation and transaction execution. + +{@inject: examples/usdc-transfer-app/App.js#variable=handleSendTokens} + +#### Rendering the UI + +Render the main UI components, including input fields for amount and recipient address, and a button to send tokens. + +{@inject: examples/usdc-transfer-app/App.js#ui} + +#### Complete HomeContent function + +After adding the UI components, your `HomeContent()` function is complete. Continue to the next section to add the function to the main application component. + +
+ +Complete `HomeContent()` function + +{@inject: examples/usdc-transfer-app/App.js#component=HomeContent} +
+ +### Main application component + +This component wraps the `HomeContent()` with necessary providers for state management and wallet connection. + +{@inject: examples/usdc-transfer-app/App.js#component=App} + +Save your `App.js` file and run `pnpm start` from your project root to start the development server (if not already running). + +Open [http://localhost:1234](http://localhost:1234) in your browser. Your UI should display and look similar to the following: + +![Stablecoin UI](./images/stablecoinui.png) + +
+ +Complete code for `App.js` + +{@inject: examples/usdc-transfer-app/App.js noComments} +
+ +### Connecting your wallet + +To connect your wallet: + +1. On the USDC Token Sender app, click the **Connect Wallet** button. +1. Select your wallet from the list of available options. +1. Approve the connection in your wallet extension. + + +### Performing a USDC transfer + +Follow these steps to perform a USDC transfer: + +1. Ensure you have USDC tokens in your wallet. You can get Testnet tokens from Circle's [faucet](https://faucet.circle.com/) if needed. +1. Click the **Request Testnet SUI Tokens** button from your Sui Wallet to source gas tokens. The button is visible only when your wallet is connected to Testnet. +1. In the app, enter the amount of USDC you want to send and the recipient address. +1. Click the **Send Tokens** button. +1. Your wallet prompts you to approve the transaction. Review the details and confirm. +1. Wait for the transaction to be processed. The app displays the transaction status. A successful transaction looks like the following: +
![Successful transaction](./images/stablecoinsuccess.png)
+ + +## Related links + +- [Regulated Coin and Deny List](./coin/regulated.mdx): Create a regulated coin and add or remove names from the deny list. +- [Loyalty Token](./coin/loyalty.mdx): Create a token to reward user loyalty. +- [In-Game Token](./coin/in-game-token.mdx): Create tokens that can be used only within a mobile game. \ No newline at end of file diff --git a/docs/content/guides/developer/sui-101/building-ptb.mdx b/docs/content/guides/developer/sui-101/building-ptb.mdx index ede87b1477098..55347e8d1bc0a 100644 --- a/docs/content/guides/developer/sui-101/building-ptb.mdx +++ b/docs/content/guides/developer/sui-101/building-ptb.mdx @@ -10,7 +10,7 @@ This example starts by constructing a PTB to send Sui. If you are familiar with ```ts import { Transaction } from '@mysten/sui/transactions'; -const txb = new Transaction(); +const tx = new Transaction(); ``` Using this, you can then add transactions to this PTB. @@ -18,10 +18,10 @@ Using this, you can then add transactions to this PTB. ```ts // Create a new coin with balance 100, based on the coins used as gas payment. // You can define any balance here. -const [coin] = txb.splitCoins(txb.gas, [txb.pure(100)]); +const [coin] = tx.splitCoins(tx.gas, [tx.pure(100)]); // Transfer the split coin to a specific address. -txb.transferObjects([coin], txb.pure('0xSomeSuiAddress')); +tx.transferObjects([coin], tx.pure('0xSomeSuiAddress')); ``` You can attach multiple transaction commands of the same type to a PTB as well. For example, to get a list of transfers, and iterate over them to transfer coins to each of them: @@ -35,24 +35,24 @@ interface Transfer { // Procure a list of some Sui transfers to make: const transfers: Transfer[] = getTransfers(); -const txb = new Transaction(); +const tx = new Transaction(); // First, split the gas coin into multiple coins: -const coins = txb.splitCoins( - txb.gas, - transfers.map((transfer) => txb.pure(transfer.amount)), +const coins = tx.splitCoins( + tx.gas, + transfers.map((transfer) => tx.pure(transfer.amount)), ); // Next, create a transfer transaction for each coin: transfers.forEach((transfer, index) => { - txb.transferObjects([coins[index]], txb.pure(transfer.to)); + tx.transferObjects([coins[index]], tx.pure(transfer.to)); }); ``` -After you have the PTB defined, you can directly execute it with a `signer` using `signAndExecuteTransaction`. +After you have the Transaction defined, you can directly execute it with a `SuiClient` and `KeyPair` using `client.signAndExecuteTransaction`. ```ts -signer.signAndExecuteTransaction({ transaction: txb }); +client.signAndExecuteTransaction({ signer: keypair, transaction: tx }); ``` ## Constructing inputs @@ -61,8 +61,8 @@ Inputs are how you provide external values to PTBs. For example, defining an amo There are currently two ways to define inputs: -- For objects: the `txb.object(objectId)` function is used to construct an input that contains an object reference. -- For pure values: the `txb.pure(value, type?)` function is used to construct an input for a non-object input. +- For objects: the `tx.object(objectId)` function is used to construct an input that contains an object reference. +- For pure values: the `tx.pure(value, type?)` function is used to construct an input for a non-object input. - If value is a `Uint8Array`, then the value is assumed to be raw bytes and is used directly. - If type is provided, it's used to generate the BCS serialization layout for the value. If not provided, the type is automatically determined based on the value. @@ -70,17 +70,17 @@ There are currently two ways to define inputs: Sui supports following transaction commands: -- `txb.splitCoins(coin, amounts)`: Creates new coins with the defined amounts, split from the provided coin. Returns the coins so that it can be used in subsequent transactions. - - Example: `txb.splitCoins(txb.gas, [txb.pure(100), txb.pure(200)])` -- `txb.mergeCoins(destinationCoin, sourceCoins)`: Merges the sourceCoins into the destinationCoin. - - Example: `txb.mergeCoins(txb.object(coin1), [txb.object(coin2), txb.object(coin3)])` -- `txb.transferObjects(objects, address)`: Transfers a list of objects to the specified address. - - Example: `txb.transferObjects([txb.object(thing1), txb.object(thing2)], txb.pure(myAddress))` -- `txb.moveCall({ target, arguments, typeArguments })`: Executes a Move call. Returns whatever the Sui Move call returns. - - Example: `txb.moveCall({ target: '0x2::devnet_nft::mint', arguments: [txb.pure(name), txb.pure(description), txb.pure(image)] })` -- `txb.makeMoveVec({ type, elements })`: Constructs a vector of objects that can be passed into a moveCall. This is required as there's no other way to define a vector as an input. - - Example: `txb.makeMoveVec({ elements: [txb.object(id1), txb.object(id2)] })` -- `txb.publish(modules, dependencies)`: Publishes a Move package. Returns the upgrade capability object. +- `tx.splitCoins(coin, amounts)`: Creates new coins with the defined amounts, split from the provided coin. Returns the coins so that it can be used in subsequent transactions. + - Example: `tx.splitCoins(tx.gas, [tx.pure(100), tx.pure(200)])` +- `tx.mergeCoins(destinationCoin, sourceCoins)`: Merges the sourceCoins into the destinationCoin. + - Example: `tx.mergeCoins(tx.object(coin1), [tx.object(coin2), tx.object(coin3)])` +- `tx.transferObjects(objects, address)`: Transfers a list of objects to the specified address. + - Example: `tx.transferObjects([tx.object(thing1), tx.object(thing2)], tx.pure(myAddress))` +- `tx.moveCall({ target, arguments, typeArguments })`: Executes a Move call. Returns whatever the Sui Move call returns. + - Example: `tx.moveCall({ target: '0x2::devnet_nft::mint', arguments: [tx.pure(name), tx.pure(description), tx.pure(image)] })` +- `tx.makeMoveVec({ type, elements })`: Constructs a vector of objects that can be passed into a moveCall. This is required as there's no other way to define a vector as an input. + - Example: `tx.makeMoveVec({ elements: [tx.object(id1), tx.object(id2)] })` +- `tx.publish(modules, dependencies)`: Publishes a Move package. Returns the upgrade capability object. ## Passing transaction results as arguments @@ -88,26 +88,26 @@ You can use the result of a transaction command as an argument in subsequent tra ```ts // Split a coin object off of the gas object: -const [coin] = txb.splitCoins(txb.gas, [txb.pure(100)]); +const [coin] = tx.splitCoins(tx.gas, [tx.pure(100)]); // Transfer the resulting coin object: -txb.transferObjects([coin], txb.pure(address)); +tx.transferObjects([coin], tx.pure(address)); ``` When a transaction command returns multiple results, you can access the result at a specific index either using destructuring, or array indexes. ```ts // Destructuring (preferred, as it gives you logical local names): -const [nft1, nft2] = txb.moveCall({ target: '0x2::nft::mint_many' }); -txb.transferObjects([nft1, nft2], txb.pure(address)); +const [nft1, nft2] = tx.moveCall({ target: '0x2::nft::mint_many' }); +tx.transferObjects([nft1, nft2], tx.pure(address)); // Array indexes: -const mintMany = txb.moveCall({ target: '0x2::nft::mint_many' }); -txb.transferObjects([mintMany[0], mintMany[1]], txb.pure(address)); +const mintMany = tx.moveCall({ target: '0x2::nft::mint_many' }); +tx.transferObjects([mintMany[0], mintMany[1]], tx.pure(address)); ``` ## Use the gas coin -With PTBs, you can use the gas payment coin to construct coins with a set balance using `splitCoin`. This is useful for Sui payments, and avoids the need for up-front coin selection. You can use `txb.gas` to access the gas coin in a PTB, and it is valid as input for any arguments; with the exception of `transferObjects`, `txb.gas` must be used by-reference. Practically speaking, this means you can also add to the gas coin with `mergeCoins` or borrow it for Move functions with `moveCall`. +With PTBs, you can use the gas payment coin to construct coins with a set balance using `splitCoin`. This is useful for Sui payments, and avoids the need for up-front coin selection. You can use `tx.gas` to access the gas coin in a PTB, and it is valid as input for any arguments; with the exception of `transferObjects`, `tx.gas` must be used by-reference. Practically speaking, this means you can also add to the gas coin with `mergeCoins` or borrow it for Move functions with `moveCall`. You can also transfer the gas coin using `transferObjects`, in the event that you want to transfer all of your coin balance to another address. @@ -122,11 +122,11 @@ You might need to explicitly call `setSender()` on the PTB to ensure that the `s ::: ```ts -const txb = new Transaction(); +const tx = new Transaction(); // ... add some transactions... -await txb.build({ provider }); +await tx.build({ provider }); ``` In most cases, building requires your JSON RPC provider to fully resolve input values. @@ -135,7 +135,7 @@ If you have PTB bytes, you can also convert them back into a `Transaction` class ```ts const bytes = getTransactionBytesFromSomewhere(); -const txb = Transaction.from(bytes); +const tx = Transaction.from(bytes); ``` ## Building offline @@ -146,13 +146,13 @@ In the event that you want to build a PTB offline (as in with no `provider` requ import { Inputs } from '@mysten/sui/transactions'; // For pure values: -txb.pure(pureValueAsBytes); +tx.pure(pureValueAsBytes); // For owned or immutable objects: -txb.object(Inputs.ObjectRef({ digest, objectId, version })); +tx.object(Inputs.ObjectRef({ digest, objectId, version })); // For shared objects: -txb.object(Inputs.SharedObjectRef({ objectId, initialSharedVersion, mutable })); +tx.object(Inputs.SharedObjectRef({ objectId, initialSharedVersion, mutable })); ``` You can then omit the `provider` object when calling `build` on the transaction. If there is any required data that is missing, this will throw an error. @@ -166,7 +166,7 @@ The new transaction builder comes with default behavior for all gas logic, inclu By default, the gas price is set to the reference gas price of the network. You can also explicitly set the gas price of the PTB by calling `setGasPrice` on the transaction builder. ```ts -txb.setGasPrice(gasPrice); +tx.setGasPrice(gasPrice); ``` ### Budget @@ -180,7 +180,7 @@ The gas budget is represented in Sui, and should take the gas price of the PTB i ::: ```ts -txb.setGasBudget(gasBudgetAmount); +tx.setGasBudget(gasBudgetAmount); ``` ### Gas payment @@ -192,14 +192,16 @@ The list of coins used as gas payment will be merged down into a single gas coin ```ts // NOTE: You need to ensure that the coins do not overlap with any // of the input objects for the PTB. -txb.setGasPayment([coin1, coin2]); +tx.setGasPayment([coin1, coin2]); ``` +Gas coins should be objects containing the coins objectId, version, and digest (ie `{ objectId: string, version: string | number, digest: string }`). + ### dApp / Wallet integration The Wallet Standard interface has been updated to support the `Transaction` kind directly. All `signTransaction` and `signAndExecuteTransaction` calls from dApps into wallets is expected to provide a `Transaction` class. This PTB class can then be serialized and sent to your wallet for execution. -To serialize a PTB for sending to a wallet, Sui recommends using the `txb.serialize()` function, which returns an opaque string representation of the PTB that can be passed from the wallet standard dApp context to your wallet. This can then be converted back into a `Transaction` using `Transaction.from()`. +To serialize a PTB for sending to a wallet, Sui recommends using the `tx.serialize()` function, which returns an opaque string representation of the PTB that can be passed from the wallet standard dApp context to your wallet. This can then be converted back into a `Transaction` using `Transaction.from()`. :::tip @@ -228,16 +230,16 @@ function handleSignRequest(input) { The PTB builder can support sponsored PTBs by using the `onlyTransactionKind` flag when building the PTB. ```ts -const txb = new Transaction(); +const tx = new Transaction(); // ... add some transactions... -const kindBytes = await txb.build({ provider, onlyTransactionKind: true }); +const kindBytes = await tx.build({ provider, onlyTransactionKind: true }); // Construct a sponsored transaction from the kind bytes: -const sponsoredTxb = Transaction.fromKind(kindBytes); +const sponsoredTx = Transaction.fromKind(kindBytes); // You can now set the sponsored transaction data that is required: -sponsoredTxb.setSender(sender); -sponsoredTxb.setGasOwner(sponsor); -sponsoredTxb.setGasPayment(sponsorCoins); +sponsoredTx.setSender(sender); +sponsoredTx.setGasOwner(sponsor); +sponsoredTx.setGasPayment(sponsorCoins); ``` diff --git a/docs/content/guides/developer/sui-101/shared-owned.mdx b/docs/content/guides/developer/sui-101/shared-owned.mdx index 80459470f35f0..779fd6e96ec53 100644 --- a/docs/content/guides/developer/sui-101/shared-owned.mdx +++ b/docs/content/guides/developer/sui-101/shared-owned.mdx @@ -40,7 +40,7 @@ The important property that this interface provides is that locked values cannot
-Toggle code sample +`owned.move` {@inject: examples/trading/contracts/escrow/sources/owned.move}
@@ -127,7 +127,7 @@ The `swap` function checks that senders and recipients match and that each party
-Toggle code sample +`shared.move` {@inject: examples/trading/contracts/escrow/sources/shared.move}
diff --git a/docs/content/guides/developer/sui-101/sign-and-send-txn.mdx b/docs/content/guides/developer/sui-101/sign-and-send-txn.mdx index 60b11fd269ead..1b64c72adca5d 100644 --- a/docs/content/guides/developer/sui-101/sign-and-send-txn.mdx +++ b/docs/content/guides/developer/sui-101/sign-and-send-txn.mdx @@ -40,7 +40,7 @@ The following examples demonstrate how to sign and execute transactions using Ru There are various ways to instantiate a key pair and to derive its public key and Sui address using the Sui TypeScript SDK. ```tsx -import { fromHEX } from '@mysten/bcs'; +import { fromHex } from '@mysten/bcs'; import { getFullnodeUrl, SuiClient } from '@mysten/sui/client'; import { type Keypair } from '@mysten/sui/cryptography'; import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; @@ -238,7 +238,7 @@ sui keytool import "$MNEMONICS" secp256k1 sui keytool import "$MNEMONICS" secp256r1 ``` -Create a transfer transaction in CLI. Set the $SUI_ADDRESS to the one corresponding to the keypair used to sign. $GAS_COIN_ID refers to the object ID that is owned by the sender to be used as gas. $GAS_BUDGET refers to the budget used to execute transaction. Then sign with the private key corresponding to the sender address. $MNEMONICS refers to 12/15/18/21/24 words from the wordlist, e.g. "retire skin goose will hurry this field stadium drastic label husband venture cruel toe wire". Refer to [Keys and Addresses](/concepts/cryptography/transaction-auth/keys-addresses.mdx) for more. +Create a transfer transaction in the CLI. Set the `$SUI_ADDRESS` to the one corresponding to the keypair used to sign. `$GAS_COIN_ID` refers to the object ID that is owned by the sender to be used as gas. `$GAS_BUDGET` refers to the budget used to execute transaction. Then sign with the private key corresponding to the sender address. `$MNEMONICS` refers to 12/15/18/21/24 words from the wordlist, e.g. "retire skin goose will hurry this field stadium drastic label husband venture cruel toe wire". Refer to [Keys and Addresses](/concepts/cryptography/transaction-auth/keys-addresses.mdx) for more. {@include: ../../../snippets/info-gas-budget.mdx} diff --git a/docs/content/guides/developer/sui-101/sponsor-txn.mdx b/docs/content/guides/developer/sui-101/sponsor-txn.mdx index 9431791765373..298c00200882e 100644 --- a/docs/content/guides/developer/sui-101/sponsor-txn.mdx +++ b/docs/content/guides/developer/sui-101/sponsor-txn.mdx @@ -2,12 +2,9 @@ title: Sponsored Transaction --- -Sponsored transactions are a primitive on the Sui blockchain that enable the execution of a transaction without a user paying the gas. It also discusses the roles in Sponsored Transaction, and a few common use cases. Then it discusses the flow of Sponsored Transaction, mostly for developers who are interested in building a Gas Station or integrate with one. Finally it talks about risk considerations of Sponsored Transaction. +A transaction on Sui takes a payment to execute. The payment, also known as gas, is a list of `0x2::coin::Coin<0x2::sui::Sui>` objects. Although gas is a critical piece in Sui tokenomics, it sometimes adds challenges when new Web3 users start to navigate on Sui. -# Overview -A transaction on Sui takes a payment to execute. The payment, also known as gas, is a list of `0x2::coin::Coin<0x2::sui::Sui>` objects, and paid to Sui validators to secure the network. Although gas is a critical piece in Sui tokenomics, it sometimes adds challenges when new users start to navigate on Sui, especially for web 2.0 users. - -Sponsored transactions can reduce the onboarding friction for users because the feature streamlines the process for end users. Using sponsored transactions, you can execute a transaction without requiring the user to pay it themselves. Instead, you can act as a sponsor of the transaction, offering your own payment gas objects for the transaction. +Sponsored transactions are a primitive on the Sui blockchain that enable the execution of a transaction without a user paying the gas. Sponsored transactions can reduce the onboarding friction for users because the feature streamlines the process for end users. Using sponsored transactions, you can execute a transaction without requiring the user to pay it themselves. Instead, you can act as a sponsor of the transaction, offering your own payment gas objects for the transaction. ## Roles in sponsored transactions diff --git a/docs/content/guides/developer/sui-101/using-events.mdx b/docs/content/guides/developer/sui-101/using-events.mdx index 00797838a7d7d..18e0e3cee339a 100644 --- a/docs/content/guides/developer/sui-101/using-events.mdx +++ b/docs/content/guides/developer/sui-101/using-events.mdx @@ -1,11 +1,13 @@ --- title: Using Events -description: Monitor Sui on-chain activity by subscribing to events that Move packages published on Sui emit. +description: Use events to notify on-chain assets of activity your smart contracts initiate and query events from other packages to trigger logic based on emitted events. --- The Sui network stores countless objects on chain where Move code can perform actions using those objects. Tracking this activity is often desired, for example, to discover how many times a module mints an NFT or to tally the amount of SUI in transactions that a smart contract generates. -To support activity monitoring, Move provides a structure to emit events on the Sui network. When you establish a connection with the Sui network, you create a two-way interactive communication session between your client and a Sui network node. With an open session, you can subscribe to specific events that the Sui network adds to the stream to create real-time monitoring of events. +To support activity monitoring, Move provides a structure to emit events on the Sui network. You can then leverage a custom indexer to process checkpoint data that includes events that have been emitted. See the [custom indexer](../advanced/custom-indexer.mdx) topic in the Advanced section to learn how to stream checkpoints and filter events continuously. + +If you don't want to run a custom indexer, you can poll the Sui network to query for emitted events instead. This approach typically includes a database to store the data retrieved from these calls. The [Poll events](#poll-events) section provides an example of using this method. ## Move event structure @@ -20,14 +22,6 @@ An event object in Sui consists of the following attributes: - `bcs`: Binary canonical serialization value. - `timestampMs`: Unix epoch timestamp in milliseconds. -## Discovering events - -If you want to subscribe to events on chain, you first need to know what events are available. You typically know or can discover the events your own code emits, but it's not as straightforward when you need to subscribe to on-chain events from packages you don't own. The Sui RPC provides a [queryEvents](/sui-api-ref#suix_queryEvents) method to query on-chain packages and return available events that you can subscribe to. - -## Filter events - -You can filter the events your code targets for either querying or subscribing. Both filter options are similar but have some differences. - ## Emit events in Move To create an event in your Move modules, add the `sui::event` dependency. @@ -36,226 +30,242 @@ To create an event in your Move modules, add the `sui::event` dependency. use sui::event; ``` -With the dependency added, you can use the `emit` function to fire an event whenever the action you want to monitor fires. For example, the following code is part of an example application using digital donuts. The `collect_profits` function handles the collection of SUI and emits an event whenever the function is called. To act on that event, you need to subscribe to it. - -```move -/// Take coin from `DonutShop` and transfer it to tx sender. -/// Requires authorization with `ShopOwnerCap`. -public fun collect_profits( _: &ShopOwnerCap, shop: &mut DonutShop, ctx: &mut TxContext ) { - let amount = balance::value(&shop.balance); - let profits = coin::take(&mut shop.balance, amount, ctx); - // simply create new type instance and emit it. - event::emit(ProfitsCollected { amount }); - transfer::public_transfer(profits, tx_context::sender(ctx)); -} -``` - -## Subscribe to events in Move - -Firing events is not very useful in a vacuum. You also need the ability to listen for those events so that you can act on them. In Sui, you subscribe to those events and provide logic that triggers when the event fires. - -Sui Full nodes support subscribe functionality using JSON-RPC notifications transmitted through the WebSocket API. You can interact with the RPC directly ([suix_subscribeEvent](/sui-api-ref#suix_subscribeEvent), [suix_subscribeTransaction](/sui-api-ref#suix_subscribeTransaction)) or you can use an SDK like the Sui TypeScript SDK. The following excerpt from one of the examples uses the TypeScript SDK to create an asynchronous subscription to the filter identified in the filter. - -```move -let unsubscribe = await provider.subscribeEvent({ - filter: { }, - onMessage: (event) => { - console.log("subscribeEvent", JSON.stringify(event, null, 2)) - } -}); +With the dependency added, you can use the `emit` function to trigger an event whenever the action you want to monitor fires. For example, the following code is part of an example application that enables the locking of objects. The `lock` function handles the locking of objects and emits an event whenever the function is called. + +{@inject: examples/trading/contracts/escrow/sources/lock.move#fun=lock noComments} + +### Query events with RPC + +The Sui RPC provides a [queryEvents](/sui-api-ref#suix_queryEvents) method to query on-chain packages and return available events. As an example, the following `curl` command queries the Deepbook package on Mainnet for a specific type of event: + +```sh +curl -X POST https://fullnode.mainnet.sui.io:443 \ +-H "Content-Type: application/json" \ +-d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "suix_queryEvents", + "params": [ + { + "MoveModule": { + "package": "0x158f2027f60c89bb91526d9bf08831d27f5a0fcb0f74e6698b9f0e1fb2be5d05", + "module": "deepbook_utils", + "type": "0xdee9::clob_v2::DepositAsset<0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>" + } + }, + null, + 3, + false + ] +}' ``` -Move smart contracts can call other smart contracts that emit events. For example, `Deepbook_utils` can call the `dee9` smart contract and emit this event. Note that using package, transaction module to query for `dee9/clob_v2` misses the following event even though it is actually an event the `dee9` package emits. The current workaround for this issue is to know all the `packageId`s you care about and search those in the `queryEvent` call. - +
+ +A successful `curl` return + ```json { - "id": { - "txDigest": "bZnc1E7k1fJYLxWihfre5xCw1tX1CyAN6579zypJeiU", - "eventSeq": "0" + "jsonrpc": "2.0", + "result": { + "data": [ + { + "id": { + "txDigest": "8NB8sXb4m9PJhCyLB7eVH4onqQWoFFzVUrqPoYUhcQe2", + "eventSeq": "0" + }, + "packageId": "0x158f2027f60c89bb91526d9bf08831d27f5a0fcb0f74e6698b9f0e1fb2be5d05", + "transactionModule": "deepbook_utils", + "sender": "0x8b35e67a519fffa11a9c74f169228ff1aa085f3a3d57710af08baab8c02211b9", + "type": "0xdee9::clob_v2::WithdrawAsset<0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>", + "parsedJson": { + "owner": "0x704c8c0d8052be7b5ca7174222a8980fb2ad3cd640f4482f931deb6436902627", + "pool_id": "0x7f526b1263c4b91b43c9e646419b5696f424de28dda3c1e6658cc0a54558baa7", + "quantity": "6956" + }, + "bcs": "2szz6igTRuGmD7YATo8BEg81VLaei4od62wehadwMXYJv63UzJE16USL9pHFYBAGbwNkDYLCk53d45eFj3tEZK1vDGqtXcqH5US", + "timestampMs": "1691757698019" + }, + { + "id": { + "txDigest": "8NB8sXb4m9PJhCyLB7eVH4onqQWoFFzVUrqPoYUhcQe2", + "eventSeq": "1" + }, + "packageId": "0x158f2027f60c89bb91526d9bf08831d27f5a0fcb0f74e6698b9f0e1fb2be5d05", + "transactionModule": "deepbook_utils", + "sender": "0x8b35e67a519fffa11a9c74f169228ff1aa085f3a3d57710af08baab8c02211b9", + "type": "0xdee9::clob_v2::OrderFilled<0x2::sui::SUI, 0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>", + "parsedJson": { + "base_asset_quantity_filled": "0", + "base_asset_quantity_remaining": "1532800000000", + "is_bid": false, + "maker_address": "0x78a1ff467e9c15b56caa0dedfcfbdfe47c0c385f28b05fdc120b2de188cc8736", + "maker_client_order_id": "1691757243084", + "maker_rebates": "0", + "order_id": "9223372036854839628", + "original_quantity": "1614700000000", + "pool_id": "0x7f526b1263c4b91b43c9e646419b5696f424de28dda3c1e6658cc0a54558baa7", + "price": "605100", + "taker_address": "0x704c8c0d8052be7b5ca7174222a8980fb2ad3cd640f4482f931deb6436902627", + "taker_client_order_id": "20082022", + "taker_commission": "0" + }, + "bcs": "DcVGz85dWTLU4S33N7VYrhgbkm79ENhHVnp5kBfENEWEeMxHQuvsczg94teh6WHdYtwPqdEsPWdvSJ7ne5qiMxxn3kBm36KLyuuzHV1QdzF45GN8ZU1MDGU4XppiaqcMeRpPPiW8JpUDyeQoobKEV8fMqcyYpDq6KWtZ1WMoGvEDxFKDgFvW9Q7bt1JAzQehRkEKEDZ6dTwfiHw92QuFqczmZ5MKJLYzeysUsSw", + "timestampMs": "1691757698019" + }, + { + "id": { + "txDigest": "8b3byDuRojHXqmSz16PsyzfdXJEY5nZBGTM23gMsMAY8", + "eventSeq": "0" + }, + "packageId": "0x158f2027f60c89bb91526d9bf08831d27f5a0fcb0f74e6698b9f0e1fb2be5d05", + "transactionModule": "deepbook_utils", + "sender": "0x8b35e67a519fffa11a9c74f169228ff1aa085f3a3d57710af08baab8c02211b9", + "type": "0xdee9::clob_v2::OrderFilled<0x2::sui::SUI, 0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>", + "parsedJson": { + "base_asset_quantity_filled": "700000000", + "base_asset_quantity_remaining": "0", + "is_bid": false, + "maker_address": "0x03b86e93d80b27763ee1fc2c37e285465dff835769de9462d9ad4ebcf46ac6df", + "maker_client_order_id": "20082022", + "maker_rebates": "634", + "order_id": "9223372036854839643", + "original_quantity": "1000000000", + "pool_id": "0x7f526b1263c4b91b43c9e646419b5696f424de28dda3c1e6658cc0a54558baa7", + "price": "604100", + "taker_address": "0x704c8c0d8052be7b5ca7174222a8980fb2ad3cd640f4482f931deb6436902627", + "taker_client_order_id": "20082022", + "taker_commission": "1058" + }, + "bcs": "DcVGz85dWTLU4S33N7VYrhgbkm79ENhHVnp5kBfENEWEjN45pa9U3AkNhxfTRZbaHTQLugLBXttE32hpJKRsbrZGdryXMPmNA8EpHJnVcnYMXZmWXkNXvY1XjEYnAKU4BnhyJ9BQuxRJDXLA4DEu5uWEpWjLPD2ZHuxqHCn7GpUxvxJjHkKjr9jVVfeR6sN2uRhUXkThEDjCekrqaqwidkyXNmTzmZG4fre3eoZ", + "timestampMs": "1691758372427" + } + ], + "nextCursor": { + "txDigest": "8b3byDuRojHXqmSz16PsyzfdXJEY5nZBGTM23gMsMAY8", + "eventSeq": "0" + }, + "hasNextPage": true }, - "packageId": "0x158f2027f60c89bb91526d9bf08831d27f5a0fcb0f74e6698b9f0e1fb2be5d05", - "transactionModule": "deepbook_utils", - "sender": "0x4419ae182ac112bb065bda2146136ed02524ee2611478bfe8ca5d3835bee4af6", - "type": "0xdee9::clob_v2::OrderPlaced<0x2::sui::SUI, 0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>", - "parsedJson": { - "base_asset_quantity_placed": "1000000000", - "client_order_id": "20082022", - "expire_timestamp": "1697121171540", - "is_bid": false, - "order_id": "9223372036854945121", - "original_quantity": "1000000000", - "owner": "0x8c23e5e23c6eb654d69f8ae7de3be23584f435cad81fa4b9cb024b6c989b7818", - "pool_id": "0x7f526b1263c4b91b43c9e646419b5696f424de28dda3c1e6658cc0a54558baa7", - "price": "500000" - }, - "bcs": "2pWctGGQ9KULfmnzNtGuPpggLQrj1ZiUQaxva4neM6QWAtUAkuPAzU2eGrdZaGHti3bsUefDioUwwYoVR3bYBkG7Gxf5JVVSxxqTqzxdg5os5ESwFaP69ZcrNsya4G9rHK4KBac9i3m1MseN38xDwMvAMx3" -} -``` - -```json -{ - "id": { - "txDigest": "896CKHod5GQ4kzhF7EwTAGyhQBdaTb9rQS41dcL76gj8", - "eventSeq": "0" - }, - "packageId": "0x000000000000000000000000000000000000000000000000000000000000dee9", - "transactionModule": "clob_v2", - "sender": "0xf821d3483fc7725ebafaa5a3d12373d49901bdfce1484f219daa7066a30df77d", - "type": "0xdee9::clob_v2::OrderPlaced<0xbc3a676894871284b3ccfb2eec66f428612000e2a6e6d23f592ce8833c27c973::coin::COIN, 0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf::coin::COIN>", - "parsedJson": { - "base_asset_quantity_placed": "5000000", - "client_order_id": "1696545636947311087", - "expire_timestamp": "1696549236947", - "is_bid": true, - "order_id": "562414", - "original_quantity": "5000000", - "owner": "0xf995d6df20e18421928ff0648bd583ccdf384ab05791d8be21d32977a37dacfc", - "pool_id": "0xf0f663cf87f1eb124da2fc9be813e0ce262146f3df60bc2052d738eb41a25899", - "price": "274518000000" - }, - "bcs": "4SgemkCzrqEsTHLFgMcbUtttZCf2CrEH2njjFL1rizCHzvAoYsToGrbFLffQPtGxsSt96Xr4j2SLNeLcBGKeYXDrVYWqivhf3551Mqj71DZBxq5D1Qwfgh1TKeF43Jz4b4XH1nEpkya2Pr8515vzJbHUkpP" + "id": 1 } ``` +
-## Examples - -### Subscribe to event - -This example leverages the Sui TypeScript SDK to subscribe to events the package with ID `` emits. Each time the event fires, the code displays the response to the console. - -#### TypeScript - -To create the event subscription, you can use a basic Node.js app. You need the Sui TypeScript SDK, so install the module using `npm install @mysten/sui` at the root of your project. In your TypeScript code, import `JsonRpcProvider` and a connection from the library. - -```ts -import { getFullnodeUrl, SuiClient } from '@mysten/sui/client'; - -// Package is on Testnet. -const client = new SuiClient({ - url: getFullnodeUrl('testnet'), -}); -const Package = ''; - -const MoveEventType = '::::'; +The TypeScript SDK provides a wrapper for the `suix_queryEvents` method: [`client.queryEvents`](https://sdk.mystenlabs.com/typedoc/classes/_mysten_sui.client.SuiClient.html#queryEvents). -console.log( - await client.getObject({ - id: Package, - options: { showPreviousTransaction: true }, - }), -); +#### Filtering event queries -let unsubscribe = await client.subscribeEvent({ - filter: { Package }, - onMessage: (event) => { - console.log('subscribeEvent', JSON.stringify(event, null, 2)); - }, -}); - -process.on('SIGINT', async () => { - console.log('Interrupted...'); - if (unsubscribe) { - await unsubscribe(); - unsubscribe = undefined; - } -}); -``` - -#### Response +To filter the events returned from your queries, use the following data structures. -When the subscribed to event fires, the example displays the following JSON representation of the event. +| Query | Description | JSON-RPC Parameter Example | +| ----------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | +| `All` | All events | `{"All": []}` | +| `Transaction` | Events emitted from the specified transaction | `{"Transaction":"DGUe2TXiJdN3FI6MH1FwghYbiHw+NKu8Nh579zdFtUk="}` | +| `MoveModule` | Events emitted from the specified Move module | `{"MoveModule":{"package":"", "module":"nft"}}` | +| `MoveEventModule` | Events emitted, defined on the specified Move module. | `{"MoveEventModule": {"package": "", "module": "nft"}}` | +| `MoveEvent` | Move struct name of the event | `{"MoveEvent":"::nft::MintNFTEvent"}` | +| `EventType` | Type of event described in Events section | `{"EventType": "NewObject"}` | +| `Sender` | Query by sender address | `{"Sender":"0x008e9c621f4fdb210b873aab59a1e5bf32ddb1d33ee85eb069b348c234465106"}` | +| `Recipient` | Query by recipient | `{"Recipient":{"AddressOwner":"0xa3c00467938b392a12355397bdd3d319cea5c9b8f4fc9c51b46b8e15a807f030"}}` | +| `Object` | Return events associated with the given object | `{"Object":"0x727b37454ab13d5c1dbb22e8741bff72b145d1e660f71b275c01f24e7860e5e5"}` | +| `TimeRange` | Return events emitted in [start_time, end_time] interval | `{"TimeRange":{"startTime":1669039504014, "endTime":1669039604014}}` | -```json -subscribeEvent { - "id": { - "txDigest": "HkCBeBLQbpKBYXmuQeTM98zprUqaACRkjKmmtvC6MiP1", - "eventSeq": "0" - }, - "packageId": "0x2d6733a32e957430324196dc5d786d7c839f3c7bbfd92b83c469448b988413b1", - "transactionModule": "coin_flip", - "sender": "0x46f184f2d68007e4344fffe603c4ccacd22f4f28c47f321826e83619dede558e", - "type": "0x2d6733a32e957430324196dc5d786d7c839f3c7bbfd92b83c469448b988413b1::coin_flip::Outcome", - "parsedJson": { - "bet_amount": "4000000000", - "game_id": "0xa7e1fb3c18a88d048b75532de219645410705fa48bfb8b13e8dbdbb7f4b9bbce", - "guess": 0, - "player_won": true - }, - "bcs": "3oWWjWKRVu115bnnZphyDcJ8EyF9X4pgVguwhEtcsVpBf74B6RywQupm2X", - "timestampMs": "1687912116638" -} -``` +### Query events in Rust -#### Rust SDK +The [Sui by Example](https://github.com/gdanezis/sui-by-example/blob/main/src/05_reading_events/bin/main.rs) repo on GitHub contains a code sample that demonstrates how to query events using the `query_events` function. The package that `PACKAGE_ID_CONST` points to exists on Mainnet, so you can test this code using Cargo. To do so, clone the `sui-by-example` repo locally and follow the [Example 05 directions](https://github.com/gdanezis/sui-by-example/tree/main/src/05_reading_events). ```rust -use futures::StreamExt; -use sui_sdk::rpc_types::EventFilter; -use sui_sdk::SuiClientBuilder; -use anyhow::Result; +use sui_sdk::{rpc_types::EventFilter, types::Identifier, SuiClientBuilder}; + +const PACKAGE_ID_CONST: &str = "0x279525274aa623ef31a25ad90e3b99f27c8dbbad636a6454918855c81d625abc"; #[tokio::main] -async fn main() -> Result<()> { - let sui = SuiClientBuilder::default() - .ws_url("wss://fullnode.mainnet.sui.io:443") +async fn main() -> Result<(), anyhow::Error> { + let sui_mainnet = SuiClientBuilder::default() .build("https://fullnode.mainnet.sui.io:443") - .await.unwrap(); - let mut subscribe_all = sui.event_api().subscribe_event(EventFilter::All(vec![])).await?; - loop { - println!("{:?}", subscribe_all.next().await); + .await?; + + let events = sui_mainnet + .event_api() + .query_events( + EventFilter::MoveModule { + package: PACKAGE_ID_CONST.parse()?, + module: Identifier::new("dev_trophy")?, + }, + None, + None, + false, + ) + .await?; + + for event in events.data { + println!("Event: {:?}", event.parsed_json); } + + Ok(()) } ``` -### Filtering event queries +### Query events with GraphQL -To filter the events returned from your queries, use the following data structures. + -:::info +You can use GraphQL to query events instead of JSON RPC. The following example queries are in the [`sui-graphql-rpc` crate](https://github.com/MystenLabs/sui/tree/main/crates/sui-graphql-rpc/examples/event_connection) in the Sui repo. -This set of filters applies only to event querying APIs. It differs from the filters offered for the subscriptions API (see following section). In particular, it does not support combinations like `"All": [...]`, `"Any": [...]`, `"And": [_, _]`, `"Or": [_, _]`, and `"Not": _`. +
+ +Event connection + +{@inject: crates/sui-graphql-rpc/examples/event_connection/event_connection.graphql} +
-::: +
+ +Filter events by sender + +{@inject: crates/sui-graphql-rpc/examples/event_connection/filter_by_sender.graphql} +
-| Query | Description | JSON-RPC Parameter Example | -| ----------------- | -------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | -| `All` | All events | `{"All"}` | -| `Transaction` | Events emitted from the specified transaction | `{"Transaction":"DGUe2TXiJdN3FI6MH1FwghYbiHw+NKu8Nh579zdFtUk="}` | -| `MoveModule` | Events emitted from the specified Move module | `{"MoveModule":{"package":"", "module":"nft"}}` | -| `MoveEventModule` | Events emitted, defined on the specified Move module. | `{"MoveEventModule": {"package": "", "module": "nft"}}` | -| `MoveEvent` | Move struct name of the event | `{"MoveEvent":"::nft::MintNFTEvent"}` | -| `EventType` | Type of event described in Events section | `{"EventType": "NewObject"}` | -| `Sender` | Query by sender address | `{"Sender":"0x008e9c621f4fdb210b873aab59a1e5bf32ddb1d33ee85eb069b348c234465106"}` | -| `Recipient` | Query by recipient | `{"Recipient":{"AddressOwner":"0xa3c00467938b392a12355397bdd3d319cea5c9b8f4fc9c51b46b8e15a807f030"}}` | -| `Object` | Return events associated with the given object | `{"Object":"0x727b37454ab13d5c1dbb22e8741bff72b145d1e660f71b275c01f24e7860e5e5"}` | -| `TimeRange` | Return events emitted in [start_time, end_time] interval | `{"TimeRange":{"startTime":1669039504014, "endTime":1669039604014}}` | +
+ +Filter events by emitting package and type + +{@inject: crates/sui-graphql-rpc/examples/event_connection/filter_by_emitting_package_module_and_event_type.graphql} +
-### Filtering events for subscription (deprecated) +The [TypeScript SDK](https://sdk.mystenlabs.com/typedoc/modules/_mysten_sui.graphql.html) provides functionality to interact with the Sui GraphQL service. -:::warning +## Monitoring events -This section is deprecated beginning with Sui client version 1.28. Use the [custom indexer](../advanced/custom-indexer.mdx) section to learn about how to stream checkpoints and filter events continuously. +Firing events is not very useful in a vacuum. You also need the ability to respond to those events. There are two methods from which to choose when you need to monitor on-chain events: +- Incorporate a [custom indexer](../advanced/custom-indexer.mdx) to take advantage of Sui's micro-data ingestion framework. +- Poll the Sui network on a schedule to query events. -::: +Using a custom indexer provides a near-real time monitoring of events, so is most useful when your project requires immediate reaction to the firing of events. Polling the network is most useful when the events you're monitoring don't fire often or the need to act on those events are not immediate. The following section provides a polling example. -To create a subscription, you can set a filter to return only the set of events you're interested in listening for. +### Poll events {#poll-events} -:::info +To monitor events, you need a database to store checkpoint data. The [Trustless Swap](../app-examples/trustless-swap.mdx) example uses a Prisma database to store checkpoint data from the Sui network. The database is populated from polling the network to retrieve emitted events. -This set of filters applies only to event subscription APIs. It differs from the filters offered for the query API (see previous section). In particular, it supports combinations like `"All": [...]`, `"Any": [...]`, `"And": [_, _]`, `"Or": [_, _]`, and `"Not": _`. +
+ +`event-indexer.ts` from Trustless Swap + +{@inject: examples/trading/api/indexer/event-indexer.ts noComments} +
-::: +Trustless Swap incorporates handlers to process each event type that triggers. For the `locked` event, the handler in `locked-handler.ts` fires and updates the Prisma database accordingly. +
+ +`locked-handler.ts` from Trustless Swap + +{@inject: examples/trading/api/indexer/locked-handler.ts} +
+## Related links -| Filter | Description | JSON-RPC Parameter Example | -| ----------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------- | -| `Package` | Move package ID | `{"Package":""}` | -| `MoveModule` | Move module where the event was emitted | `{"MoveModule": {"package": "", "module": "nft"}}` | -| `MoveEventType` | Move event type defined in the move code | `{"MoveEventType":"::nft::MintNFTEvent"}` | -| `MoveEventModule` | Move event module defined in the move code | `{"MoveEventModule": {"package": "", "module": "nft", "event": "MintNFTEvent"}}` | -| `MoveEventField` | Filter using the data fields in the move event object | `{"MoveEventField":{ "path":"/name", "value":"NFT"}}` | -| `SenderAddress` | Address that started the transaction | `{"SenderAddress": "0x008e9c621f4fdb210b873aab59a1e5bf32ddb1d33ee85eb069b348c234465106"}` | -| `Sender` | Sender address | `{"Sender":"0x008e9c621f4fdb210b873aab59a1e5bf32ddb1d33ee85eb069b348c234465106"}` | -| `Transaction` | Transaction hash | `{"Transaction":"ENmjG42TE4GyqYb1fGNwJe7oxBbbXWCdNfRiQhCNLBJQ"}` | -| `TimeRange` | Time range in millisecond | `{"TimeRange": {"start_time": "1685959791871", "end_time": "1685959791871"}}` | +- [Custom Indexer](../advanced/custom-indexer.mdx): For near-real time monitoring of events, you can use a custom indexer. +- [Events](https://move-book.com/programmability/events.html): The Move Book shows how to emit events in Move. +- [Trustless Swap](../app-examples/trustless-swap.mdx): The Trustless Swap guide uses events to update the state of its frontend. \ No newline at end of file diff --git a/docs/content/guides/operator.mdx b/docs/content/guides/operator.mdx index d9326de7d37af..27e4409712ff9 100644 --- a/docs/content/guides/operator.mdx +++ b/docs/content/guides/operator.mdx @@ -25,9 +25,15 @@ Guides that benefit both Full node operators and validators include: - [Data Management](./operator/data-management.mdx) - [Genesis](./operator/genesis.mdx) +- [Monitoring](./operator/monitoring.mdx) - [Snapshots](./operator/snapshots.mdx) - [Archives](./operator/archives.mdx) ## Exchange integration guide -The [Sui Exchange Integration Guide](./operator/exchange-integration.mdx) provides step-by-step instructions on how to integrate SUI into a cryptocurrency exchange. \ No newline at end of file +The [Sui Exchange Integration Guide](./operator/exchange-integration.mdx) provides step-by-step instructions on how to integrate SUI into a cryptocurrency exchange. + +## Sui Bridge Node validators + +Guides that inform validators how to operate Sui Bridge include: +- [Sui Bridge Node Configuration](./operator/bridge-node-configuration.mdx) diff --git a/docs/content/guides/operator/archives.mdx b/docs/content/guides/operator/archives.mdx index 8a0a2e1e25aed..aecf27718ce74 100644 --- a/docs/content/guides/operator/archives.mdx +++ b/docs/content/guides/operator/archives.mdx @@ -52,11 +52,10 @@ state-archive-read-config: # Use mysten-testnet-archives for testnet # Use mysten-mainnet-archives for mainnet bucket: "mysten--archives" - # Use your AWS account access key id - aws-access-key-id: "" - # Use your AWS account secret access key - aws-secret-access-key: "" - aws-region: "" + # you can either provide your own aws credentials via "aws-secret-access-key" and + # "aws-access-key-id" or set no-sign-request: true + no-sign-request: true + aws-region: "us-west-2" object-store-connection-limit: 20 # How many objects to read ahead when catching up concurrency: 5 diff --git a/docs/content/guides/operator/bridge-node-configuration.mdx b/docs/content/guides/operator/bridge-node-configuration.mdx new file mode 100644 index 0000000000000..d7a1e75f85b38 --- /dev/null +++ b/docs/content/guides/operator/bridge-node-configuration.mdx @@ -0,0 +1,292 @@ +--- +title: Sui Bridge Validator Node Configuration +sidebar_label: Sui Bridge Node Configuration +--- + +Running a Bridge Validator Node (Bridge Node) requires registering your node with the bridge committee. Correct configuration of your node ensures optimal performance and valid metrics data. Follow this topic to make sure your Bridge Node is set up properly. + +## Prerequisites + +To set up and run a Bridge Node, you need to install `sui` and `sui-bridge-cli`. You can install them using one of the following options: + +```sh +# install from tip of `main` +$ cargo install --locked --git "https://github.com/MystenLabs/sui.git" sui sui-bridge-cli +# install with a commit sha +$ cargo install --locked --git "https://github.com/MystenLabs/sui.git" --rev {SHA} sui sui-bridge-cli +``` + +## Committee registration + +To join the network you must first register with the bridge validator committee. + +### Prepare for metadata + +The required metadata includes two things: + +- `BridgeAuthorityKey`, an ECDSA key to sign messages. Because this is a hot key that is kept in memory, it’s fine to use the following tool to generate one and write it to file. +- A REST API URL where the Bridge Node listens to and serves requests. Example: `https://bridge.example-sui-validator.io:443`. Make sure the port is correct and the URL does not contain any invalid characters, like quotes for example. + +To create a `BridgeAuthorityKey`, run +```sh +$ sui-bridge-cli create-bridge-validator-key +``` +where `` is the location to write the key pair to. + +:::info + +It's highly recommended you create a new key pair in a secure environment (for example, in the same machine where your node runs) to avoid key compromise. + +::: + +### Registration + +After you have both authority key file and REST API URL ready, you can register them by using Sui CLI: + +```sh +$ sui validator register-bridge-committee --bridge-authority-key-path --bridge-authority-url +``` + +#### Offline signing {#offline-signing} + +If you keep your validator account key in cold storage or you want to perform offline signing, use flags `--print-only` and `--validator-address` (with the value for the validator address). This prints serialized unsigned transaction bytes, then you can use your preferred signing process to produce signed bytes. + +Run the following command to execute it: + +```sh +$ sui client execute-signed-tx +``` + +#### Update metadata (before committee is finalized) + +Both key and URL are changeable **before the committee is finalized**. If you wish to update metadata, simply rerun `sui validator register-bridge-committee`. + +#### View registered metadata + +To double check you registered the correct metadata on chain, run + +```sh +$ sui-bridge-cli view-bridge-registration --sui-rpc-url {SUI-FULLNODE-URL} +``` + +## Update metadata (after committee is finalized) + +Use the following command to update bridge node URL: +```sh +$ sui validator update-bridge-committee-node-url +``` +Refer to [offline signing section](#offline-signing) in this page for how to sign the transaction offline. + +Authoritiy key rotation is not supported yet. + + +## Bridge Node + +You have several options when configuring your Bridge Node for performance and metrics monitoring. Follow the instructions that follow to configure your node for best results in your environment. + +### Bridge Node hardware requirements + +Suggested hardware requirements: + +- CPU: 6 physical cores +- Memory: 16GB +- Storage: 200GB +- Network: 100Mbps + +### WAF protection for Bridge Node + +To protect against distributed denial of service (DDoS) attacks and similar attacks intended to expend validator resources, you must provide rate limit protection for the bridge server. + +In addition to protection, this gives node operators fine-grained control over the rate of requests they receive, and observability into those requests. + +The currently recommended rate limit is `50 requests/second per unique IP`. + +#### Web application firewall (WAF) options + +You can use a managed cloud service, for example: + +- [Cloudflare WAF](https://www.cloudflare.com/en-ca/application-services/products/waf/) +- [AWS WAF](https://aws.amazon.com/waf/) +- [GCP Cloud Armor](https://cloud.google.com/security/products/armor) + +It's also possible to use an open source load balancer, such as [HAProxy](https://www.haproxy.org/) for a practical, IP-based rate limit. + +A shortened example HAProxy configuration looks like the following: + +``` +frontend http-in + bind *:80 + # Define an ACL to count requests per IP and block if over limit + acl too_many_requests src_http_req_rate() gt 50 + # Track the request rate per IP + stick-table type ip size 1m expire 1m store http_req_rate(1s) + # Check request rate and deny if the limit is exceeded + http-request track-sc0 src + http-request deny if too_many_requests + + default_backend bridgevalidator + +backend bridgevalidator + # Note the port needs to match the value in Bridge Node config, default is 9191 + server bridgevalidator 0.0.0.0:9191 +``` + +If choosing to use an open source load balancing option, make sure to set up metrics collection and alerting on the service. + +### Bridge Node config + +Use `sui-bridge-cli` command to create a template. If you want to run `BridgeClient` (see the following section), pass `--run-client` as a parameter. + +```sh +$ sui-bridge-cli create-bridge-node-config-template {PATH} +$ sui-bridge-cli create-bridge-node-config-template --run-client {PATH} +``` + +The generated configuration includes the following parameters: + +| Parameter | Description | +| --- | --- | +| `server-listen-port` | The port that Bridge Node listens to for handling requests. | +| `metrics-port` | Port to export Prometheus metrics. | +| `bridge-authority-key-path` | The path to the Bridge Validator key, generated from `sui-bridge-cli create-bridge-validator-key` command referenced previously. | +| `run-client` | Whether Bridge Client should be enabled in Bridge Node (more instructions follow). | +| `approved-governance-actions` | A list of governance actions that you want to support. | +| `sui:sui-rpc-url` | Sui RPC URL. | +| `sui:sui-bridge-chain-id` | `0` for Sui Mainnet, `1` for Sui Testnet. | +| `eth:eth-rpc-url` | Ethereum RPC URL. | +| `eth:eth-bridge-proxy-address` | The proxy address for Bridge Solidity contracts on Ethereum. | +| `eth:eth-bridge-chain-id` | `10` for Ethereum Mainnet, `11` for Sepolia Testnet. | +| `eth:eth-contracts-start-block-fallback` | The starting block BridgeNodes queries for from Ethereum FullNode. This number should be the block where Solidity contracts are deployed or slightly before. | +| `metrics:push-url` | The url of the remote Sui metrics pipeline (for example, `https://metrics-proxy.[testnet_OR_mainnet].sui.io:8443/publish/metrics`). See the [metrics push section](#metrics-push) that follows for more details. | + +With `run-client: true`, you can find these additional fields in the generated config: + +| Parameter | Description | +| --- | --- | +| `db-path` | Path of BridgeClient database, for BridgeClient. | +| `sui:bridge-client-key-path` | The file path of Bridge Client key. This key can be generated with `sui-bridge-cli create-bridge-client-key` as previously shown. When `run-client` is true but you do not provide `sui:bridge-client-key-path`, it defaults to use the Bridge Validator key to submit transactions on Sui. This is not recommended for the sake of key separation. | + +### Bridge Client + +`BridgeClient` orchestrates bridge transfer requests. It is **optional** to run for a `BridgeNode`. `BridgeClient` submits transaction on the Sui network. Thus when it's enabled, you need a Sui account key with enough SUI balance. + +To enable `bridge_client` feature on a `BridgeNode`, set the following parameters in `BridgeNodeConfig`: + +```yaml +run-client: true +db-path: +sui: + bridge-client-key-path: +``` + +To create a `BridgeClient` key pair, run + +```sh +sui-bridge-cli create-bridge-client-key +``` +This prints the newly created Sui Address. Then we need to fund this address with some SUI for operations. + + +### Build Bridge Node + +Build or install Bridge Node in one of the following ways: + +- Use `cargo install`. + ```sh + $ cargo install --locked --git "https://github.com/MystenLabs/sui.git" --branch {BRANCH-NAME} sui-bridge + # OR + $ cargo install --locked --git "https://github.com/MystenLabs/sui.git" --rev {SHA-NAME} sui-bridge + ``` +- Compile from source code + ```sh + $ git clone https://github.com/MystenLabs/sui.git + $ cd sui + $ git fetch origin {BRANCH-NAME|SHA} + $ git checkout {BRANCH-NAME|SHA} + $ cargo build --release --bin sui-bridge + ``` +- Use `curl`/`wget` pre-built binaries (for Linux/AMD64 only). + ``` + curl https://sui-releases.s3.us-east-1.amazonaws.com/{SHA}/sui-bridge -o sui-bridge + ``` +- Use pre-built Docker image. Pull from Docker Hub: `mysten/sui-tools:{SHA}` + + +### Run Bridge Node + +Running Bridge Node is similar to running a Sui node using systemd or Ansible. The command to start the Bridge Node is: + +```sh +$ RUST_LOG=info,sui_bridge=debug sui-bridge --config-path {BRIDGE-NODE-CONFIG-PATH} +``` + +### Ingress + +Bridge Node listens for TCP connections over port `9191` (or the preferred port in the configuration file). You must allow incoming connections for that port on the host that is running Bridge Node. + +Test ingress with `curl` on a remote machine and expect a `200` response: + +```sh +$ curl -v {YOUR_BRIDGE_URL} +``` + +### Bridge Node monitoring + +Use `uptime` to check if the node is running. + +You can find a full list of Bridge Node metrics and their descriptions in the [`sui-bridge` crate](https://github.com/MystenLabs/sui/blob/main/crates/sui-bridge/src/metrics.rs). + +#### When `run-client: false` + +In this case Bridge Node runs as a passive observer and does not proactively poll on-chain activities. Important metrics to monitor in this case are the request handling metrics, such as: + +- `bridge_requests_received` +- `bridge_requests_ok` +- `bridge_err_requests` +- `bridge_requests_inflight` +- `bridge_eth_rpc_queries` +- `bridge_eth_rpc_queries_latency` +- `bridge_signer_with_cache_hit` +- `bridge_signer_with_cache_miss` +- `bridge_sui_rpc_errors` + +#### When `run-client: true` + +In this case, Bridge Client is toggled on and syncs with blockchains proactively. The best metrics to track progress are: + +- `bridge_last_synced_sui_checkpoints` +- `bridge_last_synced_eth_blocks` +- `bridge_last_finalized_eth_block` +- `bridge_sui_watcher_received_events` +- `bridge_eth_watcher_received_events` +- `bridge_sui_watcher_received_actions` +- `bridge_eth_watcher_received_actions` + +`bridge_gas_coin_balance` is also a critical metric to track the balance of your client gas coin, and top up after it dips below a certain threshold. + +### Metrics push {#metrics-push} + +The Bridge Nodes can push metrics to the remote proxy for network-level observability. + +To enable metrics push, set the following parameters in `BridgeNodeConfig`: + +```yaml +metrics: + push-url: https://metrics-proxy.[testnet|mainnet].sui.io:8443/publish/metrics +``` + +The proxy authenticates pushed metrics by using the metrics key pair. It is similar to `sui-node` pushing metrics with `NetworkKey`. Unlike `NetworkKey`, the Bridge Node metrics key is not recorded on chain and can be ephemeral. The metrics key is loaded from the `metrics-key-pair` field in `BridgeNodeConfig` if provided, otherwise a new key pair is generated on the fly. The proxy queries node public keys periodically by hitting the metrics public API key of each node. + +When Bridge Node starts, it might log this line once: + +``` +unable to push metrics: error sending request for url (xyz); new client will be created +``` + +This is okay to ignore as long as it does not persist. Otherwise, try: + +```sh +$ curl -i {your-bridge-node-url-onchain}/metrics_pub_key +``` + +and make sure the public key is correctly returned. diff --git a/docs/content/guides/operator/data-management.mdx b/docs/content/guides/operator/data-management.mdx index a5460a001de39..eb3003f0b367d 100644 --- a/docs/content/guides/operator/data-management.mdx +++ b/docs/content/guides/operator/data-management.mdx @@ -94,6 +94,78 @@ To enable historic data queries for the Sui Full nodes that prune old transactio If the information about the transaction digest, effects, events, or checkpoints is not available locally, a Full node automatically retrieves the historical data from a cloud-based key-value store (currently managed by MystenLabs). Note that the current key-value store implementation keeps historic transactional data only: we plan to provide support for a similar setup for retrieving the historic object versions in a future release. +## Object pruning {#object-pruning} + +Sui adds new object versions to the database as part of transaction execution. This makes previous versions ready for +garbage collection. However, without pruning, this can result in database performance degradation and requires large +amounts of storage space. Sui identifies the objects that are eligible for pruning in each checkpoint, and then performs +the pruning in the background. + +You can enable pruning for a Sui node by adding the `authority-store-pruning-config` config to `fullnode.yaml` file: +```yaml +authority-store-pruning-config: + # Number of epoch dbs to keep + # Not relevant for object pruning + num-latest-epoch-dbs-to-retain: 3 + # The amount of time, in seconds, between running the object pruning task. + # Not relevant for object pruning + epoch-db-pruning-period-secs: 3600 + # Number of epochs to wait before performing object pruning. + # When set to 0, Sui prunes old object versions as soon + # as possible. This is also called *aggressive pruning*, and results in the most effective + # garbage collection method with the lowest disk usage possible. + # This is the recommended setting for Sui Validator nodes since older object versions aren't + # necessary to execute transactions. + # When set to 1, Sui prunes only object versions from transaction checkpoints + # previous to the current epoch. In general, when set to N (where N >= 1), Sui prunes + # only object versions from checkpoints up to `current - N` epoch. + # It is therefore possible to have multiple versions of an object present + # in the database. This setting is recommended for Sui Full nodes as they might need to serve + # RPC requests that require looking up objects by ID and Version (rather than just latest + # version). However, if your Full node does not serve RPC requests you should then also enable + # aggressive pruning. + num-epochs-to-retain: 1 + # Advanced setting: Maximum number of checkpoints to prune in a batch. The default + # settings are appropriate for most use cases. + max-checkpoints-in-batch: 10 + # Advanced setting: Maximum number of transactions in one batch of pruning run. The default + # settings are appropriate for most use cases. + max-transactions-in-batch: 1000 +``` +## Transaction pruning {#transaction-pruning} + +Transaction pruning removes previous transactions and effects from the database. +Sui periodically creates checkpoints. Each checkpoint contains the transactions that occurred during the checkpoint and their associated effects. + +Sui performs transaction pruning in the background after checkpoints complete. + +You can enable transaction pruning for your Full node or Validator node by adding `num-epochs-to-retain-for-checkpoints` +to the `authority-store-pruning-config` config for the node: + +```yaml +authority-store-pruning-config: + num-latest-epoch-dbs-to-retain: 3 + epoch-db-pruning-period-secs: 3600 + num-epochs-to-retain: 0 + max-checkpoints-in-batch: 10 + max-transactions-in-batch: 1000 + # Number of epochs to wait before performing transaction pruning. + # When this is N (where N >= 2), Sui prunes transactions and effects from + # checkpoints up to the `current - N` epoch. Sui never prunes transactions and effects from the current and + # immediately prior epoch. N = 2 is a recommended setting for Sui Validator nodes and Sui Full nodes that don't + # serve RPC requests. + num-epochs-to-retain-for-checkpoints: 2 + # Ensures that individual database files periodically go through the compaction process. + # This helps reclaim disk space and avoid fragmentation issues + periodic-compaction-threshold-days: 1 +``` + +:::info + +If you prune transactions, Archival nodes can help ensure lagging peer nodes don't lose any information. For more information, see [Sui Archives](./archives.mdx). + +::: + ## Pruning policy examples diff --git a/docs/content/guides/operator/monitoring.mdx b/docs/content/guides/operator/monitoring.mdx new file mode 100644 index 0000000000000..236e6b72ec648 --- /dev/null +++ b/docs/content/guides/operator/monitoring.mdx @@ -0,0 +1,25 @@ +--- +title: Sui Node Monitoring +description: Monitor Sui node metrics to ensure the health and performance of your node. +--- + +:::info + +These instructions are for advanced users. If you just need a local development environment, you should instead follow the instructions in [Create a Local Sui Network](../developer/getting-started/local-network.mdx) to create a local Full node, validators, and faucet. + +::: + +Nodes expose on `localhost:9184/metrics` by default. + +You can view the metrics in the metrics UI, or you can use a tool like `curl` to get the metrics in a format that is easy to parse. + +```bash +curl -s http://localhost:9184/metrics | grep -E 'sui_validator|sui_fullnode' +``` + +## Production monitoring + +For production monitoring, we recommend using [Prometheus](https://prometheus.io/) and [Grafana](https://grafana.com/). + +You can use grafana agent, grafana alloy, or another tool to scrape the metrics from your node. + diff --git a/docs/content/guides/operator/snapshots.mdx b/docs/content/guides/operator/snapshots.mdx index b545803218a96..9d113b87a6af2 100644 --- a/docs/content/guides/operator/snapshots.mdx +++ b/docs/content/guides/operator/snapshots.mdx @@ -70,14 +70,14 @@ To restore from a RocksDB snapshot, follow these steps: * *GCS*: `GCS_SNAPSHOT_SERVICE_ACCOUNT_FILE_PATH` * *AZURE*: `AZURE_SNAPSHOT_STORAGE_ACCOUNT`, `AZURE_SNAPSHOT_STORAGE_ACCESS_KEY` -1. When using `sui-tool download-db-snapshot` the database is copied to the location you pass to `--path`, in a directory named `epoch_[NUM]`. Move this directory to the `live/` Full node database directory, for example `/opt/sui/db/authorities_db/full_node_db/live`. +1. When using `sui-tool download-db-snapshot` the database is copied to the location you pass to `--path`, in a directory named `epoch_[NUM]`. Rename the `epoch_[NUM]` directory to `live/` under your node's `db_path`, for example `cp -r /tmp/epoch_[NUM] /opt/sui/db/authorities_db/full_node_db/live`. 1. Make sure you update the ownership of the downloaded directory to the sui user (whichever linux user you run `sui-node` as) `sudo chown -R sui:sui /opt/sui/db/authorities_db/full_node_db/live`. 1. Start the Sui node. :::info -When you restore a Full node from a snapshot, write it to the path `/opt/sui/db/authorities_db/full_node_db/live`. When restoring a Validator node, you can shorten the database destination to `/opt/sui/db/authorities_db/live`. +When you restore a Full node from a snapshot, write it to the path `/opt/sui/db/authorities_db/full_node_db/live`. When restoring a Validator node, you can shorten the database destination to `/opt/sui/db/authorities_db/live`. Check the `db_path` field of your Full node or Validator configs to confirm the path location. ::: diff --git a/docs/content/guides/operator/sui-full-node.mdx b/docs/content/guides/operator/sui-full-node.mdx index 7de2c205d8c02..732a159269bd1 100644 --- a/docs/content/guides/operator/sui-full-node.mdx +++ b/docs/content/guides/operator/sui-full-node.mdx @@ -42,7 +42,7 @@ Validator nodes store only the latest transactions on the frontier of the object ## Full node setup -Follow the instructions here to run your own Sui Full node. +Follow the instructions here to run your own Sui Full node. Sui Full nodes run using the `sui-node` binary. ### Hardware requirements @@ -54,7 +54,8 @@ Suggested minimum hardware to run a Sui Full node: ### Software requirements -Sui recommends running Sui Full nodes on Linux. Sui supports the Ubuntu and Debian distributions. You can also run a Sui Full node on macOS. +Sui recommends running Sui Full nodes on Linux. Sui supports the Ubuntu and Debian distributions. You can run a Sui Full node on macOS, +but this is only recommended for development and not for production use. Make sure to [update Rust](https://doc.rust-lang.org/book/ch01-01-installation.html#updating-and-uninstalling). @@ -78,34 +79,33 @@ clang \ cmake ``` -## Configure a Full node +## Running a Full node {#running-a-full-node} + +Instructions for building, installing, or downloading the `sui-node` binary are available at [Sui Install](../developer/getting-started/sui-install.mdx). +These install instructions are specific to the `sui` cli, but apply to the `sui-node` binary as well. + +There are many ways to run a Sui Full node (bare metal, virtual machine, Kubernetes statefulset, and so on), and the solution that you choose depends on your specific needs as well as the infrastructure that you have available. + +There are some specific considerations to keep in mind when running a Sui Full node that apply to all environments: + +* [Genesis](./genesis.mdx): You must download the genesis blob for the network that you want to connect to, and make it available to the `sui-node`. +* [Data Storage](./data-management.mdx): Sui Full nodes _can_ require a significant amount of disk space to store the blockchain history. If you plan to use your Full node to serve RPC requests, you must also plan for the storage of index files, which requires a significant amount of disk space. +* [Monitoring](./monitoring.mdx): Sui Full nodes expose metrics about the node's health and the state of the Sui network. +* [Updates](./updates.mdx): Sui Full nodes must be updated to the latest version to remain in sync with the network. +* [Archival Fallback](./archives.mdx): The archival fallback allows you to sync checkpoints from any point in the chain's history. The network `seed-peers` below only keep a few epochs of history. -You can configure a Sui Full node either using Docker or by building from source. ### Using Docker Compose -Follow the instructions in the [Full node Docker Readme](https://github.com/MystenLabs/sui/tree/main/docker/fullnode#readme) to run a Sui Full node using Docker, including [resetting the environment](https://github.com/MystenLabs/sui/tree/main/docker/fullnode#reset-the-environment). +There's a guide in the Sui repository on running a Full node via [Docker Compose](https://github.com/MystenLabs/sui/tree/main/docker/fullnode#readme). +This alone is not suitable for a production environment, but can be used to get a Full node up and running quickly on a virtual machine or local machine for development purposes. +Refer to [Running a Full node](#running-a-full-node) for instructions relevant to production use cases. -### Setting up a local Sui repository -You must get the latest source files from the Sui GitHub repository. -1. Set up your fork of the Sui repository: - 1. Go to the [Sui repository](https://github.com/MystenLabs/sui) on GitHub and click the Fork button in the top right-hand corner of the screen. - 1. Clone your personal fork of the Sui repository to your local machine (ensure that you insert your GitHub username into the URL): - `git clone https://github.com//sui.git` -1. `cd` into your `sui` repository: - `cd sui` -1. Set up the Sui repository as a git remote: - `git remote add upstream https://github.com/MystenLabs/sui` -1. Sync your fork: - `git fetch upstream` -1. Check out the branch associated with the network version you want to run (for example, `devnet` to run a Devnet Full node): - `git checkout --track upstream/` +### Setting up a Full node -### Setting up a Full node from source {#set-up-from-source} +When you are ready to run `sui-node` in your production environment, you can set up your Full node by completing the following steps: -Open a terminal or console to the `sui` directory you downloaded in the previous steps to complete the following: -1. Install the required prerequisites. 1. Make a copy of the [Full node YAML template](https://github.com/MystenLabs/sui/blob/main/crates/sui-config/data/fullnode-template.yaml): `cp crates/sui-config/data/fullnode-template.yaml fullnode.yaml` 1. Download the genesis blob for the network to use: @@ -171,7 +171,7 @@ Open a terminal or console to the `sui` directory you downloaded in the previous - +1. Optional: Set up the [Archival Fallback](./archives.mdx), which allows you to sync checkpoints if you fall behind the network's `seed-peers`. 1. Optional: Skip this step to accept the default paths to resources. Edit the fullnode.yaml file to use custom paths. 1. Update the `db-path` field with the path to the Full node database. `db-path: "/db-files/sui-fullnode"` @@ -181,18 +181,19 @@ Open a terminal or console to the `sui` directory you downloaded in the previous genesis-file-location: "/sui-fullnode/genesis.blob" ``` -### Starting services +### Starting your Full node {#starting-a-full-node} + +You should not start syncing your Full node from the start of the genesis. This will take a very long time and consume a lot of resources (including likely filling up your disk). -At this point, your Sui Full node is ready to connect to the Sui network. +Instead, start your Full node from a recent snapshot. You can find details on how to obtain a snapshot from the [Sui Snapshots guide](./snapshots.mdx). -1. Open a terminal or console to the sui directory. -1. Start the Sui Full node: - `cargo run --release --bin sui-node -- --config-path fullnode.yaml` -1. Optional: Publish/subscribe to notifications using JSON-RPC via websocket. +Now that you have your Full node config file set up, and you've obtained a snapshot, you can start your Full node by running the `sui-node` binary with your `fullnode.yaml` configuration file: -If your setup is successful, your Sui Full node is now connected to the appropriate network. +```shell +sui-node --config-path fullnode.yaml +``` -Your Full node serves the read endpoints of the Sui JSON-RPC API at: `http://127.0.0.1:9000`. +It's a good idea to use something like systemd to manage your Full node in a production environment. ### Troubleshooting If you receive a `cannot find -lpq` error, you are missing the `libpq` library. Use `sudo apt-get install libpq-dev` to install on Linux, or `brew install libpq` on MacOS. After you install on MacOS, create a Homebrew link using `brew link --force libpq`. For further context, reference the [issue on Stack Overflow](https://stackoverflow.com/questions/70313347/ld-library-not-found-for-lpq-when-build-rust-in-macos?rq=1). @@ -208,131 +209,3 @@ Then update the metrics address in your fullnode.yaml file to use port `9180`. ```shell metrics-address: "0.0.0.0:9180" ``` - -## Monitoring - -Monitor your Full node using the instructions at Logging, Tracing, Metrics, and Observability. - -The default metrics port is `9184`. To change the port, edit your fullnode.yaml file. - -## Update your Full node - -Whenever Sui releases a new version, you must update your Full node with the release to ensure compatibility with the network it connects to. For example, if you use Sui Testnet you should install the version of Sui running on Sui Testnet. - -### Update with Docker Compose - -Follow the instructions to [reset the environment](https://github.com/MystenLabs/sui/tree/main/docker/fullnode#reset-the-environment), namely by running the command: - -```shell -docker-compose down --volumes -``` - -### Update from source - -If you followed the instructions for Building from Source, use the following steps to update your Full node: - -1. Shut down your running Full node. -1. `cd` into your local Sui repository: - ```shell - cd sui - ``` -1. Remove the database and 'genesis.blob' file: - ```shell - rm -r suidb genesis.blob - ``` -1. Fetch the source from the latest release: - ```shell - git fetch upstream - ``` -1. Reset your branch: - ```shell - git checkout -B --track upstream/ - ``` -1. Download the latest genesis blob: - - [Devnet genesis blob](https://github.com/MystenLabs/sui-genesis/raw/main/devnet/genesis.blob): - ```shell - curl -fLJO https://github.com/MystenLabs/sui-genesis/raw/main/devnet/genesis.blob - ``` - - [Testnet genesis blob](https://github.com/MystenLabs/sui-genesis/raw/main/testnet/genesis.blob): - ```shell - curl -fLJO https://github.com/MystenLabs/sui-genesis/raw/main/testnet/genesis.blob - ``` -1. Update your fullnode.yaml configuration file, if needed. -1. Restart your Sui Full node: - ```shell - cargo run --release --bin sui-node -- --config-path fullnode.yaml - ``` - -Your Full node starts on: http://127.0.0.1:9000. - -## Object pruning {#object-pruning} - -Sui adds new object versions to the database as part of transaction execution. This makes previous versions ready for -garbage collection. However, without pruning, this can result in database performance degradation and requires large -amounts of storage space. Sui identifies the objects that are eligible for pruning in each checkpoint, and then performs -the pruning in the background. - -You can enable pruning for a Sui node by adding the `authority-store-pruning-config` config to `fullnode.yaml` file: -```yaml -authority-store-pruning-config: - # Number of epoch dbs to keep - # Not relevant for object pruning - num-latest-epoch-dbs-to-retain: 3 - # The amount of time, in seconds, between running the object pruning task. - # Not relevant for object pruning - epoch-db-pruning-period-secs: 3600 - # Number of epochs to wait before performing object pruning. - # When set to 0, Sui prunes old object versions as soon - # as possible. This is also called *aggressive pruning*, and results in the most effective - # garbage collection method with the lowest disk usage possible. - # This is the recommended setting for Sui Validator nodes since older object versions aren't - # necessary to execute transactions. - # When set to 1, Sui prunes only object versions from transaction checkpoints - # previous to the current epoch. In general, when set to N (where N >= 1), Sui prunes - # only object versions from checkpoints up to `current - N` epoch. - # It is therefore possible to have multiple versions of an object present - # in the database. This setting is recommended for Sui Full nodes as they might need to serve - # RPC requests that require looking up objects by ID and Version (rather than just latest - # version). However, if your Full node does not serve RPC requests you should then also enable - # aggressive pruning. - num-epochs-to-retain: 0 - # Advanced setting: Maximum number of checkpoints to prune in a batch. The default - # settings are appropriate for most use cases. - max-checkpoints-in-batch: 10 - # Advanced setting: Maximum number of transactions in one batch of pruning run. The default - # settings are appropriate for most use cases. - max-transactions-in-batch: 1000 -``` -## Transaction pruning {#transaction-pruning} - -Transaction pruning removes previous transactions and effects from the database. -Sui periodically creates checkpoints. Each checkpoint contains the transactions that occurred during the checkpoint and their associated effects. - -Sui performs transaction pruning in the background after checkpoints complete. - -You can enable transaction pruning for your Full node or Validator node by adding `num-epochs-to-retain-for-checkpoints` -to the `authority-store-pruning-config` config for the node: - -```yaml -authority-store-pruning-config: - num-latest-epoch-dbs-to-retain: 3 - epoch-db-pruning-period-secs: 3600 - num-epochs-to-retain: 0 - max-checkpoints-in-batch: 10 - max-transactions-in-batch: 1000 - # Number of epochs to wait before performing transaction pruning. - # When this is N (where N >= 2), Sui prunes transactions and effects from - # checkpoints up to the `current - N` epoch. Sui never prunes transactions and effects from the current and - # immediately prior epoch. N = 2 is a recommended setting for Sui Validator nodes and Sui Full nodes that don't - # serve RPC requests. - num-epochs-to-retain-for-checkpoints: 2 - # Ensures that individual database files periodically go through the compaction process. - # This helps reclaim disk space and avoid fragmentation issues - periodic-compaction-threshold-days: 1 -``` - -:::info - -If you prune transactions, Archival nodes can help ensure lagging peer nodes don't lose any information. For more information, see [Sui Archives](./archives.mdx). - -::: diff --git a/docs/content/guides/operator/updates.mdx b/docs/content/guides/operator/updates.mdx new file mode 100644 index 0000000000000..a53399c348200 --- /dev/null +++ b/docs/content/guides/operator/updates.mdx @@ -0,0 +1,39 @@ +--- +title: Updating a Full Node +description: Update your Sui Full node to the latest version to remain in sync with the network. +--- + +## Sui release process + +Each Sui network is deployed on a consistent schedule. There are extenuating circumstances that might delay releases occasionally, but these delays are rare and communicated through [official channels](#communication). + +- `devnet`: Deployed every week on Mondays. +- `testnet`: Deployed every week on Tuesdays. +- `mainnet`: Deployed every two weeks on Wednesdays. + +:::info + +For additional details, see each network's [release schedule and configuration](https://sui.io/networkinfo). + +::: + +Whenever Sui releases a new version, you must update your Full node with the release to ensure compatibility with the network it connects to. For example, if you use Sui Testnet you should install the version of Sui running on Sui Testnet. + +Any release that contains a protocol change will need to be followed before the protocol upgrade takes place (when enough stake within the validator set upgrades, the new protocol version is enacted in the next epoch). +If you do not update your Full node, you will not be able to connect to the network after the protocol upgrade takes place. + +## Communication + +Releases are announced on [Sui Discord server](https://discord.com/invite/sui) and [node-operators](https://groups.google.com/a/groups.sui.io/g/node-operators) Google group. + +### Discord channels +- `devnet`: [`#devnet-updates`](https://discord.com/channels/916379725201563759/1004638487078772736) +- `testnet`: [`#tn-validator-announcements`](https://discord.com/channels/916379725201563759/1003660994381353101), [`#testnet-updates`](https://discord.com/channels/916379725201563759/1095151359642304612), ⁠and [`#node-announcements`](https://discord.com/channels/916379725201563759/1002231298888306718) channels. +- `mainnet`: [`⁠#mn-validator-announcements`](https://discord.com/channels/916379725201563759/1093852827627040768), [`#mainnet-updates`](https://discord.com/channels/916379725201563759/1103082453792464906), and [`#node-announcements`](https://discord.com/channels/916379725201563759/1002231298888306718) channels. + +## Update your Full node + +You can track the latest version of Sui on the [Sui Releases](https://github.com/MystenLabs/sui/releases) page on GitHub. +The schedule for each network is available in the [Network Release Schedule](https://sui.io/networkinfo) page. + +It is reasonable to have to shut down your Full node to perform an update, whether that be a rolling restart in Kubernetes, or a systemctl stop on a Linux machine to replace the sui-node binary. diff --git a/docs/content/references/contribute/mdx-components.mdx b/docs/content/references/contribute/mdx-components.mdx index 25a867999d5f1..e8ab46267a832 100644 --- a/docs/content/references/contribute/mdx-components.mdx +++ b/docs/content/references/contribute/mdx-components.mdx @@ -3,10 +3,59 @@ title: MDX Components draft: true --- -import Tabs from '@theme/Tabs'; -import TabItem from '@theme/TabItem'; +Sui Docs uses the MDX format for its network documentation, which allows [JSX in markdown content](https://mdxjs.com/). Sui uses features provided by Docusaurus, plugins from its community, and custom plugins in an effort to improve reader experience. None of these features are required in your markdown contributions to the Sui documentation, but the Sui community might include them if it improves experience. -Sui Docs uses the MDX format for its network documentation, which allows [JSX in markdown content](https://mdxjs.com/). Sui uses features provided by Docusaurus, plugins from its community, and custom plugins in an effort to improve reader experience. None of these features are required in your markdown contributions to the Sui documentation, but the Sui community might include them if it improves experience. +## Custom frontmatter + +The frontmatter at the top of MDX pages provides context for the current topic. The only frontmatter entry required at the time of riting is `title`. In addition to the standard frontmatter entries available, the following custom items are useful for certain topic types. + +### beta + +Including the `beta` entry applies a box to the top of the topic that informa the reader of the beta status of the feature or service described. Possible values include `true` for a standard box, or a list of environments the feature or service is available in (`devnet`, `testnet`, `mainnet`) to include that information in the note. + +**Example:** + +``` +--- +title: Page title +description: A page of info describing a beta feature. +beta: devnet, testnet +--- +``` + +### effort + +The `effort` option is used for end-to-end guides. Including this entry places a box at the top of the guide to let the reader know what kind of effort is required to complete the guide. Available values are `small`, `medium`, `large`. + +**Example:** +``` +--- +title: A Guides +description: A guide on how to do this thing in Sui. +effort: medium +--- +``` + +## Custom admonition boxes + +Beyond the default admonition boxes that Docusaurus offers (info, note, warning, danger), the following are also available. + +### Checkpoint + +The `:::checkpoint` admonition box is used in end-to-end guides to provide the status of the project that the current guide is walking through. The idea is that if their own work fails the checkpoint, then they know where things went wrong rather than getting to the end of the guide with a broken example. + +**Example:** + +``` +:::checkpoint + +Run your app and make sure you can: + +- Create an NFT. +- Initiate a trade. + +::: +``` ## Tabs @@ -153,10 +202,15 @@ This approach is a work in progress and there are certain formatting situations ::: -You can include specific sections of Move code using the following constructs appended to the end of the code directory. You can uses a comma delimited list for functions and structs to include each in the same codeblock: +You can include specific sections of Move code using the following constructs appended to the end of the code directory. You can uses a comma delimited list for functions, structs, and variables to include each in the same codeblock: - Module: #module=MODULE::NAME - Function: #fun=FUNCTION_NAME,ANOTHER_FUNCTION - Struct: #struct=STRUCT_NAME,ANOTHER_STRUCT +- Variables: #var=variableName, anotherVariable +- Move import: #use=LIBRARY::NAME +- React component: #component=ComponentName +- Type declaration: #type=TypeName +- Enum declaration: #enum=EnumName For example, `{@inject: examples/move/example.source#fun=buy_sword}` @@ -280,8 +334,10 @@ return book; You can include a space-limited list of options after the @inject call. The following options are supported: - `noComments`: Remove comments from Move code. - `noTests`: Remove tests from captured section of code. +- `noTitle`: Don't place a hyperlinked title at the top of the code block. +- `singleSpace`: If the code source has spurious whitespace, use this option to display code without extra spaces between lines. Useful when the code uses extra blank lines for readability. -For example, `{@inject: examples/move/example.source#module=example::example noComments noTests}` +For example, `{@inject: examples/move/example.source#module=example::example noComments noTests noTitle singleSpace}` ## Mermaid graphs diff --git a/docs/content/sidebars/concepts.js b/docs/content/sidebars/concepts.js index 78215078bfe19..490d4978e25cb 100644 --- a/docs/content/sidebars/concepts.js +++ b/docs/content/sidebars/concepts.js @@ -52,7 +52,6 @@ const concepts = [ }, items: ['concepts/transfers/custom-rules', 'concepts/transfers/transfer-to-object'], }, - 'concepts/events', 'concepts/versioning', ], }, diff --git a/docs/content/sidebars/guides.js b/docs/content/sidebars/guides.js index 2225e8b67f98e..9b981ba0d7f33 100644 --- a/docs/content/sidebars/guides.js +++ b/docs/content/sidebars/guides.js @@ -85,6 +85,7 @@ const guides = [ 'guides/developer/coin/loyalty', ], }, + 'guides/developer/stablecoins', { type: 'category', label: 'NFTs', @@ -153,19 +154,7 @@ const guides = [ }, items: [ 'guides/developer/app-examples/e2e-counter', - { - type: 'category', - label: 'Trustless Swap', - link: { - type: 'doc', - id: 'guides/developer/app-examples/trustless-swap', - }, - items: [ - 'guides/developer/app-examples/trustless-swap/backend', - 'guides/developer/app-examples/trustless-swap/indexer-api', - 'guides/developer/app-examples/trustless-swap/frontend', - ], - }, + 'guides/developer/app-examples/trustless-swap', 'guides/developer/app-examples/coin-flip', 'guides/developer/app-examples/reviews-rating', 'guides/developer/app-examples/blackjack', @@ -204,14 +193,17 @@ const guides = [ items: [ 'guides/operator/sui-full-node', 'guides/operator/validator-config', + 'guides/operator/genesis', + 'guides/operator/monitoring', + 'guides/operator/updates', 'guides/operator/data-management', 'guides/operator/snapshots', 'guides/operator/archives', - 'guides/operator/genesis', - 'guides/operator/validator-committee', - 'guides/operator/validator-tasks', 'guides/operator/node-tools', 'guides/operator/exchange-integration', + 'guides/operator/bridge-node-configuration', + 'guides/operator/validator-committee', + 'guides/operator/validator-tasks', ], }, ]; diff --git a/docs/content/snippets/deepbook.mdx b/docs/content/snippets/deepbook.mdx index fd22210ec26c0..639f1052e8c37 100644 --- a/docs/content/snippets/deepbook.mdx +++ b/docs/content/snippets/deepbook.mdx @@ -1,10 +1,23 @@ +import React, { useState, useEffect } from "react"; import { useLocation } from "@docusaurus/router"; import useGlobalData from '@docusaurus/useGlobalData'; import Link from '@docusaurus/Link'; export function DBV() { + const [showNotice, setShowNotice] = useState(false); const location = useLocation(); const isV3 = location.pathname.includes("v3"); + useEffect(() => { + const currentDate = new Date(); + const endDate = new Date("2024-10-31T23:59:59"); + // Don't show after October; remove if still here + if (currentDate <= endDate) { + setShowNotice(true); + } else { + setShowNotice(false); + } + }, []); + function DBLink() { return DeepBook{isV3 ? "V2" : "V3"} docs @@ -12,7 +25,9 @@ export function DBV() { return ( <> - This documentation is for version {isV3 ? "3" : "2"} of DeepBook. DeepBook{isV3 ? "V3" : "V2"} is currently available in {isV3 ? "Devnet and Testnet" : "Mainnet"}. For documentation on version {isV3 ? "2" : "3"} of DeepBook, see . + { showNotice ?

DeepBookV3 is now available on Mainnet!

: "" } +

This documentation is for version {isV3 ? "3" : "2"} of DeepBook. For documentation on version {isV3 ? "2" : "3"} of DeepBook, see . +

) diff --git a/docs/content/standards/deepbookv2/routing-a-swap.mdx b/docs/content/standards/deepbookv2/routing-a-swap.mdx index 776efbb740157..b18ad0344182f 100644 --- a/docs/content/standards/deepbookv2/routing-a-swap.mdx +++ b/docs/content/standards/deepbookv2/routing-a-swap.mdx @@ -22,7 +22,7 @@ To use the smart routing functionality, one should first find the best route. Th public async findBestRoute(tokenInObject: string, tokenOut: string, amountIn: number): Promise { // const tokenTypeIn: string = convertToTokenType(tokenIn, this.records); // should get the tokenTypeIn from tokenInObject - const tokenInfo = await this.provider.getObject({ + const tokenInfo = await this.suiClient.getObject({ id: tokenInObject, options: { showType: true, @@ -159,9 +159,9 @@ public async placeMarketOrderWithSmartRouting( i += 1; } } - const r = await this.provider.dryRunTransactionBlock({ + const r = await this.suiClient.dryRunTransactionBlock({ transactionBlock: await overrides.txb.build({ - provider: this.provider, + client: this.suiClient, }), }); if (r.effects.status.status === 'success') { diff --git a/docs/content/standards/deepbookv3-sdk.mdx b/docs/content/standards/deepbookv3-sdk.mdx index 93fcb5b1f1a3b..3e19f37a19aea 100644 --- a/docs/content/standards/deepbookv3-sdk.mdx +++ b/docs/content/standards/deepbookv3-sdk.mdx @@ -2,7 +2,14 @@ title: DeepBookV3 SDK --- -The DeepBook typescript SDK abstracts away the transaction calls, allowing for direct interactions with the DeepBook package. To use the SDK in your projects, install the `@mysten/deepbook` package. +The DeepBook typescript SDK abstracts away the transaction calls, allowing for direct interactions with the DeepBook package. + +- [SDK Repository](https://github.com/MystenLabs/sui/tree/main/sdk/deepbook-v3) +- [NPM version](https://www.npmjs.com/package/@mysten/deepbook-v3) + +## Install + +To use the SDK in your projects, install the `@mysten/deepbook` package. ```sh npm2yarn npm install @mysten/deepbook-v3 @@ -14,7 +21,7 @@ The DeepBookV3 SDK includes a constants file (`/utils/constants.ts`) that mainta
-Toggle constants.ts code +`constants.ts` {@inject: sdk/deepbook/src/utils/constants.ts}
diff --git a/docs/content/standards/deepbookv3-sdk/pools.mdx b/docs/content/standards/deepbookv3-sdk/pools.mdx index 2577a73c72bce..03befb48d7d2e 100644 --- a/docs/content/standards/deepbookv3-sdk/pools.mdx +++ b/docs/content/standards/deepbookv3-sdk/pools.mdx @@ -13,9 +13,58 @@ The DeepBookV3 SDK exposes functions that you can call to read the state of a po Decimal adjust all input quantities. All outputs are decimal adjusted. ::: +### account + +Use `account` to retrieve the account information for a `BalanceManager` in a pool, which has the following form: + +```tsx +{ + epoch: '511', + open_orders: { + constants: [ + '170141211130585342296014727715884105730', + '18446744092156295689709543266', + '18446744092156295689709543265' + ] + }, + taker_volume: 0, + maker_volume: 0, + active_stake: 0, + inactive_stake: 0, + created_proposal: false, + voted_proposal: null, + unclaimed_rebates: { base: 0, quote: 0, deep: 0 }, + settled_balances: { base: 0, quote: 0, deep: 0 }, + owed_balances: { base: 0, quote: 0, deep: 0 } +} +``` + +**Parameters** + +- `poolKey`: String that identifies the pool to query. +- `balanceManagerKey`: key of the balance manager defined in the SDK. + +```tsx +async account(poolKey: string, managerKey: string) {} +``` + +### accountOpenOrders + +Use `accountOpenOrders` to retrieve open orders for the balance manager and pool with the IDs you provide. The call returns a `Promise` that contains an array of open order IDs. + +**Parameters** + +- `poolKey`: String that identifies the pool to query. +- `managerKey`: String that identifies the balance manager to query. + +```tsx +async accountOpenOrders(poolKey: string, managerKey: string) {} +``` + ### checkManagerBalance Use `checkManagerBalance` to check the balance manager for a specific coin. The call returns a `Promise` in the form: + ``` { coinType: string, @@ -62,7 +111,6 @@ Use `getOrder` to retrieve an order's information. The call returns a `Promise` async getOrder(poolKey: string, orderId: string) {} ``` - ### getQuoteQuantityOut Use `getQuoteQuantityOut` to retrieve the quote quantity out for the base quantity you provide. The call returns a `Promise` in the form: @@ -131,19 +179,6 @@ where `deepRequired` is the amount of DEEP required for the dry run. async getQuantityOut(poolKey: string, baseQuantity: number, quoteQuantity: number) {} ``` -### accountOpenOrders - -Use `accountOpenOrders` to retrieve open orders for the balance manager and pool with the IDs you provide. The call returns a `Promise` that contains an array of open order IDs. - -**Parameters** - -- `poolKey`: String that identifies the pool to query. -- `managerKey`: String that identifies the balance manager to query. - -```tsx -async accountOpenOrders(poolKey: string, managerKey: string) {} -``` - ### getLevel2Range Use `getLevel2Range` to retrieve level 2 order book within the boundary price range you provide. The call returns a `Promise` in the form: @@ -186,10 +221,51 @@ Use `getLevel2TicksFromMid` to retrieve level 2 order book ticks from mid-price async getLevel2TicksFromMid(poolKey: string, ticks: number) {} ``` +### lockedBalance + +Use `lockedBalance` to retrieve a `BalanceManager` locked balance in the pool. The call returns a `Promise` in the `Order` struct, which has the following form: + +```tsx +{ + base: 5.5, + quote: 2, + deep: 0.15, +} +``` + +**Parameters** + +`poolKey`: String that identifies the pool to query. `balanceManagerKey`: key of the balance manager defined in the SDK. + +```tsx +async lockedBalance(poolKey: string, balanceManagerKey: string) {} +``` + +### poolTradeParams + +Use `poolTradeParams` to retrieve the trade params for the pool, which has the following form: + +```tsx +{ + takerFee: 0.001, + makerFee: 0.0005, + stakeRequired: 100, +} +``` + +**Parameters** + +- `poolKey`: String that identifies the pool to query. + +```tsx +async poolTradeParams(poolKey: string) {} +``` + ### vaultBalances Use `vaultBalances` to get the vault balances for a pool with the ID you provide. The call returns a `Promise` in the form: -``` + +```tsx { base: number, quote: number, diff --git a/docs/content/standards/deepbookv3/query-the-pool.mdx b/docs/content/standards/deepbookv3/query-the-pool.mdx index e8b80f796cadf..24ad2495c0591 100644 --- a/docs/content/standards/deepbookv3/query-the-pool.mdx +++ b/docs/content/standards/deepbookv3/query-the-pool.mdx @@ -57,6 +57,18 @@ public fun get_quantity_out( ): (u64, u64, u64) ``` +### Check fee required + +Returns the DEEP required for an order if it's a taker or maker given quantity and price (`deep_required_taker`, `deep_required_maker`). + +```move +public fun get_order_deep_required( + self: &Pool, + base_quantity: u64, + price: u64, +): (u64, u64) +``` + ### Retrieve mid price for a pool Returns the mid price of the pool. @@ -111,10 +123,76 @@ public fun vault_balances( ): (u64, u64, u64) ``` -### Retrieve Pool ID +### Retrieve pool ID Get the ID of the pool given the asset types. ```move public fun get_pool_id_by_asset(registry: &Registry): ID +``` + +### Retrieve order information + +Returns the `Order` struct using the order ID. + +```move +public fun get_order( + self: &Pool, + order_id: u128, +): Order +``` + +Returns a vector of `Order` structs using a vector of order IDs. + +```move +public fun get_orders( + self: &Pool, + order_ids: vector, +): vector +``` + +Returns a vector of `Order` structs for all orders that belong to a `BalanceManager` in the pool. + +```move +public fun get_account_order_details( + self: &Pool, + balance_manager: &BalanceManager, +): vector +``` + +### Retrieve locked balance + +Returns the locked balance for a `BalanceManager` in the pool (`base_quantity`, `quote_quantity`, `deep_quantity`). + +```move +public fun locked_balance( + self: &Pool, + balance_manager: &BalanceManager, +): (u64, u64, u64) +``` + +### Retrieve pool parameters + +Returns the trade parameters for the pool (`taker_fee`, `maker_fee`, `stake_required`). + +```move +public fun pool_trade_params( + self: &Pool, +): (u64, u64, u64) +``` + +Returns the book parameters for the pool (`tick_size`, `lot_size`, `min_size`). + +```move +public fun pool_book_params( + self: &Pool, +): (u64, u64, u64) +``` + +Returns the `OrderDeepPrice` struct for the pool, which determines the conversion for DEEP fees. + +```move +public fun get_order_deep_price( + self: &Pool, +): OrderDeepPrice ``` \ No newline at end of file diff --git a/docs/content/standards/wallet-standard.mdx b/docs/content/standards/wallet-standard.mdx index 6ea35087b588a..98244e5227581 100644 --- a/docs/content/standards/wallet-standard.mdx +++ b/docs/content/standards/wallet-standard.mdx @@ -179,7 +179,7 @@ registerWallet(new YourWallet()); ### Best practices for efficient transaction execution -The Wallet standard has been updated from its original design to better support changes in the Sui ecosystem. For example, the GraphQL service was introduced after Mainnet launched. The `sui:signAndExecuteTransactionBlock` feature is closely tied to the JSON RPC options and data structures, so its continued maintenance becomes increasingly difficult as the GraphQL service becomes more ubiquitous. +The Wallet standard has been updated from its original design to better support changes in the Sui ecosystem. For example, the GraphQL service was introduced after Mainnet launched. The `sui:signAndExecuteTransactionBlock` feature is closely tied to the JSON RPC options and data structures, so its continued maintenance becomes increasingly difficult as the GraphQL service becomes more ubiquitous. Consequently, the Wallet standard introduced the `sui:signAndExecuteTransaction` feature. The features of this method are more useful, regardless of which API you use to execute transactions. This usefulness comes at the expense of flexibility in what `sui:signAndExecuteTransaction` returns. @@ -214,12 +214,12 @@ The Wallet standard includes features to help your apps interact with wallets. To query the installed wallets in a user's browser, use the `get` function of `getWallets`. ```tsx -import { getWallets } from "@mysten/wallet-standard"; +import { getWallets } from '@mysten/wallet-standard'; const availableWallets = getWallets().get(); ``` -The return from this call (`availableWallets` in the previous code) is an array of `Wallet` types. +The return from this call (`availableWallets` in the previous code) is an array of `Wallet` types. Use the `Wallet.icon` and `Wallet.name` attributes to display the wallet details on your web page. @@ -238,7 +238,7 @@ Connecting in the context of a wallet refers to a user that joins the web site f The feature that provides this functionality is called `standard:connect`. To connect using this feature, make the following call: ```tsx -wallet.features['standard:connect'].connect() // connect call +wallet.features['standard:connect'].connect(); // connect call ``` This call results in the wallet opening a pop-up dialog for the user to continue the connection process. @@ -277,8 +277,10 @@ client.executeTransactionBlock({ Your app then sends the transaction effects back to the wallet, which reports results to the user. The wallet expects the effects to be `b64` encoded. ```tsx +import { toBase64 } from '@mysten/sui/utils'; + wallet.features['sui:reportTransactionEffects'].reportTransactionEffects( - effects: Array.isArray(transactionResponse.effects) ? toB64( + effects: Array.isArray(transactionResponse.effects) ? toBase64( Uint8Array.from(transactionResponse.effects) : transactionResponse.effects, account: wallet.accounts[0], // for example chain: wallet.chains[0] @@ -309,7 +311,7 @@ The wallet standard only defines the change event that can apply to chains, feat To subscribe your apps to events with the following call: ```tsx -const unsubscribe = wallet.features['standard:events'].on ('change', callback); +const unsubscribe = wallet.features['standard:events'].on('change', callback); ``` This call returns a function that can be called to unsubscribe from listening to the events. diff --git a/docs/site/docusaurus.config.js b/docs/site/docusaurus.config.js index 5d34bcbe6ef36..f9b79cb1ac8f9 100644 --- a/docs/site/docusaurus.config.js +++ b/docs/site/docusaurus.config.js @@ -6,7 +6,8 @@ import path from "path"; import math from "remark-math"; import katex from "rehype-katex"; -const betatag = require("./src/plugins/betatag"); +const effortRemarkPlugin = require("./src/plugins/effort"); +const betaRemarkPlugin = require("./src/plugins/betatag"); require("dotenv").config(); @@ -51,10 +52,11 @@ const config = { }, plugins: [ // .... + // path.resolve(__dirname, `./src/plugins/examples`), [ "posthog-docusaurus", { - apiKey: process.env.POSTHOG_API_KEY || 'dev', // required + apiKey: process.env.POSTHOG_API_KEY || "dev", // required appUrl: "https://us.i.posthog.com", // optional, defaults to "https://us.i.posthog.com" enableInDevelopment: false, // optional }, @@ -63,8 +65,7 @@ const config = { [ "@graphql-markdown/docusaurus", { - schema: - "../../crates/sui-graphql-rpc/schema.graphql", + schema: "../../crates/sui-graphql-rpc/schema.graphql", rootPath: "../content", // docs will be generated under rootPath/baseURL baseURL: "references/sui-api/sui-graphql/reference", loaders: { @@ -116,13 +117,18 @@ const config = { "current", "1.0.0", ],*/ + admonitions: { + keywords: ["checkpoint"], + extendDefaults: true, + }, remarkPlugins: [ math, [ require("@docusaurus/remark-plugin-npm2yarn"), { sync: true, converters: ["yarn", "pnpm"] }, ], - betatag, + effortRemarkPlugin, + betaRemarkPlugin, ], rehypePlugins: [katex], }, @@ -240,7 +246,7 @@ const config = { prism: { theme: themes.github, darkTheme: themes.nightOwl, - additionalLanguages: ["rust", "typescript", "toml"], + additionalLanguages: ["rust", "typescript", "toml", "json"], }, }), }; diff --git a/docs/site/src/components/BetaTag/index.tsx b/docs/site/src/components/BetaTag/index.tsx index eec85d99a686c..056651704e023 100644 --- a/docs/site/src/components/BetaTag/index.tsx +++ b/docs/site/src/components/BetaTag/index.tsx @@ -12,17 +12,17 @@ export default function BetaTag(props) { } const beta = props.beta.toLowerCase(); - + // If `props.slim` is included, do not add spacing because the box is inline with + // content as opposed to at top of topic based on `beta` frontmatter. return (

- The content in this topic describes a beta feature or service. Beta - features and services are in active development, so details are likely - to change. + This content describes a beta feature or service. Beta features and + services are in active development, so details are likely to change.

{(beta.includes("testnet") || beta.includes("devnet") || diff --git a/docs/site/src/components/EffortBox/index.tsx b/docs/site/src/components/EffortBox/index.tsx new file mode 100644 index 0000000000000..a1de99f888bb0 --- /dev/null +++ b/docs/site/src/components/EffortBox/index.tsx @@ -0,0 +1,45 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// This component is used as part of the effort plugin. +// It appears at the top of any guide with an `effort` +// rating (small, medium, large) in its frontmatter. + +import React from "react"; +import Admonition from "@theme/Admonition"; + +export default function EffortBox(props) { + if (!props.effort) { + return; + } + + function timeAndEffort(effort: string): [string, string] { + if (effort[0] === "s") { + return ["30-45 minutes", "basic"]; + } else if (effort[0] === "m") { + return ["1-1.5 hours", "involved"]; + } else { + return ["2 hours or more", "advanced"]; + } + } + + const [time, effort] = timeAndEffort(props.effort); + return ( + +

+ This guide is rated as {effort}. +

+

+ You can expect {effort} guides to take{" "} + {time} of dedicated time. The length + of time necessary to fully understand some of the concepts raised in + this guide might increase this estimate. +

+
+ ); +} diff --git a/docs/site/src/css/custom.css b/docs/site/src/css/custom.css index c0495d0e3399b..c75f8aa59dab7 100644 --- a/docs/site/src/css/custom.css +++ b/docs/site/src/css/custom.css @@ -7,6 +7,23 @@ @tailwind components; @tailwind utilities; + @layer utilities { + .bg-checkerboard { + background-image: linear-gradient(45deg, var(--sui-black) 25%, transparent 25%, transparent 75%, var(--sui-black) 75%, var(--sui-black)), + linear-gradient(45deg, var(--sui-black) 25%, transparent 25%, transparent 75%, var(--sui-black) 75%, var(--sui-black)); + background-size: 20px 20px; + background-position: 0 0, 10px 10px; + border-right: 1px solid var(--sui-blue); + } + .bg-checkerboard-dark { + background-image: linear-gradient(45deg, rgb(var(--sui-blue-dark)) 25%, transparent 25%, transparent 75%, rgb(var(--sui-blue-dark)) 75%, rgb(var(--sui-blue-dark))), + linear-gradient(45deg, rgb(var(--sui-blue-dark)) 25%, transparent 25%, transparent 75%, rgb(var(--sui-blue-dark)) 75%, rgb(var(--sui-blue-dark))); + background-size: 20px 20px; + background-position: 0 0, 10px 10px; + border-right: 1px solid rgb(var(--sui-blue-dark)); + } +} + /* You can override the default Infima variables here. */ :root { --ifm-color-primary: #4ca2ff; @@ -354,7 +371,21 @@ h4 { color: var(--sui-white); text-decoration: none; } +/* +details { + @apply !bg-sui-gray-45 !border-sui-gray-65 relative before:content-["Click\20to\20toggle"] before:absolute before:-top-3 before:-left-1 before:text-xs before:bg-white before:px-2 before:py-0.5 before:rounded before:border before:border-sui-gray-65 before:border-solid before:opacity-0 hover:before:opacity-100 before:duration-300 before:transition-opacity; +} +[data-theme='dark'] details { + @apply !bg-sui-gray-90 !border-sui-gray-65; +} +*/ +summary::before { + @apply !border-l-sui-gray-65; +} +details > div > div { + @apply !border-t-sui-gray-65; +} @media (max-width: 1050px) { .navbar .button-cta { display: none; diff --git a/docs/site/src/pages/index.js b/docs/site/src/pages/index.js index 7fa05c6824877..35e4c66203ce0 100644 --- a/docs/site/src/pages/index.js +++ b/docs/site/src/pages/index.js @@ -70,6 +70,23 @@ export default function Home() { Run a Sui Full node + + Sui Bridge Node configuration + + + + + Tokenomics + + + Cryptography + + + Standards + diff --git a/docs/site/src/plugins/effort/index.js b/docs/site/src/plugins/effort/index.js new file mode 100644 index 0000000000000..39d951f5193c0 --- /dev/null +++ b/docs/site/src/plugins/effort/index.js @@ -0,0 +1,29 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Plugin processes example sizes in the frontmatter to +// place a admonition box explaining the rating. + +function effortRemarkPlugin() { + return (tree, file) => { + if (file.data.frontMatter && file.data.frontMatter.effort) { + const effortValue = file.data.frontMatter.effort; + // Create a new node that represents the custom component + const customComponentNode = { + type: "mdxJsxFlowElement", + name: "EffortBox", + attributes: [ + { + type: "mdxJsxAttribute", + name: "effort", + value: effortValue, + }, + ], + children: [], + }; + tree.children.unshift(customComponentNode); + } + }; +} + +module.exports = effortRemarkPlugin; diff --git a/docs/site/src/plugins/inject-code/injectLoader.js b/docs/site/src/plugins/inject-code/injectLoader.js index 8aee6e91a2fc2..4c7629338b4b4 100644 --- a/docs/site/src/plugins/inject-code/injectLoader.js +++ b/docs/site/src/plugins/inject-code/injectLoader.js @@ -54,12 +54,20 @@ const addCodeInject = function (source) { case "rs": language = "rust"; break; + case "prisma": + language = "ts"; + break; default: language = fileExt; } + const isMove = language === "move"; + const isTs = language === "ts" || language === "js"; + if (fs.existsSync(fullPath)) { - let injectFileContent = fs.readFileSync(fullPath, "utf8"); + let injectFileContent = fs + .readFileSync(fullPath, "utf8") + .replaceAll(`\t`, " "); const marker = injectFileFull.indexOf("#") > 0 ? injectFileFull.substring(injectFileFull.indexOf("#")) @@ -69,6 +77,11 @@ const addCodeInject = function (source) { const funKey = "#fun="; const structKey = "#struct="; const moduleKey = "#module="; + const varKey = "#variable="; + const useKey = "#use="; + const componentKey = "#component="; + const enumKey = "#enum="; + const typeKey = "#type="; const getName = (mark, key) => { return mark.indexOf(key, mark) >= 0 ? mark.substring(mark.indexOf(key) + key.length).trim() @@ -77,12 +90,23 @@ const addCodeInject = function (source) { const funName = getName(marker, funKey); const structName = getName(marker, structKey); const moduleName = getName(marker, moduleKey); + const variableName = getName(marker, varKey); + const useName = getName(marker, useKey); + const componentName = getName(marker, componentKey); + const enumName = getName(marker, enumKey); + const typeName = getName(marker, typeKey); if (funName) { const funs = funName.split(","); let funContent = []; + for (let fn of funs) { fn = fn.trim(); - const funStr = `^(\\s*)*?(public )?fun \\b${fn}\\b.*?}\\n(\\s*?})?(?=\\n)?`; + let funStr = ""; + if (isMove) { + funStr = `^(\\s*)*?(pub(lic)? )?fu?n \\b${fn}\\b.*?}\\n(\\s*?})?(?=\\n)?`; + } else if (isTs) { + funStr = `^(\\s*)(async )?(export (default )?)?function \\b${fn}\\b.*?\\n\\1}\\n`; + } const funRE = new RegExp(funStr, "msi"); const funMatch = funRE.exec(injectFileContent); if (funMatch) { @@ -92,6 +116,7 @@ const addCodeInject = function (source) { ); // Check if last function in module, removing last } if true. if ( + isMove && funMatch[0].match(/}\s*}\s*$/s) && !utils.checkBracesBalance(funMatch[0]) ) { @@ -134,6 +159,186 @@ const addCodeInject = function (source) { } } injectFileContent = structContent.join("\n").trim(); + } else if (variableName) { + const vs = variableName.split(","); + let temp = ""; + let isGroup = false; + let groupedVars = []; + vs.forEach((v) => { + if (v.startsWith("(")) { + temp = v; + isGroup = true; + } else if (isGroup) { + temp += ", " + v; + if (temp.endsWith(")")) { + groupedVars.push(temp); + temp = ""; + isGroup = false; + } + } else { + groupedVars.push(v); + } + }); + let varContent = []; + if (language === "ts" || language === "js") { + const varTsFunction = `^( *)?.*?(let|const) \\b${variableName}\\b.*=>`; + const varTsVariable = `^( *)?.*?(let|const) \\b${variableName}\\b (?!.*=>)=.*;`; + const varTsRE = new RegExp(varTsFunction, "m"); + const varTsVarRE = new RegExp(varTsVariable, "m"); + const varTsMatch = varTsRE.exec(injectFileContent); + const varTsVarMatch = varTsVarRE.exec(injectFileContent); + if (varTsMatch) { + const start = injectFileContent.slice(varTsMatch.index); + const endText = `^${varTsMatch[1] ? varTsMatch[1] : ""}\\)?\\};`; + const endRE = new RegExp(endText, "m"); + const endMatch = endRE.exec(start); + let preVarTs = utils.capturePrepend( + varTsMatch, + injectFileContent, + ); + varContent.push( + utils.removeLeadingSpaces( + start.slice(0, endMatch.index + endMatch[0].length), + preVarTs, + ), + ); + } else if (varTsVarMatch) { + let preVarTs2 = utils.capturePrepend( + varTsVarMatch, + injectFileContent, + ); + varContent.push( + utils.removeLeadingSpaces(varTsVarMatch[0], preVarTs2), + ); + } + } else { + for (let v of groupedVars) { + v = v.trim(); + const varStrShort = `^(\\s*)?(#\\[test_only\\])?(let|const) \\(?.*?\\b${v}\\b.*?\\)?\\s?=.*;`; + //const varStrLong = `^(\\s*)?(#\\[test_only\\])?(let|const) ${v}.*\\{.*\\};\\n`; + const varStrLong = `^(\\s*)?(#\\[test_only\\])?(let|const) \\(?.*?\\b${v}\\b.*?\\)?\\s?= \\{[^}]*\\};\\n`; + const varREShort = new RegExp(varStrShort, "m"); + const varRELong = new RegExp(varStrLong, "m"); + const varShortMatch = varREShort.exec(injectFileContent); + const varLongMatch = varRELong.exec(injectFileContent); + if (varShortMatch || varLongMatch) { + let varMatch = varShortMatch + ? varShortMatch + : varLongMatch; + let preVar = utils.capturePrepend( + varMatch, + injectFileContent, + ); + varContent.push( + utils.removeLeadingSpaces(varMatch[0], preVar), + ); + } else { + injectFileContent = + "Variable not found. If code is formatted correctly, consider using code comments instead."; + } + } + } + + injectFileContent = varContent.join("\n").trim(); + } else if (useName) { + const us = useName.split(","); + let useContent = []; + for (let u of us) { + u = u.trim(); + const uArray = u.split("::"); + const useStr = `^( *)(#\\[test_only\\] )?use ${uArray[0]}::\\{?.*?${uArray[1] ? uArray[1] : ""}.*?\\};`; + const useRE = new RegExp(useStr, "ms"); + const useMatch = useRE.exec(injectFileContent); + if (useMatch) { + let preUse = utils.capturePrepend( + useMatch, + injectFileContent, + ); + useContent.push( + utils.removeLeadingSpaces(useMatch[0], preUse), + ); + } else { + injectFileContent = + "Use statement not found. If code is formatted correctly, consider using code comments instead."; + } + } + + injectFileContent = useContent.join("\n").trim(); + } else if (componentName) { + const components = componentName.split(","); + let componentContent = []; + for (let comp of components) { + let names = []; + let name = comp; + let element = ""; + let ordinal = ""; + if (comp.indexOf(":") > 0) { + names = comp.split(":"); + name = names[0]; + element = names[1]; + ordinal = names[2] ? names[2] : ""; + } + const compStr = `^( *)(export (default )?)?function \\b${name}\\b.*?\\n\\1\\}\\n`; + const compRE = new RegExp(compStr, "ms"); + const compMatch = compRE.exec(injectFileContent); + if (compMatch) { + if (element) { + const elStr = `^( *)\\<${element}\\b.*?>.*?\\<\\/${element}>`; + const elRE = new RegExp(elStr, "msg"); + let elementsToKeep = [1]; + if (ordinal) { + if ( + ordinal.indexOf("-") > 0 && + ordinal.indexOf("&") > 0 + ) { + console.log( + "Only dashes or commas allowed for selecting component elements, not both.", + ); + } else { + if (ordinal.indexOf("-") > 0) { + const [start, end] = ordinal.split("-").map(Number); + elementsToKeep = Array.from( + { length: end - start + 1 }, + (_, i) => start + i, + ); + } + if (ordinal.indexOf("&") > 0) { + elementsToKeep = ordinal.split("&").map(Number); + } + } + } + elementsToKeep.sort((a, b) => a - b); + for ( + let x = 0; + x < elementsToKeep[elementsToKeep.length - 1]; + x++ + ) { + const elMatch = elRE.exec(compMatch); + if (elementsToKeep.includes(x + 1)) { + componentContent.push( + utils.removeLeadingSpaces(elMatch[0]), + ); + } else { + if ( + x > 0 && + componentContent[x - 1].trim() !== "..." + ) { + componentContent.push("\n..."); + } + } + } + } else { + let preComp = utils.capturePrepend( + compMatch, + injectFileContent, + ); + componentContent.push( + utils.removeLeadingSpaces(compMatch[0], preComp), + ); + } + } + } + injectFileContent = componentContent.join("\n").trim(); } else if (moduleName) { const modStr = `^(\\s*)*module \\b${moduleName}\\b.*?}\\n(?=\\n)?`; const modRE = new RegExp(modStr, "msi"); @@ -164,6 +369,45 @@ const addCodeInject = function (source) { injectFileContent = "Module not found. If code is formatted correctly, consider using code comments instead."; } + } else if (enumName) { + const enums = enumName.split(","); + let enumContent = []; + for (let e of enums) { + const enumStr = `^( *)(export)? enum \\b${e}\\b\\s*\\{[^}]*\\}`; + const enumRE = new RegExp(enumStr, "m"); + const enumMatch = enumRE.exec(injectFileContent); + if (enumMatch) { + enumContent.push(utils.removeLeadingSpaces(enumMatch[0])); + } + } + injectFileContent = enumContent.join("\n").trim(); + } else if (typeName) { + const types = typeName.split(","); + let typeContent = []; + for (let t of types) { + const typeStartStr = `^( *)(export )?type \\b${t}\\b`; + const typeRE = new RegExp(typeStartStr, "m"); + const typeMatch = typeRE.exec(injectFileContent); + if (typeMatch) { + let typeSubContent = injectFileContent.slice( + typeMatch.index, + ); + const spaces = typeMatch[1] ? typeMatch[1] : ""; + const endStr = `^${spaces}\\};`; + const endRE = new RegExp(endStr, "m"); + const endMatch = endRE.exec(typeSubContent); + if (endMatch) { + typeSubContent = typeSubContent.slice( + 0, + endMatch.index + endMatch[0].length, + ); + } else { + typeSubContent = "Error capturing type declaration."; + } + typeContent.push(utils.removeLeadingSpaces(typeSubContent)); + } + } + injectFileContent = typeContent.join("\n").trim(); } else { const regexStr = `\\/\\/\\s?docs::${marker.trim()}\\b([\\s\\S]*)\\/\\/\\s*docs::\\/\\s?${marker.trim()}\\b`; const closingsStr = `\\/\\/\\s?docs::\\/${marker.trim()}\\b([)};]*)`; @@ -214,9 +458,10 @@ const addCodeInject = function (source) { "\\$&", ); let replacer = ""; + if (matches[match].indexOf("-pause:") > 0) { replacer = matches[match].substring( - matches[match].indexOf("-pause") + 8, + matches[match].indexOf("-pause") + 7, ); } @@ -259,6 +504,7 @@ const addCodeInject = function (source) { language, injectFile, injectFileContent, + options, ); res = res.replace(replacer, injectFileContent); res = addMarkdownIncludes(res); @@ -272,8 +518,14 @@ const addCodeInject = function (source) { language, injectFile, processed, + options, + ); + // Temporarily replace double spaces with tabs. Replaced back downstream. + // Prevents unexpected whitespace removal from util functions. + res = res.replace( + replacer, + processedFileContent.replace(/ {2}/g, `\t`), ); - res = res.replace(replacer, processedFileContent); } } else { res = res.replace( diff --git a/docs/site/src/plugins/inject-code/utils.js b/docs/site/src/plugins/inject-code/utils.js index 1911adce2cbfd..87370f9ba078d 100644 --- a/docs/site/src/plugins/inject-code/utils.js +++ b/docs/site/src/plugins/inject-code/utils.js @@ -21,12 +21,15 @@ exports.removeLeadingSpaces = (codeText, prepend = "") => { // Options are added to the @inject command by appending // a space delimited list +const isOption = (opts, option) => { + return option + ? opts.some((element) => element.toLowerCase().includes(option)) + : false; +}; + // Remove comments. TODO: Add other langs const removeComments = (text, options) => { - const cont = options.some((element) => - element.toLowerCase().includes("nocomment"), - ); - if (cont) { + if (isOption(options, "nocomment")) { return text.replace(/^ *\/\/.*\n/gm, ""); } else { return text; @@ -34,10 +37,7 @@ const removeComments = (text, options) => { }; const removeTests = (text, options) => { - const cont = options.some((element) => - element.toLowerCase().includes("notest"), - ); - if (cont) { + if (isOption(options, "notest")) { const processed = text .replace( /\s*#\[test.*?\n.*?(}(?!;)\n?|$)/gs, @@ -50,6 +50,16 @@ const removeTests = (text, options) => { } }; +// Remove double spaces from output when changing the code is not preferred. +const singleSpace = (text, options) => { + if (isOption(options, "singlespace")) { + const processed = text.replace(/^\s*[\r\n]/gm, ""); + return processed; + } else { + return text; + } +}; + // Remove blank lines from beginning and end of code source // but leave whitespace indentation alone. Also, replace multiple // blank lines that occur in succession. @@ -89,6 +99,7 @@ exports.processOptions = (text, options) => { ); processed = removeComments(processed, options); processed = removeTests(processed, options); + processed = singleSpace(processed, options); processed = trimContent(processed); return processed; @@ -108,8 +119,9 @@ exports.capturePrepend = (match, text) => { let pre = []; for (let x = lines.length - 1; x > 0; x--) { if ( - lines[x].match(/^\s*\/\//) || - lines[x].match(/^\s*#/) || + lines[x].match(/^ *\//) || + lines[x].match(/^ *\*/) || + lines[x].match(/^ *#/) || lines[x].trim() === "" ) { // Capture sometimes incorrectly includes a blank line @@ -134,6 +146,9 @@ exports.checkBracesBalance = (str) => { }; // Output codeblocks -exports.formatOutput = (language, title, content) => { - return `\`\`\`${language} title="${title}"\n${content}\n\`\`\``; +exports.formatOutput = (language, title, content, options) => { + if (options && isOption(options, "notitle")) { + return `\`\`\`${language}\n${content.replace(/\t/g, " ")}\n\`\`\``; + } + return `\`\`\`${language} title="${title}"\n${content.replace(/\t/g, " ")}\n\`\`\``; }; diff --git a/docs/site/src/theme/Admonition/Types.js b/docs/site/src/theme/Admonition/Types.js new file mode 100644 index 0000000000000..758734c32c02a --- /dev/null +++ b/docs/site/src/theme/Admonition/Types.js @@ -0,0 +1,30 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import React from "react"; +import DefaultAdmonitionTypes from "@theme-original/Admonition/Types"; + +function CheckpointAdmonition(props) { + return ( +
+
+
+
+ CHECKPOINT +
+
{props.title}
+
{props.children}
+
+
+ ); +} + +const AdmonitionTypes = { + ...DefaultAdmonitionTypes, + + // Add all your custom admonition types here... + // You can also override the default ones if you want + checkpoint: CheckpointAdmonition, +}; + +export default AdmonitionTypes; diff --git a/docs/site/src/theme/CodeBlock/Content/String.js b/docs/site/src/theme/CodeBlock/Content/String.js new file mode 100644 index 0000000000000..d7e2c53f80974 --- /dev/null +++ b/docs/site/src/theme/CodeBlock/Content/String.js @@ -0,0 +1,124 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import React from "react"; +import clsx from "clsx"; +import { useThemeConfig, usePrismTheme } from "@docusaurus/theme-common"; +import { + parseCodeBlockTitle, + parseLanguage, + parseLines, + containsLineNumbers, + useCodeWordWrap, +} from "@docusaurus/theme-common/internal"; +import { Highlight } from "prism-react-renderer"; +import Line from "@theme/CodeBlock/Line"; +import CopyButton from "@theme/CodeBlock/CopyButton"; +import WordWrapButton from "@theme/CodeBlock/WordWrapButton"; +import Container from "@theme/CodeBlock/Container"; +import styles from "./styles.module.css"; + +// Prism languages are always lowercase +// We want to fail-safe and allow both "php" and "PHP" +// See https://github.com/facebook/docusaurus/issues/9012 +function normalizeLanguage(language) { + return language?.toLowerCase(); +} +export default function CodeBlockString({ + children, + className: blockClassName = "", + metastring, + title: titleProp, + showLineNumbers: showLineNumbersProp, + language: languageProp, +}) { + const { + prism: { defaultLanguage, magicComments }, + } = useThemeConfig(); + const language = normalizeLanguage( + languageProp ?? parseLanguage(blockClassName) ?? defaultLanguage, + ); + const prismTheme = usePrismTheme(); + const wordWrap = useCodeWordWrap(); + // We still parse the metastring in case we want to support more syntax in the + // future. Note that MDX doesn't strip quotes when parsing metastring: + // "title=\"xyz\"" => title: "\"xyz\"" + const title = parseCodeBlockTitle(metastring) || titleProp; + const { lineClassNames, code } = parseLines(children, { + metastring, + language, + magicComments, + }); + const showLineNumbers = + showLineNumbersProp ?? containsLineNumbers(metastring); + + // Sui added code. + // Change component to render title as anchor. + const sourceLink = + title && !title.match(/^http/) + ? `https://github.com/MystenLabs/sui/tree/main/${title}` + : title; + const tailwind = "relative "; + + return ( + + {title && ( +
+ )} +
+ + {({ className, style, tokens, getLineProps, getTokenProps }) => ( +
+              
+                {tokens.map((line, i) => (
+                  
+                ))}
+              
+            
+ )} +
+
+ {(wordWrap.isEnabled || wordWrap.isCodeScrollable) && ( + wordWrap.toggle()} + isEnabled={wordWrap.isEnabled} + /> + )} + +
+
+ + ); +} diff --git a/docs/site/src/theme/CodeBlock/Content/styles.module.css b/docs/site/src/theme/CodeBlock/Content/styles.module.css new file mode 100644 index 0000000000000..c2103cd7ea4c7 --- /dev/null +++ b/docs/site/src/theme/CodeBlock/Content/styles.module.css @@ -0,0 +1,80 @@ +.codeBlockContent { + position: relative; + /* rtl:ignore */ + direction: ltr; + border-radius: inherit; +} + +.codeBlockTitle { + border-bottom: 1px solid var(--ifm-color-emphasis-300); + font-size: var(--ifm-code-font-size); + font-weight: 500; + padding: 0.75rem var(--ifm-pre-padding); + border-top-left-radius: inherit; + border-top-right-radius: inherit; +} + +.codeBlock { + --ifm-pre-background: var(--prism-background-color); + margin: 0; + padding: 0; +} + +.codeBlockTitle + .codeBlockContent .codeBlock { + border-top-left-radius: 0; + border-top-right-radius: 0; +} + +.codeBlockStandalone { + padding: 0; +} + +.codeBlockLines { + font: inherit; + /* rtl:ignore */ + float: left; + min-width: 100%; + padding: var(--ifm-pre-padding); +} + +.codeBlockLinesWithNumbering { + display: table; + padding: var(--ifm-pre-padding) 0; +} + +@media print { + .codeBlockLines { + white-space: pre-wrap; + } +} + +.buttonGroup { + display: flex; + column-gap: 0.2rem; + position: absolute; + /* rtl:ignore */ + right: calc(var(--ifm-pre-padding) / 2); + top: calc(var(--ifm-pre-padding) / 2); +} + +.buttonGroup button { + display: flex; + align-items: center; + background: var(--prism-background-color); + color: var(--prism-color); + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: var(--ifm-global-radius); + padding: 0.4rem; + line-height: 0; + transition: opacity var(--ifm-transition-fast) ease-in-out; + opacity: 0; +} + +.buttonGroup button:focus-visible, +.buttonGroup button:hover { + opacity: 1 !important; +} + +:global(.theme-code-block:hover) .buttonGroup button { + opacity: 0.4; +} diff --git a/docs/site/src/theme/MDXComponents/Details.js b/docs/site/src/theme/MDXComponents/Details.js new file mode 100644 index 0000000000000..eee9c9efe73ba --- /dev/null +++ b/docs/site/src/theme/MDXComponents/Details.js @@ -0,0 +1,59 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import React, { useState, useEffect, useRef } from "react"; +import Details from "@theme/Details"; +export default function MDXDetails(props) { + const [hover, setHover] = useState(false); + const [isOpen, setIsOpen] = useState(false); + const handleMouseEnter = () => { + setHover(true); + }; + const handleMouseLeave = () => { + setHover(false); + }; + const handleClick = () => { + setIsOpen(!isOpen); + }; + const items = React.Children.toArray(props.children); + const mergeHandlers = (originalHandler, newHandler) => (event) => { + if (originalHandler) { + originalHandler(event); + } + if (newHandler) { + newHandler(event); + } + }; + // Split summary item from the rest to pass it as a separate prop to the + // Details theme component + const summary = items.find( + (item) => React.isValidElement(item) && item.type === "summary", + ); + const children = <>{items.filter((item) => item !== summary)}; + + const enhancedSummary = summary + ? React.cloneElement(summary, { + onMouseEnter: handleMouseEnter, + onMouseLeave: handleMouseLeave, + onClick: mergeHandlers(summary.props.onClick, handleClick), + className: `${summary.props.className || ""}`, // Add custom class to summary + }) + : null; + + return ( +
+ + Click to {isOpen ? "close" : "open"} + +
+ {children} +
+
+ ); +} diff --git a/docs/site/src/theme/MDXContent/index.js b/docs/site/src/theme/MDXContent/index.js index 98103098097f2..0b98d5c9480c9 100644 --- a/docs/site/src/theme/MDXContent/index.js +++ b/docs/site/src/theme/MDXContent/index.js @@ -6,6 +6,7 @@ import MDXComponents from "@theme/MDXComponents"; import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; import { Card, Cards } from "@site/src/components/Cards"; +import EffortBox from "@site/src/components/EffortBox"; import BetaTag from "@site/src/components/BetaTag"; export default function MDXContent({ children }) { const suiComponents = { @@ -14,6 +15,7 @@ export default function MDXContent({ children }) { Cards, Tabs, TabItem, + EffortBox, BetaTag, }; return {children}; diff --git a/docs/site/vercel.json b/docs/site/vercel.json index 47324006b85af..82879ec344c34 100644 --- a/docs/site/vercel.json +++ b/docs/site/vercel.json @@ -107,6 +107,7 @@ { "source": "/standards/trade-and-swap", "destination": "/standards/deepbookv2/trade-and-swap", "permanent": true }, { "source": "/standards/query-the-pool", "destination": "/standards/deepbookv2/query-the-pool", "permanent": true }, { "source": "/standards/deepbook-design", "destination": "/standards/deepbookv2/design", "permanent": true }, - { "source": "/standards/deepbook-orders", "destination": "/standards/deepbookv2/orders", "permanent": true } + { "source": "/standards/deepbook-orders", "destination": "/standards/deepbookv2/orders", "permanent": true }, + { "source": "/concepts/events", "destination": "/guides/developer/sui-101/using-events", "permanent": true } ] } diff --git a/examples/custom-indexer/rust/local_reader.rs b/examples/custom-indexer/rust/local_reader.rs index 6d372749f3600..17f93f3d33e7d 100644 --- a/examples/custom-indexer/rust/local_reader.rs +++ b/examples/custom-indexer/rust/local_reader.rs @@ -16,6 +16,7 @@ struct CustomWorker; #[async_trait] impl Worker for CustomWorker { + type Result = (); async fn process_checkpoint(&self, checkpoint: CheckpointData) -> Result<()> { // custom processing logic println!("Processing Local checkpoint: {}", checkpoint.checkpoint_summary.to_string()); diff --git a/examples/custom-indexer/rust/remote_reader.rs b/examples/custom-indexer/rust/remote_reader.rs index 4c55fcb960772..ed91f1523a5de 100644 --- a/examples/custom-indexer/rust/remote_reader.rs +++ b/examples/custom-indexer/rust/remote_reader.rs @@ -10,6 +10,7 @@ struct CustomWorker; #[async_trait] impl Worker for CustomWorker { + type Result = (); async fn process_checkpoint(&self, checkpoint: CheckpointData) -> Result<()> { // custom processing logic // print out the checkpoint number diff --git a/examples/tic-tac-toe/ui/src/components/NewMultiSigGame.tsx b/examples/tic-tac-toe/ui/src/components/NewMultiSigGame.tsx index d1770005093ef..1bf7d988db16b 100644 --- a/examples/tic-tac-toe/ui/src/components/NewMultiSigGame.tsx +++ b/examples/tic-tac-toe/ui/src/components/NewMultiSigGame.tsx @@ -3,7 +3,7 @@ import { useCurrentAccount } from '@mysten/dapp-kit'; import { PublicKey } from '@mysten/sui/cryptography'; -import { fromB64, toB64 } from '@mysten/sui/utils'; +import { fromBase64, toBase64 } from '@mysten/sui/utils'; import { publicKeyFromRawBytes } from '@mysten/sui/verify'; import { ExclamationTriangleIcon } from '@radix-ui/react-icons'; import { Box, Button, Em, Flex, Separator, Spinner, Text, TextField } from '@radix-ui/themes'; @@ -50,7 +50,7 @@ export function NewMultiSigGame(): ReactElement { return ( <> - + { if (totalBears < 5) throw new Error('Please create at least 5 bears to run this script.'); - const txb = new TransactionBlock(); + const txb = new Transaction(); const toTransfer = []; const DEMO_BEAR_TYPE = `${CONFIG.DEMO_CONTRACT.packageId}::demo_bear::DemoBear`; diff --git a/examples/trading/api/helpers/create-demo-escrows.ts b/examples/trading/api/helpers/create-demo-escrows.ts index b013c3c02fe51..98e11d9c59229 100644 --- a/examples/trading/api/helpers/create-demo-escrows.ts +++ b/examples/trading/api/helpers/create-demo-escrows.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { TransactionBlock } from '@mysten/sui/transactions'; +import { Transaction } from '@mysten/sui/transactions'; import { CONFIG } from '../config'; import { getActiveAddress, getClient, signAndExecute } from '../sui-utils'; @@ -77,7 +77,7 @@ const createEscrows = async (total: number) => { }); } - const txb = new TransactionBlock(); + const txb = new Transaction(); for (const tuple of tuples) { if (!tuple.bear) break; diff --git a/examples/trading/api/sui-utils.ts b/examples/trading/api/sui-utils.ts index 24b5bdcbc69e7..022bec8dd4d1c 100644 --- a/examples/trading/api/sui-utils.ts +++ b/examples/trading/api/sui-utils.ts @@ -8,7 +8,7 @@ import path from 'path'; import { getFullnodeUrl, SuiClient } from '@mysten/sui/client'; import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; import { Transaction } from '@mysten/sui/transactions'; -import { fromB64 } from '@mysten/sui/utils'; +import { fromBase64 } from '@mysten/sui/utils'; export type Network = 'mainnet' | 'testnet' | 'devnet' | 'localnet'; @@ -29,7 +29,7 @@ export const getSigner = () => { ); for (const priv of keystore) { - const raw = fromB64(priv); + const raw = fromBase64(priv); if (raw[0] !== 0) { continue; } diff --git a/examples/trading/contracts/escrow/sources/lock.move b/examples/trading/contracts/escrow/sources/lock.move index fa1b56c91d185..ad8e1c1cdefb6 100644 --- a/examples/trading/contracts/escrow/sources/lock.move +++ b/examples/trading/contracts/escrow/sources/lock.move @@ -112,7 +112,7 @@ module escrow::lock { let (lock, key) = lock(coin, ts.ctx()); let coin = lock.unlock(key); - coin::burn_for_testing(coin); + coin.burn_for_testing(); ts.end(); } diff --git a/examples/trading/contracts/escrow/sources/shared.move b/examples/trading/contracts/escrow/sources/shared.move index b927be7d11a27..a22e828f60394 100644 --- a/examples/trading/contracts/escrow/sources/shared.move +++ b/examples/trading/contracts/escrow/sources/shared.move @@ -65,6 +65,7 @@ module escrow::shared { // === Public Functions === + //docs::#noemit public fun create( escrowed: T, exchange_key: ID, @@ -78,6 +79,7 @@ module escrow::shared { exchange_key, }; + //docs::#noemit-pause event::emit(EscrowCreated { escrow_id: object::id(&escrow), key_id: exchange_key, @@ -85,11 +87,13 @@ module escrow::shared { recipient, item_id: object::id(&escrowed), }); + //docs::#noemit-resume dof::add(&mut escrow.id, EscrowedObjectKey {}, escrowed); transfer::public_share_object(escrow); } + //docs::/#noemit /// The `recipient` of the escrow can exchange `obj` with the escrowed item public fun swap( @@ -184,9 +188,12 @@ module escrow::shared { coin::mint_for_testing(42, ts.ctx()) } + //docs::#test #[test] fun test_successful_swap() { let mut ts = ts::begin(@0x0); + + //docs::#test-pause:// Rest of the test ... // Bob locks the object they want to trade. let (i2, ik2) = { @@ -212,6 +219,7 @@ module escrow::shared { // Bob responds by offering their object, and gets Alice's object in // return. + // docs::#bob { ts.next_tx(BOB); let escrow: Escrow> = ts.take_shared(); @@ -221,7 +229,9 @@ module escrow::shared { transfer::public_transfer(c, BOB); }; + // docs::/#bob + // docs::#finish // Commit effects from the swap ts.next_tx(@0x0); @@ -236,9 +246,12 @@ module escrow::shared { let c: Coin = ts.take_from_address_by_id(BOB, i1); ts::return_to_address(BOB, c); }; + // docs::/#finish + //docs::#test-resume ts::end(ts); } + //docs::/#test #[test] #[expected_failure(abort_code = EMismatchedSenderRecipient)] diff --git a/examples/trading/frontend/src/components/escrows/Escrow.tsx b/examples/trading/frontend/src/components/escrows/Escrow.tsx index 79b10cf47e30d..ea9d1da71d029 100644 --- a/examples/trading/frontend/src/components/escrows/Escrow.tsx +++ b/examples/trading/frontend/src/components/escrows/Escrow.tsx @@ -1,6 +1,5 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 - import { useCurrentAccount, useSuiClientQuery } from "@mysten/dapp-kit"; import { SuiObjectDisplay } from "@/components/SuiObjectDisplay"; import { Button } from "@radix-ui/themes"; @@ -80,7 +79,7 @@ export function Escrow({ escrow }: { escrow: ApiEscrowObject }) { label={getLabel()} labelClasses={getLabelClasses()} > -
+
{

diff --git a/examples/trading/frontend/src/components/escrows/EscrowList.tsx b/examples/trading/frontend/src/components/escrows/EscrowList.tsx index 1c8996f4fb632..084a4d78786ee 100644 --- a/examples/trading/frontend/src/components/escrows/EscrowList.tsx +++ b/examples/trading/frontend/src/components/escrows/EscrowList.tsx @@ -47,13 +47,11 @@ export function EscrowList({ return (

{enableSearch && ( - - setEscrowId(e.target.value)} - /> - + setEscrowId(e.target.value)} + /> )} fetchNextPage()} diff --git a/examples/trading/frontend/src/components/locked/ApiLockedList.tsx b/examples/trading/frontend/src/components/locked/ApiLockedList.tsx index cc81d15f6ee58..84cb60c11b4fc 100644 --- a/examples/trading/frontend/src/components/locked/ApiLockedList.tsx +++ b/examples/trading/frontend/src/components/locked/ApiLockedList.tsx @@ -40,6 +40,9 @@ export function LockedList({ initialPageParam: null, queryKey: [QueryKey.Locked, params, lockedId], queryFn: async ({ pageParam }) => { + /* + * Fetch the locked objects from the API. + */ const data = await ( await fetch( CONSTANTS.apiEndpoint + @@ -52,6 +55,10 @@ export function LockedList({ ) ).json(); + /* + * Use the objectIds from the API to fetch the on-chain state. This is done to ensure that + * the ownership of each object is up-to-date. + */ const objects = await suiClient.multiGetObjects({ ids: data.data.map((x: ApiLockedObject) => x.objectId), options: { @@ -70,6 +77,9 @@ export function LockedList({ enabled: !lockedId, }); + /** + * Returns all `Locked` objects or the one that matches the search query if it exists. + */ const suiObjects = () => { if (lockedId) { if ( @@ -94,13 +104,12 @@ export function LockedList({ return ( <> {enableSearch && ( - - setLockedId(e.target.value)} - /> - + setLockedId(e.target.value)} + > )} fetchNextPage()} diff --git a/examples/trading/frontend/src/components/locked/LockOwnedObjects.tsx b/examples/trading/frontend/src/components/locked/LockOwnedObjects.tsx index 044856947412d..860fed9063fe3 100644 --- a/examples/trading/frontend/src/components/locked/LockOwnedObjects.tsx +++ b/examples/trading/frontend/src/components/locked/LockOwnedObjects.tsx @@ -1,6 +1,5 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 - import { useCurrentAccount, useSuiClientInfiniteQuery } from "@mysten/dapp-kit"; import { SuiObjectDisplay } from "@/components/SuiObjectDisplay"; import { Button } from "@radix-ui/themes"; @@ -48,7 +47,7 @@ export function LockOwnedObjects() { > {data?.map((obj) => ( -
+

Lock the item so it can be used for escrows.

diff --git a/examples/trading/frontend/src/components/locked/partials/Locked.tsx b/examples/trading/frontend/src/components/locked/partials/Locked.tsx index c2c1d8d197b9d..7d2de9fd91110 100644 --- a/examples/trading/frontend/src/components/locked/partials/Locked.tsx +++ b/examples/trading/frontend/src/components/locked/partials/Locked.tsx @@ -78,7 +78,7 @@ export function Locked({ label={getLabel()} labelClasses={getLabelClasses()} > -
+
{

diff --git a/examples/trading/frontend/src/hooks/useTransactionExecution.ts b/examples/trading/frontend/src/hooks/useTransactionExecution.ts index a080a4ce7e626..dde36a8939b32 100644 --- a/examples/trading/frontend/src/hooks/useTransactionExecution.ts +++ b/examples/trading/frontend/src/hooks/useTransactionExecution.ts @@ -20,7 +20,7 @@ export function useTransactionExecution() { ): Promise => { try { const signature = await signTransactionBlock({ - transactionBlock: txb, + transaction: txb, }); const res = await client.executeTransactionBlock({ diff --git a/examples/trading/frontend/src/mutations/escrow.ts b/examples/trading/frontend/src/mutations/escrow.ts index dc56c21712855..8e3cc3bbb2861 100644 --- a/examples/trading/frontend/src/mutations/escrow.ts +++ b/examples/trading/frontend/src/mutations/escrow.ts @@ -1,6 +1,5 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 - import { CONSTANTS, QueryKey } from "@/constants"; import { useTransactionExecution } from "@/hooks/useTransactionExecution"; import { ApiEscrowObject, ApiLockedObject } from "@/types/types"; @@ -10,65 +9,36 @@ import { Transaction } from "@mysten/sui/transactions"; import { useMutation, useQueryClient } from "@tanstack/react-query"; /** - * Builds and executes the PTB to accept an escrow. + * Builds and executes the PTB to create an escrow. */ -export function useAcceptEscrowMutation() { +export function useCreateEscrowMutation() { const currentAccount = useCurrentAccount(); - const client = useSuiClient(); const executeTransaction = useTransactionExecution(); - const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ - escrow, + object, locked, }: { - escrow: ApiEscrowObject; + object: SuiObjectData; locked: ApiLockedObject; }) => { if (!currentAccount?.address) throw new Error("You need to connect your wallet!"); - const txb = new Transaction(); - - const escrowObject = await client.multiGetObjects({ - ids: [escrow.itemId, locked.itemId], - options: { - showType: true, - }, - }); - - const escrowType = escrowObject.find( - (x) => x.data?.objectId === escrow.itemId, - )?.data?.type; - - const lockedType = escrowObject.find( - (x) => x.data?.objectId === locked.itemId, - )?.data?.type; - if (!escrowType || !lockedType) { - throw new Error("Failed to fetch types."); - } - - const item = txb.moveCall({ - target: `${CONSTANTS.escrowContract.packageId}::shared::swap`, + const txb = new Transaction(); + txb.moveCall({ + target: `${CONSTANTS.escrowContract.packageId}::shared::create`, arguments: [ - txb.object(escrow.objectId), - txb.object(escrow.keyId), - txb.object(locked.objectId), + txb.object(object.objectId!), + txb.pure.id(locked.keyId), + txb.pure.address(locked.creator!), ], - typeArguments: [escrowType, lockedType], + typeArguments: [object.type!], }); - txb.transferObjects([item], txb.pure.address(currentAccount.address)); - return executeTransaction(txb); }, - - onSuccess: () => { - setTimeout(() => { - queryClient.invalidateQueries({ queryKey: [QueryKey.Escrow] }); - }, 1_000); - }, }); } @@ -112,35 +82,64 @@ export function useCancelEscrowMutation() { } /** - * Builds and executes the PTB to create an escrow. + * Builds and executes the PTB to accept an escrow. */ -export function useCreateEscrowMutation() { +export function useAcceptEscrowMutation() { const currentAccount = useCurrentAccount(); + const client = useSuiClient(); const executeTransaction = useTransactionExecution(); + const queryClient = useQueryClient(); return useMutation({ mutationFn: async ({ - object, + escrow, locked, }: { - object: SuiObjectData; + escrow: ApiEscrowObject; locked: ApiLockedObject; }) => { if (!currentAccount?.address) throw new Error("You need to connect your wallet!"); - const txb = new Transaction(); - txb.moveCall({ - target: `${CONSTANTS.escrowContract.packageId}::shared::create`, + + const escrowObject = await client.multiGetObjects({ + ids: [escrow.itemId, locked.itemId], + options: { + showType: true, + }, + }); + + const escrowType = escrowObject.find( + (x) => x.data?.objectId === escrow.itemId, + )?.data?.type; + + const lockedType = escrowObject.find( + (x) => x.data?.objectId === locked.itemId, + )?.data?.type; + + if (!escrowType || !lockedType) { + throw new Error("Failed to fetch types."); + } + + const item = txb.moveCall({ + target: `${CONSTANTS.escrowContract.packageId}::shared::swap`, arguments: [ - txb.object(object.objectId!), - txb.pure.id(locked.keyId), - txb.pure.address(locked.creator!), + txb.object(escrow.objectId), + txb.object(escrow.keyId), + txb.object(locked.objectId), ], - typeArguments: [object.type!], + typeArguments: [escrowType, lockedType], }); + txb.transferObjects([item], txb.pure.address(currentAccount.address)); + return executeTransaction(txb); }, + + onSuccess: () => { + setTimeout(() => { + queryClient.invalidateQueries({ queryKey: [QueryKey.Escrow] }); + }, 1_000); + }, }); } diff --git a/examples/trading/frontend/src/mutations/locked.ts b/examples/trading/frontend/src/mutations/locked.ts index d0ea805ee476d..55c8cc264b1b6 100644 --- a/examples/trading/frontend/src/mutations/locked.ts +++ b/examples/trading/frontend/src/mutations/locked.ts @@ -9,6 +9,7 @@ import { Transaction } from "@mysten/sui/transactions"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import toast from "react-hot-toast"; +//docs::#mutationlock /** * Builds and executes the PTB to lock an object. */ @@ -34,7 +35,9 @@ export function useLockObjectMutation() { }, }); } +//docs::/#mutationlock +//docs::#mutationunlock /** * Builds and executes the PTB to unlock an object. */ @@ -97,3 +100,4 @@ export function useUnlockMutation() { }, }); } +//docs::/#mutationunlock diff --git a/examples/trading/frontend/src/networkConfig.ts b/examples/trading/frontend/src/networkConfig.ts new file mode 100644 index 0000000000000..f81095781caa2 --- /dev/null +++ b/examples/trading/frontend/src/networkConfig.ts @@ -0,0 +1,19 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 +import { getFullnodeUrl } from "@mysten/sui/client"; +import { createNetworkConfig } from "@mysten/dapp-kit"; + +const { networkConfig, useNetworkVariable, useNetworkVariables } = + createNetworkConfig({ + devnet: { + url: getFullnodeUrl("devnet"), + }, + testnet: { + url: getFullnodeUrl("testnet"), + }, + mainnet: { + url: getFullnodeUrl("mainnet"), + }, + }); + +export { useNetworkVariable, useNetworkVariables, networkConfig }; diff --git a/examples/trading/frontend/src/routes/EscrowDashboard.tsx b/examples/trading/frontend/src/routes/EscrowDashboard.tsx index 2633fb94a5d6b..2d66f0ba3dd0f 100644 --- a/examples/trading/frontend/src/routes/EscrowDashboard.tsx +++ b/examples/trading/frontend/src/routes/EscrowDashboard.tsx @@ -1,6 +1,5 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 - import { useState } from "react"; import { Tabs, Tooltip } from "@radix-ui/themes"; import { LockedList } from "../components/locked/ApiLockedList"; diff --git a/examples/trading/frontend/src/routes/LockedDashboard.tsx b/examples/trading/frontend/src/routes/LockedDashboard.tsx index 30122ecd43587..ac37310dfe9cd 100644 --- a/examples/trading/frontend/src/routes/LockedDashboard.tsx +++ b/examples/trading/frontend/src/routes/LockedDashboard.tsx @@ -3,7 +3,7 @@ import { useState } from "react"; import { Tabs } from "@radix-ui/themes"; -import { LockOwnedObjects } from "../components/locked/LockOwnedObjects"; +import { LockOwnedObjects } from "@/components/locked/LockOwnedObjects"; import { OwnedLockedList } from "@/components/locked/OwnedLockedList"; export function LockedDashboard() { diff --git a/examples/usdc-transfer-app/App.js b/examples/usdc-transfer-app/App.js new file mode 100644 index 0000000000000..1cace7e744471 --- /dev/null +++ b/examples/usdc-transfer-app/App.js @@ -0,0 +1,163 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// docs::#setup +import React, { useState, useEffect } from "react"; +import { + createNetworkConfig, + SuiClientProvider, + useSuiClient, + ConnectButton, + useCurrentAccount, + useSignAndExecuteTransaction, + WalletProvider, +} from "@mysten/dapp-kit"; +import { Transaction } from "@mysten/sui/transactions"; +import { getFullnodeUrl } from "@mysten/sui/client"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { useState } from "react"; +import "@mysten/dapp-kit/dist/index.css"; +// docs::/#setup + +const { networkConfig } = createNetworkConfig({ + testnet: { + url: getFullnodeUrl("testnet"), + }, + mainnet: { + url: getFullnodeUrl("mainnet"), + }, +}); + +// Create a new QueryClient for managing and caching asynchronous queries +const queryClient = new QueryClient(); + +// Define the USDC token type on Sui Testnet +// This is the unique identifier for the USDC token on Sui +const USDC_TYPE = '0xa1ec7fc00a6f40db9693ad1415d0c193ad3906494428cf252621037bd7117e29::usdc::USDC'; + +function HomeContent() { + // docs::#state + // Use the wallet kit to get the current account and transaction signing function + const currentAccount = useCurrentAccount(); + const { mutate: signAndExecuteTransaction } = useSignAndExecuteTransaction(); + // Get the Sui client for interacting with the Sui network + const suiClient = useSuiClient(); + const [open, setOpen] = useState(false); + const [connected, setConnected] = useState(false); + const [amount, setAmount] = useState(""); + const [recipientAddress, setRecipientAddress] = useState(""); + const [txStatus, setTxStatus] = useState(""); + // docs::/#state + + // docs::#useeffect + useEffect(() => { + setConnected(!!currentAccount); + }, [currentAccount]); + // docs::/#useeffect + + const handleSendTokens = async () => { + if (!currentAccount || !amount || !recipientAddress) { + setTxStatus("Please connect wallet and fill in all fields"); + return; + } + try { + // Fetch USDC coins owned by the current account + // This uses the SuiClient to get coins of the specified type owned by the current address + const { data: coins } = await suiClient.getCoins({ + owner: currentAccount.address, + coinType: USDC_TYPE, + }); + if (coins.length === 0) { + setTxStatus("No USDC coins found in your wallet"); + return; + } + // Create a new transaction block + // Transaction is used to construct and execute transactions on Sui + const tx = new Transaction(); + // Convert amount to smallest unit (6 decimals) + const amountInSmallestUnit = BigInt(parseFloat(amount) * 1_000_000); + // Split the coin and get a new coin with the specified amount + // This creates a new coin object with the desired amount to be transferred + const [coin] = tx.splitCoins(coins[0].coinObjectId, [ + tx.pure.u64(amountInSmallestUnit), + ]); + // Transfer the split coin to the recipient + // This adds a transfer operation to the transaction block + tx.transferObjects([coin], tx.pure.address(recipientAddress)); + // Sign and execute the transaction block + // This sends the transaction to the network and waits for it to be executed + const result = await signAndExecuteTransaction( + { + transaction: tx, + }, + { + onSuccess: (result) => { + console.log("Transaction result:", result); + setTxStatus(`Transaction successful. Digest: ${result.digest}`); + }, + } + ); + } catch (error) { + console.error("Error sending tokens:", error); + setTxStatus( + `Error: ${error instanceof Error ? error.message : "Unknown error"}` + ); + } + }; + + // docs::#ui + return ( +

+
+

Sui USDC Sender (Testnet)

+ + {connected && currentAccount && ( +

Connected: {currentAccount.address}

+ )} +
+ setAmount(e.target.value)} + className="input" + /> + setRecipientAddress(e.target.value)} + className="input" + /> + +
+ {txStatus &&

{txStatus}

} +
+
+ ); + // docs::/#ui +} + +function App() { + return ( + + + + + + + + ); +} + +export default App; diff --git a/examples/usdc-transfer-app/favicon.ico b/examples/usdc-transfer-app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..d664e37e793e5a30d461a40dc4e37f332df47feb GIT binary patch literal 918 zcmV;H18Mw;P)7xk0qF^V6NKN_CE)>U!U^C8_y~EgrHf)) z{YF@poH&Z?*+{ZSqfay6j7CsHiGInoG^K|ipx)%0h^e8psDbFir6e^**Rn$mr4fK6 zp)nuMa~_jFbWIJVHh}(>Jj4*reGWG-pDCppJM+}6dg94DCqc6~-8sY^&kxrK4d~*L?C@{A_)OezQ1khuB@|2YT znBki7yvB{=c4ET7aoK5z`Y+kSZM%xVTU5UEs zD|wO934j@oSeJ;EwP{kN%b%902s# zI$#@Qoh;L@^gvFcF*Oj7@)W~Qy~ZWRE>czkP^2SzCs9tW0^E2t!{McoG&Q2bq$FPe zD(*Jp-%MOtk`@ifLp~%lv`N`NlOrzx*fh{{kgCVzvTnmW_I*-J?Gv=OYF`O}B9M7x zZeS$lIr9G4@0|mp4BCE7gtA34m{w&t{YD#cIdwwn5}Fa(RQfU==4f z2ehEn%xH<^AbJJE$#`SgbDSA_ZU7e2HF5}(3n5vjIdAZDymcve=kBgLpppf`D#pEsK<6dw07keOCj> zJDZ+e`(gM=RlFL_W^p*|usz5BY5^a*;DhN4NQETEFh!hiK-d^%R)K_G7nU~geNfWp z!xQzQKGz12gIG%gfDrD&RB0G)&!6+ysYf0H_m5z3*9QpSnu*L@s_LFA^! + + + + + Sui USDC Sender + + + + +
+ + + \ No newline at end of file diff --git a/examples/usdc-transfer-app/index.js b/examples/usdc-transfer-app/index.js new file mode 100644 index 0000000000000..1bd33d2398271 --- /dev/null +++ b/examples/usdc-transfer-app/index.js @@ -0,0 +1,10 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import React from 'react'; +import ReactDOM from 'react-dom'; +import { createRoot } from 'react-dom/client'; +import App from './App.js'; + +const root = createRoot(document.getElementById('root')); +root.render(); \ No newline at end of file diff --git a/examples/usdc-transfer-app/lib/App-stub.js b/examples/usdc-transfer-app/lib/App-stub.js new file mode 100644 index 0000000000000..54bc2d27e499a --- /dev/null +++ b/examples/usdc-transfer-app/lib/App-stub.js @@ -0,0 +1,17 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// This file is used only for instruction. + +import React from 'react'; + +function App() { + return ( +
+

Hello, World!

+

This is a minimal React app.

+
+ ); +} + +export default App; \ No newline at end of file diff --git a/examples/usdc-transfer-app/package.json b/examples/usdc-transfer-app/package.json new file mode 100644 index 0000000000000..eead21e3af1f0 --- /dev/null +++ b/examples/usdc-transfer-app/package.json @@ -0,0 +1,23 @@ +{ + "name": "usdc-transfer-app", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "start": "parcel index.html" + }, + "keywords": [], + "author": "", + "license": "Apache 2.0", + "dependencies": { + "@mysten/dapp-kit": "^0.14.25", + "@mysten/sui": "^1.12.0", + "@tanstack/react-query": "^5.50.1", + "parcel": "^2.12.0", + "react": "^18.3.1", + "react-dom": "^18.3.1" + }, + "devDependencies": { + "process": "^0.11.10" + } +} diff --git a/external-crates/move/Cargo.lock b/external-crates/move/Cargo.lock index 8ce40a2c189cd..cdb0abbe485ff 100644 --- a/external-crates/move/Cargo.lock +++ b/external-crates/move/Cargo.lock @@ -1611,6 +1611,7 @@ dependencies = [ "lsp-types", "move-command-line-common", "move-compiler", + "move-core-types", "move-ir-types", "move-package", "move-symbol-pool", @@ -1657,6 +1658,7 @@ dependencies = [ "move-ir-types", "move-symbol-pool", "serde", + "serde_json", ] [[package]] @@ -2224,6 +2226,25 @@ dependencies = [ "serde_json", ] +[[package]] +name = "move-trace-format" +version = "0.0.1" +dependencies = [ + "anyhow", + "arbitrary", + "enum-compat-util", + "getrandom 0.2.15", + "move-binary-format", + "move-core-types", + "move-proc-macros", + "proptest", + "proptest-derive", + "ref-cast", + "serde", + "serde_json", + "variant_count", +] + [[package]] name = "move-transactional-test-runner" version = "0.1.0" @@ -2279,6 +2300,7 @@ dependencies = [ "move-stdlib", "move-stdlib-natives", "move-symbol-pool", + "move-trace-format", "move-vm-profiler", "move-vm-runtime", "move-vm-test-utils", @@ -2344,6 +2366,7 @@ dependencies = [ "move-compiler", "move-core-types", "move-ir-compiler", + "move-trace-format", "move-vm-config", "move-vm-profiler", "move-vm-types", diff --git a/external-crates/move/Cargo.toml b/external-crates/move/Cargo.toml index 3620dbff12092..3231118dec233 100644 --- a/external-crates/move/Cargo.toml +++ b/external-crates/move/Cargo.toml @@ -140,6 +140,7 @@ module-generation = { path = "crates/module-generation" } move-abstract-interpreter = { path = "crates/move-abstract-interpreter" } move-abstract-stack = { path = "crates/move-abstract-stack" } move-binary-format = { path = "crates/move-binary-format" } +move-trace-format = { path = "crates/move-trace-format" } move-borrow-graph = { path = "crates/move-borrow-graph" } move-bytecode-source-map = { path = "crates/move-bytecode-source-map" } move-bytecode-utils = { path = "crates/move-bytecode-utils" } diff --git a/external-crates/move/crates/module-generation/src/generator.rs b/external-crates/move/crates/module-generation/src/generator.rs index 8f2dcbef73ddf..ebe0e131993e8 100644 --- a/external-crates/move/crates/module-generation/src/generator.rs +++ b/external-crates/move/crates/module-generation/src/generator.rs @@ -113,13 +113,12 @@ impl<'a> ModuleGenerator<'a> { if !structs.is_empty() { end += 1; }; - - match self.index(end) { - 0 => Type::Address, - 1 => Type::U8, - 2 => Type::U64, - 3 => Type::U128, - 4 => Type::Bool, + let type_ = match self.index(end) { + 0 => Type_::Address, + 1 => Type_::U8, + 2 => Type_::U64, + 3 => Type_::U128, + 4 => Type_::Bool, 5 if !structs.is_empty() => { let index = self.index(structs.len()); let struct_def = structs[index].value.clone(); @@ -133,17 +132,18 @@ impl<'a> ModuleGenerator<'a> { let module_name = ModuleName::module_self(); QualifiedDatatypeIdent::new(module_name, struct_name) }; - Type::Datatype(struct_ident, ty_instants) + Type_::Datatype(struct_ident, ty_instants) } - 6 => Type::U16, - 7 => Type::U32, - 8 => Type::U256, + 6 => Type_::U16, + 7 => Type_::U32, + 8 => Type_::U256, _ => { let index = self.index(ty_param_context.len()); let ty_var = ty_param_context[index].value.clone(); - Type::TypeParameter(ty_var) + Type_::TypeParameter(ty_var) } - } + }; + Spanned::unsafe_no_loc(type_) } fn typ(&mut self, ty_param_context: &[(TypeVar, BTreeSet)]) -> Type { @@ -158,7 +158,7 @@ impl<'a> ModuleGenerator<'a> { // if typ.is_nominal_resource { .... } if self.options.references_allowed && self.gen.gen_bool(0.25) { let is_mutable = self.gen.gen_bool(0.25); - Type::Reference(is_mutable, Box::new(typ)) + Spanned::unsafe_no_loc(Type_::Reference(is_mutable, Box::new(typ))) } else { typ } @@ -209,7 +209,7 @@ impl<'a> ModuleGenerator<'a> { .iter() .map(|(ty_var_, _)| { let param_name = Spanned::unsafe_no_loc(Var_(self.identifier().into())); - let ty = Type::TypeParameter(ty_var_.value.clone()); + let ty = Spanned::unsafe_no_loc(Type_::TypeParameter(ty_var_.value.clone())); (param_name, ty) }) .collect(); diff --git a/external-crates/move/crates/move-analyzer/Cargo.toml b/external-crates/move/crates/move-analyzer/Cargo.toml index b1be5edc5fae5..0f7977bfca6f5 100644 --- a/external-crates/move/crates/move-analyzer/Cargo.toml +++ b/external-crates/move/crates/move-analyzer/Cargo.toml @@ -23,6 +23,7 @@ lsp-types.workspace = true move-command-line-common.workspace = true move-compiler.workspace = true move-ir-types.workspace = true +move-core-types.workspace = true move-package.workspace = true move-symbol-pool.workspace = true once_cell.workspace = true diff --git a/external-crates/move/crates/move-analyzer/editors/code/language-configuration.json b/external-crates/move/crates/move-analyzer/editors/code/language-configuration.json index 5b20cd79bc16d..ba5e2b35b83c5 100644 --- a/external-crates/move/crates/move-analyzer/editors/code/language-configuration.json +++ b/external-crates/move/crates/move-analyzer/editors/code/language-configuration.json @@ -27,5 +27,5 @@ ["(", ")"], ["<", ">"], ["\"", "\""] - ], + ] } diff --git a/external-crates/move/crates/move-analyzer/editors/code/package-lock.json b/external-crates/move/crates/move-analyzer/editors/code/package-lock.json index ebb58e03531da..70e287b7a59e4 100644 --- a/external-crates/move/crates/move-analyzer/editors/code/package-lock.json +++ b/external-crates/move/crates/move-analyzer/editors/code/package-lock.json @@ -1,12 +1,12 @@ { "name": "move", - "version": "1.0.10", + "version": "1.0.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "move", - "version": "1.0.10", + "version": "1.0.13", "license": "Apache-2.0", "dependencies": { "command-exists": "^1.2.9", @@ -701,12 +701,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -1541,10 +1542,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2098,6 +2100,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -2357,12 +2360,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -3391,6 +3395,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, diff --git a/external-crates/move/crates/move-analyzer/editors/code/package.json b/external-crates/move/crates/move-analyzer/editors/code/package.json index 97bb29c43284d..b97675a005c68 100644 --- a/external-crates/move/crates/move-analyzer/editors/code/package.json +++ b/external-crates/move/crates/move-analyzer/editors/code/package.json @@ -5,7 +5,7 @@ "publisher": "mysten", "icon": "images/move.png", "license": "Apache-2.0", - "version": "1.0.12", + "version": "1.0.13", "preview": true, "repository": { "url": "https://github.com/MystenLabs/sui.git", @@ -124,13 +124,13 @@ "menus": { "commandPalette": [ { - "command": "sui.serverVersion" + "command": "move.serverVersion" }, { - "command": "sui.build" + "command": "move.build" }, { - "command": "sui.test" + "command": "move.test" } ] } diff --git a/external-crates/move/crates/move-analyzer/editors/code/tests/runTests.ts b/external-crates/move/crates/move-analyzer/editors/code/tests/runTests.ts index a8cd46f28528f..750421cc9844b 100644 --- a/external-crates/move/crates/move-analyzer/editors/code/tests/runTests.ts +++ b/external-crates/move/crates/move-analyzer/editors/code/tests/runTests.ts @@ -43,6 +43,10 @@ async function runVSCodeTest(vscodeVersion: string): Promise { } // Install vscode and depends extension + // + // TODO: currently, running `npm test` fails with an ENOENT error when spawning Electron; + // make sure that the path is correct and that `npm test` runs correctly to completion + const vscodeExecutablePath = await downloadAndUnzipVSCode(vscodeVersion); const [cli, ...args] = resolveCliArgsFromVSCodeExecutablePath(vscodeExecutablePath); const newCli = cli ?? 'code'; diff --git a/external-crates/move/crates/move-analyzer/src/analysis/typing_analysis.rs b/external-crates/move/crates/move-analyzer/src/analysis/typing_analysis.rs index 3a9fca207f99e..e22321ddf20ac 100644 --- a/external-crates/move/crates/move-analyzer/src/analysis/typing_analysis.rs +++ b/external-crates/move/crates/move-analyzer/src/analysis/typing_analysis.rs @@ -138,18 +138,18 @@ impl TypingAnalysisContext<'_> { } /// Add use of a const identifier - fn add_const_use_def(&mut self, module_ident: &E::ModuleIdent, name: &ConstantName) { + fn add_const_use_def(&mut self, module_ident: &E::ModuleIdent_, name: &ConstantName) { if self.traverse_only { return; } let use_pos = name.loc(); let use_name = name.value(); - let mod_ident_str = expansion_mod_ident_to_map_key(&module_ident.value); + let mod_ident_str = expansion_mod_ident_to_map_key(module_ident); let Some(mod_defs) = self.mod_outer_defs.get(&mod_ident_str) else { return; }; // insert use of the const's module - let mod_name = module_ident.value.module; + let mod_name = module_ident.module; if let Some(mod_name_start) = self.file_start_position_opt(&mod_name.loc()) { // a module will not be present if a constant belongs to an implicit module self.use_defs.insert( @@ -388,7 +388,14 @@ impl TypingAnalysisContext<'_> { sp!(_, N::TypeName_::ModuleType(sp!(_, mod_ident), struct_name)), _, ) => { - self.add_field_use_def(mod_ident, &struct_name.value(), None, use_name, use_pos); + self.add_field_use_def( + mod_ident, + &struct_name.value(), + None, + use_name, + use_pos, + /* named_only */ false, + ); } _ => (), } @@ -441,7 +448,8 @@ impl TypingAnalysisContext<'_> { ); } - /// Add use of a variant field identifier + /// Add use of a variant field identifier. In some cases, such as packing (controlled by `named_only`), we only + /// want to add named fields. fn add_field_use_def( &mut self, module_ident: &E::ModuleIdent_, @@ -449,6 +457,7 @@ impl TypingAnalysisContext<'_> { variant_name_opt: Option<&Symbol>, use_name: &Symbol, use_loc: &Loc, + named_only: bool, ) { if self.traverse_only { return; @@ -463,7 +472,7 @@ impl TypingAnalysisContext<'_> { return; }; - let field_defs = if let Some(variant_name) = variant_name_opt { + let (field_defs, positional) = if let Some(variant_name) = variant_name_opt { // get the enum let Some(def) = mod_defs.enums.get(datatype_name) else { return; @@ -474,10 +483,10 @@ impl TypingAnalysisContext<'_> { return; }; // get variant's fields - let Some((_, field_defs, _)) = variants_info.get(variant_name) else { + let Some((_, field_defs, positional)) = variants_info.get(variant_name) else { return; }; - field_defs + (field_defs, positional) } else { // get the struct let Some(def) = mod_defs.structs.get(datatype_name) else { @@ -486,14 +495,18 @@ impl TypingAnalysisContext<'_> { // get variant's fields let MemberDefInfo::Struct { field_defs, - positional: _, + positional, } = &def.info else { return; }; - field_defs + (field_defs, positional) }; + if *positional && named_only { + return; + } + for fdef in field_defs { if fdef.name == *use_name { let field_info = self.def_info.get(&fdef.loc).unwrap(); @@ -568,6 +581,7 @@ impl TypingAnalysisContext<'_> { Some(&vname.value()), fname, &fpos, + /* named_only */ false, ); } self.process_match_patterm(pat); @@ -579,12 +593,19 @@ impl TypingAnalysisContext<'_> { tyargs.iter().for_each(|t| self.visit_type(None, t)); for (fpos, fname, (_, (_, pat))) in fields.iter() { if self.compiler_info.ellipsis_binders.get(&fpos).is_none() { - self.add_field_use_def(&mident.value, &name.value(), None, fname, &fpos); + self.add_field_use_def( + &mident.value, + &name.value(), + None, + fname, + &fpos, + /* named_only */ false, + ); } self.process_match_patterm(pat); } } - UA::Constant(mod_ident, name) => self.add_const_use_def(mod_ident, name), + UA::Constant(mod_ident, name) => self.add_const_use_def(&mod_ident.value, name), UA::Or(pat1, pat2) => { self.process_match_patterm(pat1); self.process_match_patterm(pat2); @@ -932,7 +953,14 @@ impl<'a> TypingVisitorContext for TypingAnalysisContext<'a> { self.add_datatype_use_def(mident, name); tyargs.iter().for_each(|t| self.visit_type(None, t)); for (fpos, fname, (_, (_, lvalue))) in fields { - self.add_field_use_def(&mident.value, &name.value(), None, fname, &fpos); + self.add_field_use_def( + &mident.value, + &name.value(), + None, + fname, + &fpos, + /* named_only */ false, + ); lvalue_queue.push(lvalue); } } @@ -948,6 +976,7 @@ impl<'a> TypingVisitorContext for TypingAnalysisContext<'a> { Some(&vname.value()), fname, &fpos, + /* named_only */ false, ); lvalue_queue.push(lvalue); } @@ -990,7 +1019,7 @@ impl<'a> TypingVisitorContext for TypingAnalysisContext<'a> { true } TE::Constant(mod_ident, name) => { - visitor.add_const_use_def(mod_ident, name); + visitor.add_const_use_def(&mod_ident.value, name); true } TE::ModuleCall(mod_call) => { @@ -1007,7 +1036,14 @@ impl<'a> TypingVisitorContext for TypingAnalysisContext<'a> { TE::Pack(mident, name, tyargs, fields) => { visitor.add_datatype_use_def(mident, name); for (fpos, fname, (_, (_, init_exp))) in fields.iter() { - visitor.add_field_use_def(&mident.value, &name.value(), None, fname, &fpos); + visitor.add_field_use_def( + &mident.value, + &name.value(), + None, + fname, + &fpos, + /* named_only */ true, + ); visitor.visit_exp(init_exp); } tyargs @@ -1022,6 +1058,7 @@ impl<'a> TypingVisitorContext for TypingAnalysisContext<'a> { } TE::PackVariant(mident, name, vname, tyargs, fields) => { visitor.add_datatype_use_def(mident, name); + visitor.add_variant_use_def(mident, name, vname); for (fpos, fname, (_, (_, init_exp))) in fields.iter() { visitor.add_field_use_def( &mident.value, @@ -1029,6 +1066,7 @@ impl<'a> TypingVisitorContext for TypingAnalysisContext<'a> { Some(&vname.value()), fname, &fpos, + /* named_only */ true, ); visitor.visit_exp(init_exp); } @@ -1047,6 +1085,23 @@ impl<'a> TypingVisitorContext for TypingAnalysisContext<'a> { v.iter().for_each(|arm| visitor.process_match_arm(arm)); true } + TE::ErrorConstant { + line_number_loc: _, + error_constant, + } => { + // assume that constant is defined in the same module where it's used + // TODO: if above ever changes, we need to update this (presumably + // `ErrorConstant` will carry module ident at this point) + if let Some(name) = error_constant { + if let Some(mod_def) = visitor + .mod_outer_defs + .get(visitor.current_mod_ident_str.as_ref().unwrap()) + { + visitor.add_const_use_def(&mod_def.ident.clone(), name); + } + }; + true + } TE::Unit { .. } | TE::Builtin(_, _) | TE::Vector(_, _, _, _) @@ -1069,7 +1124,6 @@ impl<'a> TypingVisitorContext for TypingAnalysisContext<'a> { | TE::TempBorrow(_, _) | TE::Cast(_, _) | TE::Annotate(_, _) - | TE::ErrorConstant { .. } | TE::UnresolvedError => false, } } diff --git a/external-crates/move/crates/move-analyzer/src/completions/utils.rs b/external-crates/move/crates/move-analyzer/src/completions/utils.rs index 93ac2d7f08686..926ef410e58b1 100644 --- a/external-crates/move/crates/move-analyzer/src/completions/utils.rs +++ b/external-crates/move/crates/move-analyzer/src/completions/utils.rs @@ -56,8 +56,8 @@ pub fn call_completion_item( ) -> CompletionItem { let sig_string = format!( "fun {}({}){}", - type_args_to_ide_string(type_args, /* verbose */ false), - type_list_to_ide_string(arg_types, /* verbose */ false), + type_args_to_ide_string(type_args, /* separate_lines */ false, /* verbose */ false), + type_list_to_ide_string(arg_types, /* separate_lines */ false, /* verbose */ false), ret_type_to_ide_str(ret_type, /* verbose */ false) ); // if it's a method call we omit the first argument which is guaranteed to be there as this is a @@ -83,8 +83,8 @@ pub fn call_completion_item( let macro_suffix = if is_macro { "!" } else { "" }; let label_details = Some(CompletionItemLabelDetails { detail: Some(format!( - " ({}::{})", - mod_ident_to_ide_string(mod_ident), + " ({}{})", + mod_ident_to_ide_string(mod_ident, None, true), function_name )), description: Some(sig_string), diff --git a/external-crates/move/crates/move-analyzer/src/diagnostics.rs b/external-crates/move/crates/move-analyzer/src/diagnostics.rs index c58e7464775da..450330f195b6a 100644 --- a/external-crates/move/crates/move-analyzer/src/diagnostics.rs +++ b/external-crates/move/crates/move-analyzer/src/diagnostics.rs @@ -22,12 +22,12 @@ pub fn lsp_diagnostics( files: &MappedFiles, ) -> BTreeMap> { let mut lsp_diagnostics = BTreeMap::new(); - for (s, _, (loc, msg), labels, _) in diagnostics { + for (s, _, (loc, msg), labels, notes) in diagnostics { let fpath = files.file_path(&loc.file_hash()); if let Some(start) = loc_start_to_lsp_position_opt(files, loc) { if let Some(end) = loc_end_to_lsp_position_opt(files, loc) { let range = Range::new(start, end); - let related_info_opt = if labels.is_empty() { + let related_info_opt = if labels.is_empty() && notes.is_empty() { None } else { Some( @@ -46,6 +46,16 @@ pub fn lsp_diagnostics( message: lmsg.to_string(), }) }) + .chain(notes.iter().map(|note| { + // for notes use the same location as for the main message + let fpath = files.file_path(&loc.file_hash()); + let fpos = + Location::new(Url::from_file_path(fpath).unwrap(), range); + DiagnosticRelatedInformation { + location: fpos, + message: format!("Note: {note}"), + } + })) .collect(), ) }; diff --git a/external-crates/move/crates/move-analyzer/src/symbols.rs b/external-crates/move/crates/move-analyzer/src/symbols.rs index bcb2eb2df7d5c..4bec62fce555b 100644 --- a/external-crates/move/crates/move-analyzer/src/symbols.rs +++ b/external-crates/move/crates/move-analyzer/src/symbols.rs @@ -73,7 +73,7 @@ use lsp_types::{ use sha2::{Digest, Sha256}; use std::{ cmp, - collections::{BTreeMap, BTreeSet, HashMap}, + collections::{BTreeMap, BTreeSet}, fmt, path::{Path, PathBuf}, sync::{Arc, Condvar, Mutex}, @@ -90,14 +90,19 @@ use move_command_line_common::files::FileHash; use move_compiler::{ command_line::compiler::{construct_pre_compiled_lib, FullyCompiledProgram}, editions::{Edition, FeatureGate, Flavor}, - expansion::ast::{self as E, AbilitySet, ModuleIdent, ModuleIdent_, Value, Value_, Visibility}, + expansion::{ + ast::{self as E, AbilitySet, ModuleIdent, ModuleIdent_, Value, Value_, Visibility}, + name_validation::{IMPLICIT_STD_MEMBERS, IMPLICIT_STD_MODULES}, + }, linters::LintLevel, naming::ast::{DatatypeTypeParameter, StructFields, Type, TypeName_, Type_, VariantFields}, - parser::ast as P, + parser::{ + ast::{self as P}, + comments::CommentMap, + }, shared::{ - files::{FileId, MappedFiles}, - unique_map::UniqueMap, - Identifier, Name, NamedAddressMap, NamedAddressMaps, + files::MappedFiles, unique_map::UniqueMap, Identifier, Name, NamedAddressMap, + NamedAddressMaps, }, typing::{ ast::{ @@ -109,6 +114,7 @@ use move_compiler::{ unit_test::filter_test_members::UNIT_TEST_POISON_FUN_NAME, PASS_CFGIR, PASS_PARSER, PASS_TYPING, }; +use move_core_types::account_address::AccountAddress; use move_ir_types::location::*; use move_package::{ compilation::{build_plan::BuildPlan, compiled_package::ModuleFormat}, @@ -118,7 +124,7 @@ use move_package::{ use move_symbol_pool::Symbol; const MANIFEST_FILE_NAME: &str = "Move.toml"; - +const STD_LIB_PKG_ADDRESS: &str = "0x1"; type SourceFiles = BTreeMap; /// Information about the compiled package and data structures @@ -132,6 +138,7 @@ pub struct CompiledPkgInfo { mapped_files: MappedFiles, edition: Option, compiler_info: Option, + all_comments: CommentMap, } /// Data used during symbols computation @@ -656,20 +663,35 @@ impl fmt::Display for DefInfo { ret_type, _, ) => { - let type_args_str = type_args_to_ide_string(type_args, /* verbose */ true); + const SINGLE_LINE_TYPE_ARGS_NUM: usize = 2; + // The strategy for displaying function signature is as follows: + // - if there are more than SINGLE_LINE_TYPE_ARGS_NUM type args, + // they are displayed on separate lines + // - "regular" args are always displayed on separate lines, which + // which is motivated by the fact that datatypes are displayed + // in a fully-qualified form (i.e., with package and module name), + // and that makes the function name already long and (likely) + // the length of each individual type also long (modulo primitive + // types of course, but I think we can live with that) + let type_args_str = type_args_to_ide_string( + type_args, + /* separate_lines */ type_args.len() > SINGLE_LINE_TYPE_ARGS_NUM, + /* verbose */ true, + ); + let args_str = typed_id_list_to_ide_string( + arg_names, arg_types, '(', ')', /* separate_lines */ true, + /* verbose */ true, + ); let ret_type_str = ret_type_to_ide_str(ret_type, /* verbose */ true); write!( f, - "{}{}fun {}::{}{}({}){}", + "{}{}fun {}{}{}{}{}", visibility_to_ide_string(visibility), fun_type_to_ide_string(fun_type), - mod_ident_to_ide_string(mod_ident), + mod_ident_to_ide_string(mod_ident, None, true), name, type_args_str, - typed_id_list_to_ide_string( - arg_names, arg_types, /* separate_lines */ false, - /* verbose */ true - ), + args_str, ret_type_str, ) } @@ -689,9 +711,9 @@ impl fmt::Display for DefInfo { if field_names.is_empty() { write!( f, - "{}struct {}::{}{}{} {{}}", + "{}struct {}{}{}{} {{}}", visibility_to_ide_string(visibility), - mod_ident_to_ide_string(mod_ident), + mod_ident_to_ide_string(mod_ident, Some(name), true), name, type_args_str, abilities_str, @@ -699,15 +721,17 @@ impl fmt::Display for DefInfo { } else { write!( f, - "{}struct {}::{}{}{} {{\n{}\n}}", + "{}struct {}{}{}{} {}", visibility_to_ide_string(visibility), - mod_ident_to_ide_string(mod_ident), + mod_ident_to_ide_string(mod_ident, Some(name), true), name, type_args_str, abilities_str, typed_id_list_to_ide_string( field_names, field_types, + '{', + '}', /* separate_lines */ true, /* verbose */ true ), @@ -721,9 +745,9 @@ impl fmt::Display for DefInfo { if variants.is_empty() { write!( f, - "{}enum {}::{}{}{} {{}}", + "{}enum {}{}{}{} {{}}", visibility_to_ide_string(visibility), - mod_ident_to_ide_string(mod_ident), + mod_ident_to_ide_string(mod_ident, Some(name), true), name, type_args_str, abilities_str, @@ -731,9 +755,9 @@ impl fmt::Display for DefInfo { } else { write!( f, - "{}enum {}::{}{}{} {{\n{}\n}}", + "{}enum {}{}{}{} {{\n{}\n}}", visibility_to_ide_string(visibility), - mod_ident_to_ide_string(mod_ident), + mod_ident_to_ide_string(mod_ident, Some(name), true), name, type_args_str, abilities_str, @@ -745,30 +769,36 @@ impl fmt::Display for DefInfo { if field_types.is_empty() { write!( f, - "{}::{}::{}", - mod_ident_to_ide_string(mod_ident), + "{}{}::{}", + mod_ident_to_ide_string(mod_ident, Some(enum_name), true), enum_name, name ) } else if *positional { write!( f, - "{}::{}::{}({})", - mod_ident_to_ide_string(mod_ident), + "{}{}::{}({})", + mod_ident_to_ide_string(mod_ident, Some(enum_name), true), enum_name, name, - type_list_to_ide_string(field_types, /* verbose */ true) + type_list_to_ide_string( + field_types, + /* separate_lines */ false, + /* verbose */ true + ) ) } else { write!( f, - "{}::{}::{}{{{}}}", - mod_ident_to_ide_string(mod_ident), + "{}{}::{}{}", + mod_ident_to_ide_string(mod_ident, Some(enum_name), true), enum_name, name, typed_id_list_to_ide_string( field_names, field_types, + '{', + '}', /* separate_lines */ false, /* verbose */ true, ), @@ -778,8 +808,8 @@ impl fmt::Display for DefInfo { Self::Field(mod_ident, struct_name, name, t, _) => { write!( f, - "{}::{}\n{}: {}", - mod_ident_to_ide_string(mod_ident), + "{}{}\n{}: {}", + mod_ident_to_ide_string(mod_ident, Some(struct_name), true), struct_name, name, type_to_ide_string(t, /* verbose */ true) @@ -916,11 +946,17 @@ fn visibility_to_ide_string(visibility: &Visibility) -> String { visibility_str } -pub fn type_args_to_ide_string(type_args: &[Type], verbose: bool) -> String { +pub fn type_args_to_ide_string(type_args: &[Type], separate_lines: bool, verbose: bool) -> String { let mut type_args_str = "".to_string(); if !type_args.is_empty() { type_args_str.push('<'); - type_args_str.push_str(&type_list_to_ide_string(type_args, verbose)); + if separate_lines { + type_args_str.push('\n'); + } + type_args_str.push_str(&type_list_to_ide_string(type_args, separate_lines, verbose)); + if separate_lines { + type_args_str.push('\n'); + } type_args_str.push('>'); } type_args_str @@ -939,10 +975,12 @@ fn datatype_type_args_to_ide_string(type_args: &[(Type, bool)], verbose: bool) - fn typed_id_list_to_ide_string( names: &[Name], types: &[Type], + list_start: char, + list_end: char, separate_lines: bool, verbose: bool, ) -> String { - names + let list = names .iter() .zip(types.iter()) .map(|(n, t)| { @@ -953,7 +991,12 @@ fn typed_id_list_to_ide_string( } }) .collect::>() - .join(if separate_lines { ",\n" } else { ", " }) + .join(if separate_lines { ",\n" } else { ", " }); + if separate_lines && !list.is_empty() { + format!("{}\n{}\n{}", list_start, list, list_end) + } else { + format!("{}{}{}", list_start, list, list_end) + } } pub fn type_to_ide_string(sp!(_, t): &Type, verbose: bool) -> String { @@ -969,32 +1012,47 @@ pub fn type_to_ide_string(sp!(_, t): &Type, verbose: bool) -> String { } Type_::Apply(_, sp!(_, type_name), ss) => match type_name { TypeName_::Multiple(_) => { - format!("({})", type_list_to_ide_string(ss, verbose)) + format!( + "({})", + type_list_to_ide_string(ss, /* separate_lines */ false, verbose) + ) } TypeName_::Builtin(name) => { if ss.is_empty() { format!("{}", name) } else { - format!("{}<{}>", name, type_list_to_ide_string(ss, verbose)) + format!( + "{}<{}>", + name, + type_list_to_ide_string(ss, /* separate_lines */ false, verbose) + ) } } - TypeName_::ModuleType(sp!(_, module_ident), struct_name) => { + TypeName_::ModuleType(sp!(_, mod_ident), datatype_name) => { let type_args = if ss.is_empty() { "".to_string() } else { - format!("<{}>", type_list_to_ide_string(ss, verbose)) + format!( + "<{}>", + type_list_to_ide_string(ss, /* separate_lines */ false, verbose) + ) }; if verbose { - format!("{}::{}{}", module_ident, struct_name, type_args,) + format!( + "{}{}{}", + mod_ident_to_ide_string(mod_ident, Some(&datatype_name.value()), true), + datatype_name, + type_args + ) } else { - struct_name.to_string() + datatype_name.to_string() } } }, Type_::Fun(args, ret) => { format!( "|{}| -> {}", - type_list_to_ide_string(args, verbose), + type_list_to_ide_string(args, /* separate_lines */ false, verbose), type_to_ide_string(ret, verbose) ) } @@ -1004,12 +1062,18 @@ pub fn type_to_ide_string(sp!(_, t): &Type, verbose: bool) -> String { } } -pub fn type_list_to_ide_string(types: &[Type], verbose: bool) -> String { +pub fn type_list_to_ide_string(types: &[Type], separate_lines: bool, verbose: bool) -> String { types .iter() - .map(|t| type_to_ide_string(t, verbose)) + .map(|t| { + if separate_lines { + format!("\t{}", type_to_ide_string(t, verbose)) + } else { + type_to_ide_string(t, verbose) + } + }) .collect::>() - .join(", ") + .join(if separate_lines { ",\n" } else { ", " }) } fn datatype_type_list_to_ide_string(types: &[(Type, bool)], verbose: bool) -> String { @@ -1127,15 +1191,59 @@ fn ast_value_to_ide_string(sp!(_, val): &Value) -> String { } } -pub fn mod_ident_to_ide_string(mod_ident: &E::ModuleIdent_) -> String { +/// Creates a string representing a module ID, either on it's owne as in `pkg::module` +/// or as part of a datatype or function type, in which it should be `pkg::module::`. +/// If it's part of the datatype, name of the datatype is passed in `datatype_name_opt`. +pub fn mod_ident_to_ide_string( + mod_ident: &ModuleIdent_, + datatype_name_opt: Option<&Symbol>, + is_access_chain_prefix: bool, // part of access chaing that should end with `::` +) -> String { use E::Address as A; + // the module ID is to be a prefix to a data + let suffix = if is_access_chain_prefix { "::" } else { "" }; match mod_ident.address { - A::Numerical { - name: None, value, .. - } => format!("{value}::{}", mod_ident.module).to_string(), - A::Numerical { name: Some(n), .. } | A::NamedUnassigned(n) => { - format!("{n}::{}", mod_ident.module).to_string() + A::Numerical { name, value, .. } => { + let pkg_name = match name { + Some(n) => n.to_string(), + None => value.to_string(), + }; + + let Ok(std_lib_pkg_address) = AccountAddress::from_hex_literal(STD_LIB_PKG_ADDRESS) + else { + // getting stdlib address did not work - use the whole thing + return format!("{pkg_name}::{}{}", mod_ident.module, suffix); + }; + if value.value.into_inner() != std_lib_pkg_address { + // it's not a stdlib package - use the whole thing + return format!("{pkg_name}::{}{}", mod_ident.module, suffix); + } + // try stripping both package and module if this conversion + // is for a datatype, oherwise try only stripping package + if let Some(datatype_name) = datatype_name_opt { + if IMPLICIT_STD_MEMBERS.iter().any( + |(implicit_mod_name, implicit_datatype_name, _)| { + mod_ident.module.value() == *implicit_mod_name + && datatype_name == implicit_datatype_name + }, + ) { + // strip both package and module (whether its meant to be + // part of access chain or not, if there is not module, + // there should be no `::` at the end) + return "".to_string(); + } + } + if IMPLICIT_STD_MODULES + .iter() + .any(|implicit_mod_name| mod_ident.module.value() == *implicit_mod_name) + { + // strip package + return format!("{}{}", mod_ident.module.value(), suffix); + } + // stripping prefix didn't work - use the whole thing + format!("{pkg_name}::{}{}", mod_ident.module, suffix) } + A::NamedUnassigned(n) => format!("{n}::{}", mod_ident.module).to_string(), } } @@ -1713,13 +1821,14 @@ pub fn get_compiled_pkg( }; let mut edition = None; + let mut comments = None; build_plan.compile_with_driver_and_deps(dependencies, &mut std::io::sink(), |compiler| { let compiler = compiler.set_ide_mode(); // extract expansion AST let (files, compilation_result) = compiler .set_pre_compiled_lib_opt(compiled_libs.clone()) .run::()?; - let (_, compiler) = match compilation_result { + let (comments_map, compiler) = match compilation_result { Ok(v) => v, Err((_pass, diags)) => { let failure = true; @@ -1728,6 +1837,7 @@ pub fn get_compiled_pkg( return Ok((files, vec![])); } }; + comments = Some(comments_map); eprintln!("compiled to parsed AST"); let (compiler, parsed_program) = compiler.into_ast(); parsed_ast = Some(parsed_program.clone()); @@ -1791,6 +1901,10 @@ pub fn get_compiled_pkg( // when failing to produce the ASTs let parsed_program = parsed_ast.unwrap(); let typed_program = typed_ast.clone().unwrap(); + let mut all_comments = comments.unwrap(); + if let Some(libs) = &compiled_libs { + all_comments.extend(libs.comments.clone()); + } let compiled_pkg_info = CompiledPkgInfo { parsed_program, typed_program, @@ -1799,6 +1913,7 @@ pub fn get_compiled_pkg( mapped_files, edition, compiler_info, + all_comments, }; Ok((Some(compiled_pkg_info), ide_diagnostics)) } @@ -1809,17 +1924,6 @@ pub fn compute_symbols_pre_process( compiled_pkg_info: &CompiledPkgInfo, cursor_info: Option<(&PathBuf, Position)>, ) -> Option { - let mut file_id_to_lines = HashMap::new(); - for file_id in compiled_pkg_info.mapped_files.file_mapping().values() { - let Ok(file) = compiled_pkg_info.mapped_files.files().get(*file_id) else { - eprintln!("file id without source code"); - continue; - }; - let source = file.source(); - let lines: Vec = source.lines().map(String::from).collect(); - file_id_to_lines.insert(*file_id, lines); - } - let mut fields_order_info = FieldOrderInfo::new(); pre_process_parsed_program(&compiled_pkg_info.parsed_program, &mut fields_order_info); @@ -1830,13 +1934,13 @@ pub fn compute_symbols_pre_process( &compiled_pkg_info.typed_program.modules, &fields_order_info, &compiled_pkg_info.mapped_files, - &file_id_to_lines, &mut computation_data.mod_outer_defs, &mut computation_data.mod_use_defs, &mut computation_data.references, &mut computation_data.def_info, &compiled_pkg_info.edition, cursor_context.as_mut(), + &compiled_pkg_info.all_comments, ); if let Some(libs) = compiled_pkg_info.libs.clone() { @@ -1844,13 +1948,13 @@ pub fn compute_symbols_pre_process( &libs.typing.modules, &fields_order_info, &compiled_pkg_info.mapped_files, - &file_id_to_lines, &mut computation_data.mod_outer_defs, &mut computation_data.mod_use_defs, &mut computation_data.references, &mut computation_data.def_info, &compiled_pkg_info.edition, None, // Cursor can never be in a compiled library(?) + &compiled_pkg_info.all_comments, ); } cursor_context @@ -2070,13 +2174,13 @@ fn pre_process_typed_modules( typed_modules: &UniqueMap, fields_order_info: &FieldOrderInfo, files: &MappedFiles, - file_id_to_lines: &HashMap>, mod_outer_defs: &mut BTreeMap, mod_use_defs: &mut BTreeMap, references: &mut References, def_info: &mut DefMap, edition: &Option, mut cursor_context: Option<&mut CursorContext>, + all_comments: &CommentMap, ) { for (pos, module_ident, module_def) in typed_modules { // If the cursor is in this module, mark that down. @@ -2094,10 +2198,10 @@ fn pre_process_typed_modules( module_def, fields_order_info, files, - file_id_to_lines, references, def_info, edition, + all_comments, ); mod_outer_defs.insert(mod_ident_str.clone(), defs); mod_use_defs.insert(mod_ident_str, symbols); @@ -2253,15 +2357,21 @@ pub fn empty_symbols() -> Symbols { } } +/// Get optional doc comment string at a given location. +fn get_doc_string(all_comments: &CommentMap, loc: Loc) -> Option { + all_comments + .get(&loc.file_hash()) + .and_then(|m| m.get(&loc.start())) + .cloned() +} + fn field_defs_and_types( datatype_name: Symbol, - datatype_loc: Loc, fields: &E::Fields, fields_order_opt: Option<&BTreeMap>, mod_ident: &ModuleIdent, - files: &MappedFiles, - file_id_to_lines: &HashMap>, def_info: &mut DefMap, + all_comments: &CommentMap, ) -> (Vec, Vec) { let mut field_defs = vec![]; let mut field_types = vec![]; @@ -2278,7 +2388,7 @@ fn field_defs_and_types( name: *fname, loc: floc, }); - let doc_string = extract_doc_string(files, file_id_to_lines, &floc, Some(datatype_loc)); + let doc_string = get_doc_string(all_comments, floc); def_info.insert( floc, DefInfo::Field( @@ -2326,10 +2436,10 @@ fn get_mod_outer_defs( mod_def: &ModuleDefinition, fields_order_info: &FieldOrderInfo, files: &MappedFiles, - file_id_to_lines: &HashMap>, references: &mut References, def_info: &mut DefMap, edition: &Option, + all_comments: &CommentMap, ) -> (ModuleDefs, UseDefMap) { let mut structs = BTreeMap::new(); let mut enums = BTreeMap::new(); @@ -2350,13 +2460,11 @@ fn get_mod_outer_defs( .and_then(|s| s.get(name)); (field_defs, field_types) = field_defs_and_types( *name, - name_loc, fields, fields_order_opt, mod_ident, - files, - file_id_to_lines, def_info, + all_comments, ); }; @@ -2381,7 +2489,7 @@ fn get_mod_outer_defs( } else { Visibility::Internal }; - let doc_string = extract_doc_string(files, file_id_to_lines, &name_loc, None); + let doc_string = get_doc_string(all_comments, def.loc); def_info.insert( name_loc, DefInfo::Struct( @@ -2411,13 +2519,11 @@ fn get_mod_outer_defs( .and_then(|v| v.get(vname)); let (defs, types) = field_defs_and_types( *name, - name_loc, fields, fields_order_opt, mod_ident, - files, - file_id_to_lines, def_info, + all_comments, ); (defs, types, *pos_fields) } @@ -2431,8 +2537,7 @@ fn get_mod_outer_defs( }); variants_info.insert(*vname, (vname_loc, field_defs, positional)); - let vdoc_string = - extract_doc_string(files, file_id_to_lines, &vname_loc, Some(name_loc)); + let vdoc_string = get_doc_string(all_comments, def.loc); def_info.insert( vname_loc, DefInfo::Variant( @@ -2454,7 +2559,7 @@ fn get_mod_outer_defs( info: MemberDefInfo::Enum { variants_info }, }, ); - let enum_doc_string = extract_doc_string(files, file_id_to_lines, &name_loc, None); + let enum_doc_string = get_doc_string(all_comments, def.loc); def_info.insert( name_loc, DefInfo::Enum( @@ -2477,7 +2582,7 @@ fn get_mod_outer_defs( info: MemberDefInfo::Const, }, ); - let doc_string = extract_doc_string(files, file_id_to_lines, &name_loc, None); + let doc_string = get_doc_string(all_comments, c.loc); def_info.insert( name_loc, DefInfo::Const( @@ -2501,7 +2606,7 @@ fn get_mod_outer_defs( } else { FunType::Regular }; - let doc_string = extract_doc_string(files, file_id_to_lines, &name_loc, None); + let doc_string = get_doc_string(all_comments, fun.loc); let fun_info = DefInfo::Function( mod_ident.value, fun.visibility, @@ -2545,7 +2650,7 @@ fn get_mod_outer_defs( let mut use_def_map = UseDefMap::new(); let ident = mod_ident.value; - let doc_comment = extract_doc_string(files, file_id_to_lines, loc, None); + let doc_string = get_doc_string(all_comments, mod_def.loc); let mod_defs = ModuleDefs { fhash, ident, @@ -2575,7 +2680,7 @@ fn get_mod_outer_defs( ); def_info.insert( mod_defs.name_loc, - DefInfo::Module(mod_ident_to_ide_string(&ident), doc_comment), + DefInfo::Module(mod_ident_to_ide_string(&ident, None, false), doc_string), ); } @@ -2673,95 +2778,6 @@ pub fn find_datatype(mod_defs: &ModuleDefs, datatype_name: &Symbol) -> Option>, - loc: &Loc, - outer_def_loc: Option, -) -> Option { - let file_hash = loc.file_hash(); - let file_id = files.file_hash_to_file_id(&file_hash)?; - let start_position = files.start_position_opt(loc)?; - let file_lines = file_id_to_lines.get(&file_id)?; - - if let Some(outer_loc) = outer_def_loc { - if let Some(outer_pos) = files.start_position_opt(&outer_loc) { - if outer_pos.line_offset() == start_position.line_offset() { - // It's a bit of a hack but due to the way we extract doc strings - // we should not do it for a definition if this definition is placed - // on the same line as another (outer) one as this way we'd pick - // doc comment of the outer definition. For example (where field - // of the struct would pick up struct's doc comment) - // - // /// Struct doc comment - // public struct Tmp { field: u64 } - return None; - } - } - } - - if start_position.line_offset() == 0 { - return None; - } - - let mut iter = start_position.line_offset() - 1; - let mut line_before = file_lines[iter].trim(); - - let mut doc_string = String::new(); - // Detect the two different types of docstrings - if line_before.starts_with("///") { - while let Some(stripped_line) = line_before.strip_prefix("///") { - doc_string = format!("{}\n{}", stripped_line.trim(), doc_string); - if iter == 0 { - break; - } - iter -= 1; - line_before = file_lines[iter].trim(); - } - } else if line_before.ends_with("*/") { - let mut doc_string_found = false; - line_before = file_lines[iter].strip_suffix("*/").unwrap_or("").trim(); - - // Loop condition is a safe guard. - while !doc_string_found { - // We found the start of the multi-line comment/docstring - if line_before.starts_with("/*") { - let is_doc = line_before.starts_with("/**") && !line_before.starts_with("/***"); - - // Invalid doc_string start prefix. - if !is_doc { - return None; - } - - line_before = line_before.strip_prefix("/**").unwrap_or("").trim(); - doc_string_found = true; - } - - doc_string = format!("{}\n{}", line_before, doc_string); - - if iter == 0 { - break; - } - - iter -= 1; - line_before = file_lines[iter].trim(); - } - - // No doc_string found - return String::new(); - if !doc_string_found { - return None; - } - } - - // No point in trying to print empty comment - if doc_string.is_empty() { - return None; - } - - Some(doc_string) -} - /// Handles go-to-def request of the language server pub fn on_go_to_def_request(context: &Context, request: &Request) { let symbols_map = &context.symbols.lock().unwrap(); diff --git a/external-crates/move/crates/move-analyzer/tests/colon_colon_completion.exp b/external-crates/move/crates/move-analyzer/tests/colon_colon_completion.exp index fe9138fda0f88..84f7f87073f55 100644 --- a/external-crates/move/crates/move-analyzer/tests/colon_colon_completion.exp +++ b/external-crates/move/crates/move-analyzer/tests/colon_colon_completion.exp @@ -281,119 +281,119 @@ Method 'targ_type()' use line: 25, use_col: 17 Method 'and!()' INSERT TEXT: 'and!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::and)' + TARGET : '(option::and)' TYPE : 'fun <$T, $U>(Option, |$T| -> Option): Option' Method 'and_ref!()' INSERT TEXT: 'and_ref!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::and_ref)' + TARGET : '(option::and_ref)' TYPE : 'fun <$T, $U>(&Option, |&$T| -> Option): Option' Method 'borrow()' INSERT TEXT: 'borrow(${1:t})' - TARGET : '(std::option::borrow)' + TARGET : '(option::borrow)' TYPE : 'fun (&Option): &Element' Method 'borrow_mut()' INSERT TEXT: 'borrow_mut(${1:t})' - TARGET : '(std::option::borrow_mut)' + TARGET : '(option::borrow_mut)' TYPE : 'fun (&mut Option): &mut Element' Method 'borrow_with_default()' INSERT TEXT: 'borrow_with_default(${1:t}, ${2:default_ref})' - TARGET : '(std::option::borrow_with_default)' + TARGET : '(option::borrow_with_default)' TYPE : 'fun (&Option, &Element): &Element' Method 'contains()' INSERT TEXT: 'contains(${1:t}, ${2:e_ref})' - TARGET : '(std::option::contains)' + TARGET : '(option::contains)' TYPE : 'fun (&Option, &Element): bool' Method 'destroy!()' INSERT TEXT: 'destroy!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::destroy)' + TARGET : '(option::destroy)' TYPE : 'fun <$T>(Option, |$T| -> ())' Method 'destroy_none()' INSERT TEXT: 'destroy_none(${1:t})' - TARGET : '(std::option::destroy_none)' + TARGET : '(option::destroy_none)' TYPE : 'fun (Option)' Method 'destroy_or!()' INSERT TEXT: 'destroy_or!(${1:o}, ${2:default})' - TARGET : '(std::option::destroy_or)' + TARGET : '(option::destroy_or)' TYPE : 'fun <$T>(Option, $T): $T' Method 'destroy_some()' INSERT TEXT: 'destroy_some(${1:t})' - TARGET : '(std::option::destroy_some)' + TARGET : '(option::destroy_some)' TYPE : 'fun (Option): Element' Method 'destroy_with_default()' INSERT TEXT: 'destroy_with_default(${1:t}, ${2:default})' - TARGET : '(std::option::destroy_with_default)' + TARGET : '(option::destroy_with_default)' TYPE : 'fun (Option, Element): Element' Method 'do!()' INSERT TEXT: 'do!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::do)' + TARGET : '(option::do)' TYPE : 'fun <$T>(Option, |$T| -> ())' Method 'do_mut!()' INSERT TEXT: 'do_mut!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::do_mut)' + TARGET : '(option::do_mut)' TYPE : 'fun <$T>(&mut Option, |&mut $T| -> ())' Method 'do_ref!()' INSERT TEXT: 'do_ref!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::do_ref)' + TARGET : '(option::do_ref)' TYPE : 'fun <$T>(&Option, |&$T| -> ())' Method 'extract()' INSERT TEXT: 'extract(${1:t})' - TARGET : '(std::option::extract)' + TARGET : '(option::extract)' TYPE : 'fun (&mut Option): Element' Method 'fill()' INSERT TEXT: 'fill(${1:t}, ${2:e})' - TARGET : '(std::option::fill)' + TARGET : '(option::fill)' TYPE : 'fun (&mut Option, Element)' Method 'filter!()' INSERT TEXT: 'filter!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::filter)' + TARGET : '(option::filter)' TYPE : 'fun <$T>(Option, |&$T| -> bool): Option' Method 'get_with_default()' INSERT TEXT: 'get_with_default(${1:t}, ${2:default})' - TARGET : '(std::option::get_with_default)' + TARGET : '(option::get_with_default)' TYPE : 'fun (&Option, Element): Element' Method 'is_none()' INSERT TEXT: 'is_none(${1:t})' - TARGET : '(std::option::is_none)' + TARGET : '(option::is_none)' TYPE : 'fun (&Option): bool' Method 'is_some()' INSERT TEXT: 'is_some(${1:t})' - TARGET : '(std::option::is_some)' + TARGET : '(option::is_some)' TYPE : 'fun (&Option): bool' Method 'is_some_and!()' INSERT TEXT: 'is_some_and!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::is_some_and)' + TARGET : '(option::is_some_and)' TYPE : 'fun <$T>(&Option, |&$T| -> bool): bool' Method 'map!()' INSERT TEXT: 'map!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::map)' + TARGET : '(option::map)' TYPE : 'fun <$T, $U>(Option, |$T| -> $U): Option' Method 'map_ref!()' INSERT TEXT: 'map_ref!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::map_ref)' + TARGET : '(option::map_ref)' TYPE : 'fun <$T, $U>(&Option, |&$T| -> $U): Option' Method 'none()' INSERT TEXT: 'none()' - TARGET : '(std::option::none)' + TARGET : '(option::none)' TYPE : 'fun (): Option' Method 'or!()' INSERT TEXT: 'or!(${1:o}, ${2:default})' - TARGET : '(std::option::or)' + TARGET : '(option::or)' TYPE : 'fun <$T>(Option, Option): Option' Method 'some()' INSERT TEXT: 'some(${1:e})' - TARGET : '(std::option::some)' + TARGET : '(option::some)' TYPE : 'fun (Element): Option' Method 'swap()' INSERT TEXT: 'swap(${1:t}, ${2:e})' - TARGET : '(std::option::swap)' + TARGET : '(option::swap)' TYPE : 'fun (&mut Option, Element): Element' Method 'swap_or_fill()' INSERT TEXT: 'swap_or_fill(${1:t}, ${2:e})' - TARGET : '(std::option::swap_or_fill)' + TARGET : '(option::swap_or_fill)' TYPE : 'fun (&mut Option, Element): Option' Method 'to_vec()' INSERT TEXT: 'to_vec(${1:t})' - TARGET : '(std::option::to_vec)' + TARGET : '(option::to_vec)' TYPE : 'fun (Option): vector' -- test 12 ------------------- @@ -1011,119 +1011,119 @@ EnumMember 'SomeVariant' use line: 25, use_col: 16 Method 'and!()' INSERT TEXT: 'and!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::and)' + TARGET : '(option::and)' TYPE : 'fun <$T, $U>(Option, |$T| -> Option): Option' Method 'and_ref!()' INSERT TEXT: 'and_ref!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::and_ref)' + TARGET : '(option::and_ref)' TYPE : 'fun <$T, $U>(&Option, |&$T| -> Option): Option' Method 'borrow()' INSERT TEXT: 'borrow(${1:t})' - TARGET : '(std::option::borrow)' + TARGET : '(option::borrow)' TYPE : 'fun (&Option): &Element' Method 'borrow_mut()' INSERT TEXT: 'borrow_mut(${1:t})' - TARGET : '(std::option::borrow_mut)' + TARGET : '(option::borrow_mut)' TYPE : 'fun (&mut Option): &mut Element' Method 'borrow_with_default()' INSERT TEXT: 'borrow_with_default(${1:t}, ${2:default_ref})' - TARGET : '(std::option::borrow_with_default)' + TARGET : '(option::borrow_with_default)' TYPE : 'fun (&Option, &Element): &Element' Method 'contains()' INSERT TEXT: 'contains(${1:t}, ${2:e_ref})' - TARGET : '(std::option::contains)' + TARGET : '(option::contains)' TYPE : 'fun (&Option, &Element): bool' Method 'destroy!()' INSERT TEXT: 'destroy!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::destroy)' + TARGET : '(option::destroy)' TYPE : 'fun <$T>(Option, |$T| -> ())' Method 'destroy_none()' INSERT TEXT: 'destroy_none(${1:t})' - TARGET : '(std::option::destroy_none)' + TARGET : '(option::destroy_none)' TYPE : 'fun (Option)' Method 'destroy_or!()' INSERT TEXT: 'destroy_or!(${1:o}, ${2:default})' - TARGET : '(std::option::destroy_or)' + TARGET : '(option::destroy_or)' TYPE : 'fun <$T>(Option, $T): $T' Method 'destroy_some()' INSERT TEXT: 'destroy_some(${1:t})' - TARGET : '(std::option::destroy_some)' + TARGET : '(option::destroy_some)' TYPE : 'fun (Option): Element' Method 'destroy_with_default()' INSERT TEXT: 'destroy_with_default(${1:t}, ${2:default})' - TARGET : '(std::option::destroy_with_default)' + TARGET : '(option::destroy_with_default)' TYPE : 'fun (Option, Element): Element' Method 'do!()' INSERT TEXT: 'do!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::do)' + TARGET : '(option::do)' TYPE : 'fun <$T>(Option, |$T| -> ())' Method 'do_mut!()' INSERT TEXT: 'do_mut!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::do_mut)' + TARGET : '(option::do_mut)' TYPE : 'fun <$T>(&mut Option, |&mut $T| -> ())' Method 'do_ref!()' INSERT TEXT: 'do_ref!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::do_ref)' + TARGET : '(option::do_ref)' TYPE : 'fun <$T>(&Option, |&$T| -> ())' Method 'extract()' INSERT TEXT: 'extract(${1:t})' - TARGET : '(std::option::extract)' + TARGET : '(option::extract)' TYPE : 'fun (&mut Option): Element' Method 'fill()' INSERT TEXT: 'fill(${1:t}, ${2:e})' - TARGET : '(std::option::fill)' + TARGET : '(option::fill)' TYPE : 'fun (&mut Option, Element)' Method 'filter!()' INSERT TEXT: 'filter!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::filter)' + TARGET : '(option::filter)' TYPE : 'fun <$T>(Option, |&$T| -> bool): Option' Method 'get_with_default()' INSERT TEXT: 'get_with_default(${1:t}, ${2:default})' - TARGET : '(std::option::get_with_default)' + TARGET : '(option::get_with_default)' TYPE : 'fun (&Option, Element): Element' Method 'is_none()' INSERT TEXT: 'is_none(${1:t})' - TARGET : '(std::option::is_none)' + TARGET : '(option::is_none)' TYPE : 'fun (&Option): bool' Method 'is_some()' INSERT TEXT: 'is_some(${1:t})' - TARGET : '(std::option::is_some)' + TARGET : '(option::is_some)' TYPE : 'fun (&Option): bool' Method 'is_some_and!()' INSERT TEXT: 'is_some_and!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::is_some_and)' + TARGET : '(option::is_some_and)' TYPE : 'fun <$T>(&Option, |&$T| -> bool): bool' Method 'map!()' INSERT TEXT: 'map!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::map)' + TARGET : '(option::map)' TYPE : 'fun <$T, $U>(Option, |$T| -> $U): Option' Method 'map_ref!()' INSERT TEXT: 'map_ref!(${1:o}, |${2}| ${3})' - TARGET : '(std::option::map_ref)' + TARGET : '(option::map_ref)' TYPE : 'fun <$T, $U>(&Option, |&$T| -> $U): Option' Method 'none()' INSERT TEXT: 'none()' - TARGET : '(std::option::none)' + TARGET : '(option::none)' TYPE : 'fun (): Option' Method 'or!()' INSERT TEXT: 'or!(${1:o}, ${2:default})' - TARGET : '(std::option::or)' + TARGET : '(option::or)' TYPE : 'fun <$T>(Option, Option): Option' Method 'some()' INSERT TEXT: 'some(${1:e})' - TARGET : '(std::option::some)' + TARGET : '(option::some)' TYPE : 'fun (Element): Option' Method 'swap()' INSERT TEXT: 'swap(${1:t}, ${2:e})' - TARGET : '(std::option::swap)' + TARGET : '(option::swap)' TYPE : 'fun (&mut Option, Element): Element' Method 'swap_or_fill()' INSERT TEXT: 'swap_or_fill(${1:t}, ${2:e})' - TARGET : '(std::option::swap_or_fill)' + TARGET : '(option::swap_or_fill)' TYPE : 'fun (&mut Option, Element): Option' Method 'to_vec()' INSERT TEXT: 'to_vec(${1:t})' - TARGET : '(std::option::to_vec)' + TARGET : '(option::to_vec)' TYPE : 'fun (Option): vector' -- test 30 ------------------- diff --git a/external-crates/move/crates/move-analyzer/tests/consts.exp b/external-crates/move/crates/move-analyzer/tests/consts.exp index 99b15bf362afe..07aa620aad150 100644 --- a/external-crates/move/crates/move-analyzer/tests/consts.exp +++ b/external-crates/move/crates/move-analyzer/tests/consts.exp @@ -135,3 +135,27 @@ TypeDef: no info On Hover: const Symbols::M8::EQUAL: bool = 1 == 1 +-- test 17 ------------------- +use line: 34, use_ndx: 0 +Use: 'ERROR_CONST', start: 10, end: 21 +Def: 'ERROR_CONST', line: 33, def char: 10 +TypeDef: no info +On Hover: +const Symbols::M8::ERROR_CONST: u64 = 42 + +-- test 18 ------------------- +use line: 37, use_ndx: 0 +Use: 'ERROR_CONST', start: 16, end: 27 +Def: 'ERROR_CONST', line: 33, def char: 10 +TypeDef: no info +On Hover: +const Symbols::M8::ERROR_CONST: u64 = 42 + +-- test 19 ------------------- +use line: 37, use_ndx: 1 +Use: 'ERROR_CONST', start: 35, end: 46 +Def: 'ERROR_CONST', line: 33, def char: 10 +TypeDef: no info +On Hover: +const Symbols::M8::ERROR_CONST: u64 = 42 + diff --git a/external-crates/move/crates/move-analyzer/tests/consts.ide b/external-crates/move/crates/move-analyzer/tests/consts.ide index f7b7d9feb7055..de79e5274d82d 100644 --- a/external-crates/move/crates/move-analyzer/tests/consts.ide +++ b/external-crates/move/crates/move-analyzer/tests/consts.ide @@ -71,6 +71,18 @@ { "use_line": 31, "use_ndx": 2 + }, + { + "use_line": 34, + "use_ndx": 0 + }, + { + "use_line": 37, + "use_ndx": 0 + }, + { + "use_line": 37, + "use_ndx": 1 } ] } diff --git a/external-crates/move/crates/move-analyzer/tests/docstring.exp b/external-crates/move/crates/move-analyzer/tests/docstring.exp index d4fc4dd628c70..b48d2c995bbcc 100644 --- a/external-crates/move/crates/move-analyzer/tests/docstring.exp +++ b/external-crates/move/crates/move-analyzer/tests/docstring.exp @@ -1,17 +1,16 @@ == M6.move ======================================================== -- test 0 ------------------- use line: 5, use_ndx: 0 -Use: 'DocumentedStruct', start: 11, end: 27 -Def: 'DocumentedStruct', line: 4, def char: 11 -TypeDef: 'DocumentedStruct', line: 4, char: 11 +Use: 'DocumentedStruct', start: 18, end: 34 +Def: 'DocumentedStruct', line: 4, def char: 18 +TypeDef: 'DocumentedStruct', line: 4, char: 18 On Hover: -struct Symbols::M6::DocumentedStruct has drop, store, key { +public struct Symbols::M6::DocumentedStruct has drop, store { documented_field: u64 } -This is a documented struct -With a multi-line docstring - + This is a documented struct + With a multi-line docstring -- test 1 ------------------- use line: 11, use_ndx: 0 @@ -21,8 +20,7 @@ TypeDef: no info On Hover: const Symbols::M6::DOCUMENTED_CONSTANT: u64 = 42 -Constant containing the answer to the universe - + Constant containing the answer to the universe -- test 2 ------------------- use line: 15, use_ndx: 0 @@ -30,46 +28,45 @@ Use: 'unpack', start: 8, end: 14 Def: 'unpack', line: 14, def char: 8 TypeDef: no info On Hover: -fun Symbols::M6::unpack(s: Symbols::M6::DocumentedStruct): u64 - -A documented function that unpacks a DocumentedStruct +fun Symbols::M6::unpack( + s: Symbols::M6::DocumentedStruct +): u64 + A documented function that unpacks a DocumentedStruct -- test 3 ------------------- use line: 15, use_ndx: 1 Use: 's', start: 15, end: 16 Def: 's', line: 14, def char: 15 -TypeDef: 'DocumentedStruct', line: 4, char: 11 +TypeDef: 'DocumentedStruct', line: 4, char: 18 On Hover: s: Symbols::M6::DocumentedStruct -- test 4 ------------------- use line: 15, use_ndx: 2 Use: 'DocumentedStruct', start: 18, end: 34 -Def: 'DocumentedStruct', line: 4, def char: 11 -TypeDef: 'DocumentedStruct', line: 4, char: 11 +Def: 'DocumentedStruct', line: 4, def char: 18 +TypeDef: 'DocumentedStruct', line: 4, char: 18 On Hover: -struct Symbols::M6::DocumentedStruct has drop, store, key { +public struct Symbols::M6::DocumentedStruct has drop, store { documented_field: u64 } -This is a documented struct -With a multi-line docstring - + This is a documented struct + With a multi-line docstring -- test 5 ------------------- use line: 16, use_ndx: 0 Use: 'DocumentedStruct', start: 12, end: 28 -Def: 'DocumentedStruct', line: 4, def char: 11 -TypeDef: 'DocumentedStruct', line: 4, char: 11 +Def: 'DocumentedStruct', line: 4, def char: 18 +TypeDef: 'DocumentedStruct', line: 4, char: 18 On Hover: -struct Symbols::M6::DocumentedStruct has drop, store, key { +public struct Symbols::M6::DocumentedStruct has drop, store { documented_field: u64 } -This is a documented struct -With a multi-line docstring - + This is a documented struct + With a multi-line docstring -- test 6 ------------------- use line: 16, use_ndx: 1 @@ -80,14 +77,13 @@ On Hover: Symbols::M6::DocumentedStruct documented_field: u64 -A documented field - + A documented field -- test 7 ------------------- use line: 16, use_ndx: 3 Use: 's', start: 59, end: 60 Def: 's', line: 14, def char: 15 -TypeDef: 'DocumentedStruct', line: 4, char: 11 +TypeDef: 'DocumentedStruct', line: 4, char: 18 On Hover: s: Symbols::M6::DocumentedStruct @@ -100,13 +96,12 @@ On Hover: fun Symbols::M6::other_doc_struct(): Symbols::M7::OtherDocStruct -This is a multiline docstring - -This docstring has empty lines. - -It uses the ** format instead of /// + This is a multiline docstring + This docstring has empty lines. + It uses the ** format instead of /// + -- test 9 ------------------- use line: 32, use_ndx: 0 @@ -114,23 +109,23 @@ Use: 'acq', start: 8, end: 11 Def: 'acq', line: 31, def char: 8 TypeDef: no info On Hover: -fun Symbols::M6::acq(uint: u64): u64 - -Asterix based single-line docstring +fun Symbols::M6::acq( + uint: u64 +): u64 + Asterix based single-line docstring -- test 10 ------------------- use line: 27, use_ndx: 2 Use: 'OtherDocStruct', start: 41, end: 55 -Def: 'OtherDocStruct', line: 3, def char: 11 -TypeDef: 'OtherDocStruct', line: 3, char: 11 +Def: 'OtherDocStruct', line: 3, def char: 18 +TypeDef: 'OtherDocStruct', line: 3, char: 18 On Hover: -struct Symbols::M7::OtherDocStruct has drop { +public struct Symbols::M7::OtherDocStruct has drop { some_field: u64 } -Documented struct in another module - + Documented struct in another module -- test 11 ------------------- use line: 28, use_ndx: 1 @@ -138,10 +133,11 @@ Use: 'create_other_struct', start: 21, end: 40 Def: 'create_other_struct', line: 9, def char: 15 TypeDef: no info On Hover: -public fun Symbols::M7::create_other_struct(v: u64): Symbols::M7::OtherDocStruct - -Documented initializer in another module +public fun Symbols::M7::create_other_struct( + v: u64 +): Symbols::M7::OtherDocStruct + Documented initializer in another module -- test 12 ------------------- use line: 28, use_ndx: 2 @@ -151,21 +147,19 @@ TypeDef: no info On Hover: const Symbols::M6::DOCUMENTED_CONSTANT: u64 = 42 -Constant containing the answer to the universe - + Constant containing the answer to the universe -- test 13 ------------------- use line: 39, use_ndx: 1 Use: 'OtherDocStruct', start: 35, end: 49 -Def: 'OtherDocStruct', line: 3, def char: 11 -TypeDef: 'OtherDocStruct', line: 3, char: 11 +Def: 'OtherDocStruct', line: 3, def char: 18 +TypeDef: 'OtherDocStruct', line: 3, char: 18 On Hover: -struct Symbols::M7::OtherDocStruct has drop { +public struct Symbols::M7::OtherDocStruct has drop { some_field: u64 } -Documented struct in another module - + Documented struct in another module -- test 14 ------------------- use line: 44, use_ndx: 1 @@ -183,3 +177,78 @@ TypeDef: no info On Hover: param: T +-- test 16 ------------------- +use line: 56, use_ndx: 0 +Use: 'code_block_doc_slash', start: 8, end: 28 +Def: 'code_block_doc_slash', line: 55, def char: 8 +TypeDef: no info +On Hover: +fun Symbols::M6::code_block_doc_slash() + + A documented function with code block + (should preserve indentation in the code block) + + ```rust + fun foo() { + 42 + } + ``` + +-- test 17 ------------------- +use line: 68, use_ndx: 0 +Use: 'code_block_doc_star', start: 8, end: 27 +Def: 'code_block_doc_star', line: 67, def char: 8 +TypeDef: no info +On Hover: +fun Symbols::M6::code_block_doc_star() + + + A documented function with code block + (should preserve indentation in the code block) + + ```rust + fun foo() { + 42 + } + ``` + + +-- test 18 ------------------- +use line: 80, use_ndx: 0 +Use: 'misformatted_docstring', start: 8, end: 30 +Def: 'misformatted_docstring', line: 79, def char: 8 +TypeDef: no info +On Hover: +fun Symbols::M6::misformatted_docstring() + + + Misformatted docstring to have fewer whitespace in the body than + at the ending marker. + + + Beginning of this string should not disappear. + +Beginning of this string should not disappear either. + + + +-- test 19 ------------------- +use line: 85, use_ndx: 0 +Use: 'attributes_after_docstring', start: 8, end: 34 +Def: 'attributes_after_docstring', line: 84, def char: 8 +TypeDef: no info +On Hover: +fun Symbols::M6::attributes_after_docstring() + + Docstring before attributes + +-- test 20 ------------------- +use line: 89, use_ndx: 0 +Use: 'attributes_before_docstring', start: 8, end: 35 +Def: 'attributes_before_docstring', line: 88, def char: 8 +TypeDef: no info +On Hover: +fun Symbols::M6::attributes_before_docstring() + + Docstring after attributes + diff --git a/external-crates/move/crates/move-analyzer/tests/docstring.ide b/external-crates/move/crates/move-analyzer/tests/docstring.ide index 1aff49d4e0c8e..ee95a5c428e3a 100644 --- a/external-crates/move/crates/move-analyzer/tests/docstring.ide +++ b/external-crates/move/crates/move-analyzer/tests/docstring.ide @@ -68,6 +68,26 @@ { "use_line": 44, "use_ndx": 2 + }, + { + "use_line": 56, + "use_ndx": 0 + }, + { + "use_line": 68, + "use_ndx": 0 + }, + { + "use_line": 80, + "use_ndx": 0 + }, + { + "use_line": 85, + "use_ndx": 0 + }, + { + "use_line": 89, + "use_ndx": 0 } ] } diff --git a/external-crates/move/crates/move-analyzer/tests/dot_calls.exp b/external-crates/move/crates/move-analyzer/tests/dot_calls.exp index d7f5f78ba1225..ff4b7ad89d16f 100644 --- a/external-crates/move/crates/move-analyzer/tests/dot_calls.exp +++ b/external-crates/move/crates/move-analyzer/tests/dot_calls.exp @@ -13,7 +13,9 @@ Use: 'foo', start: 25, end: 28 Def: 'foo', line: 13, def char: 15 TypeDef: no info On Hover: -public fun Move2024::M1::foo(s: &Move2024::M1::SomeStruct): u64 +public fun Move2024::M1::foo( + s: &Move2024::M1::SomeStruct +): u64 -- test 2 ------------------- use line: 3, use_ndx: 2 @@ -31,7 +33,9 @@ Use: 'f1', start: 43, end: 45 Def: 'foo', line: 13, def char: 15 TypeDef: no info On Hover: -public fun Move2024::M1::foo(s: &Move2024::M1::SomeStruct): u64 +public fun Move2024::M1::foo( + s: &Move2024::M1::SomeStruct +): u64 -- test 4 ------------------- use line: 4, use_ndx: 0 @@ -47,7 +51,9 @@ Use: 'foo', start: 33, end: 36 Def: 'foo', line: 13, def char: 15 TypeDef: no info On Hover: -public fun Move2024::M1::foo(s: &Move2024::M1::SomeStruct): u64 +public fun Move2024::M1::foo( + s: &Move2024::M1::SomeStruct +): u64 -- test 6 ------------------- use line: 4, use_ndx: 2 @@ -65,7 +71,9 @@ Use: 'f2', start: 51, end: 53 Def: 'foo', line: 13, def char: 15 TypeDef: no info On Hover: -public fun Move2024::M1::foo(s: &Move2024::M1::SomeStruct): u64 +public fun Move2024::M1::foo( + s: &Move2024::M1::SomeStruct +): u64 -- test 8 ------------------- use line: 27, use_ndx: 0 @@ -81,7 +89,10 @@ Use: 'bar', start: 22, end: 25 Def: 'bar', line: 17, def char: 15 TypeDef: no info On Hover: -public fun Move2024::M1::bar(s: &Move2024::M1::SomeStruct, v: u64): u64 +public fun Move2024::M1::bar( + s: &Move2024::M1::SomeStruct, + v: u64 +): u64 -- test 10 ------------------- use line: 27, use_ndx: 2 @@ -107,7 +118,10 @@ Use: 'f3', start: 54, end: 56 Def: 'bar', line: 17, def char: 15 TypeDef: no info On Hover: -public fun Move2024::M1::bar(s: &Move2024::M1::SomeStruct, v: u64): u64 +public fun Move2024::M1::bar( + s: &Move2024::M1::SomeStruct, + v: u64 +): u64 -- test 13 ------------------- use line: 30, use_ndx: 0 @@ -123,7 +137,10 @@ Use: 'bar', start: 20, end: 23 Def: 'bar', line: 17, def char: 15 TypeDef: no info On Hover: -public fun Move2024::M1::bar(s: &Move2024::M1::SomeStruct, v: u64): u64 +public fun Move2024::M1::bar( + s: &Move2024::M1::SomeStruct, + v: u64 +): u64 -- test 15 ------------------- use line: 30, use_ndx: 2 @@ -141,7 +158,10 @@ Use: 'f4', start: 43, end: 45 Def: 'bar', line: 17, def char: 15 TypeDef: no info On Hover: -public fun Move2024::M1::bar(s: &Move2024::M1::SomeStruct, v: u64): u64 +public fun Move2024::M1::bar( + s: &Move2024::M1::SomeStruct, + v: u64 +): u64 -- test 17 ------------------- use line: 34, use_ndx: 0 @@ -157,7 +177,9 @@ Use: 'f1', start: 28, end: 30 Def: 'foo', line: 13, def char: 15 TypeDef: no info On Hover: -public fun Move2024::M1::foo(s: &Move2024::M1::SomeStruct): u64 +public fun Move2024::M1::foo( + s: &Move2024::M1::SomeStruct +): u64 -- test 19 ------------------- use line: 35, use_ndx: 0 @@ -173,7 +195,10 @@ Use: 'f3', start: 28, end: 30 Def: 'bar', line: 17, def char: 15 TypeDef: no info On Hover: -public fun Move2024::M1::bar(s: &Move2024::M1::SomeStruct, v: u64): u64 +public fun Move2024::M1::bar( + s: &Move2024::M1::SomeStruct, + v: u64 +): u64 -- test 21 ------------------- use line: 35, use_ndx: 2 diff --git a/external-crates/move/crates/move-analyzer/tests/dot_completion.exp b/external-crates/move/crates/move-analyzer/tests/dot_completion.exp index 547bc99ee900a..3a43095ec41d4 100644 --- a/external-crates/move/crates/move-analyzer/tests/dot_completion.exp +++ b/external-crates/move/crates/move-analyzer/tests/dot_completion.exp @@ -54,115 +54,115 @@ Method 'test()' use line: 4, use_col: 10 Method 'all!()' INSERT TEXT: 'all!(|${1}| ${2})' - TARGET : '(std::vector::all)' + TARGET : '(vector::all)' TYPE : 'fun <$T>(&vector<$T>, |&$T| -> bool): bool' Method 'any!()' INSERT TEXT: 'any!(|${1}| ${2})' - TARGET : '(std::vector::any)' + TARGET : '(vector::any)' TYPE : 'fun <$T>(&vector<$T>, |&$T| -> bool): bool' Method 'append()' INSERT TEXT: 'append(${1:other})' - TARGET : '(std::vector::append)' + TARGET : '(vector::append)' TYPE : 'fun (&mut vector, vector)' Method 'borrow()' INSERT TEXT: 'borrow(${1:i})' - TARGET : '(std::vector::borrow)' + TARGET : '(vector::borrow)' TYPE : 'fun (&vector, u64): &Element' Method 'borrow_mut()' INSERT TEXT: 'borrow_mut(${1:i})' - TARGET : '(std::vector::borrow_mut)' + TARGET : '(vector::borrow_mut)' TYPE : 'fun (&mut vector, u64): &mut Element' Method 'contains()' INSERT TEXT: 'contains(${1:e})' - TARGET : '(std::vector::contains)' + TARGET : '(vector::contains)' TYPE : 'fun (&vector, &Element): bool' Method 'count!()' INSERT TEXT: 'count!(|${1}| ${2})' - TARGET : '(std::vector::count)' + TARGET : '(vector::count)' TYPE : 'fun <$T>(&vector<$T>, |&$T| -> bool): u64' Method 'destroy!()' INSERT TEXT: 'destroy!(|${1}| ${2})' - TARGET : '(std::vector::destroy)' + TARGET : '(vector::destroy)' TYPE : 'fun <$T>(vector<$T>, |$T| -> ())' Method 'destroy_empty()' INSERT TEXT: 'destroy_empty()' - TARGET : '(std::vector::destroy_empty)' + TARGET : '(vector::destroy_empty)' TYPE : 'fun (vector)' Method 'do!()' INSERT TEXT: 'do!(|${1}| ${2})' - TARGET : '(std::vector::do)' + TARGET : '(vector::do)' TYPE : 'fun <$T>(vector<$T>, |$T| -> ())' Method 'do_mut!()' INSERT TEXT: 'do_mut!(|${1}| ${2})' - TARGET : '(std::vector::do_mut)' + TARGET : '(vector::do_mut)' TYPE : 'fun <$T>(&mut vector<$T>, |&mut $T| -> ())' Method 'do_ref!()' INSERT TEXT: 'do_ref!(|${1}| ${2})' - TARGET : '(std::vector::do_ref)' + TARGET : '(vector::do_ref)' TYPE : 'fun <$T>(&vector<$T>, |&$T| -> ())' Method 'filter!()' INSERT TEXT: 'filter!(|${1}| ${2})' - TARGET : '(std::vector::filter)' + TARGET : '(vector::filter)' TYPE : 'fun <$T>(vector<$T>, |&$T| -> bool): vector<$T>' Method 'find_index!()' INSERT TEXT: 'find_index!(|${1}| ${2})' - TARGET : '(std::vector::find_index)' + TARGET : '(vector::find_index)' TYPE : 'fun <$T>(&vector<$T>, |&$T| -> bool): Option' Method 'fold!()' INSERT TEXT: 'fold!(${1:init}, |${2}, ${3}| ${4})' - TARGET : '(std::vector::fold)' + TARGET : '(vector::fold)' TYPE : 'fun <$T, $Acc>(vector<$T>, $Acc, |$Acc, $T| -> $Acc): $Acc' Method 'index_of()' INSERT TEXT: 'index_of(${1:e})' - TARGET : '(std::vector::index_of)' + TARGET : '(vector::index_of)' TYPE : 'fun (&vector, &Element): (bool, u64)' Method 'insert()' INSERT TEXT: 'insert(${1:e}, ${2:i})' - TARGET : '(std::vector::insert)' + TARGET : '(vector::insert)' TYPE : 'fun (&mut vector, Element, u64)' Method 'is_empty()' INSERT TEXT: 'is_empty()' - TARGET : '(std::vector::is_empty)' + TARGET : '(vector::is_empty)' TYPE : 'fun (&vector): bool' Method 'length()' INSERT TEXT: 'length()' - TARGET : '(std::vector::length)' + TARGET : '(vector::length)' TYPE : 'fun (&vector): u64' Method 'map!()' INSERT TEXT: 'map!(|${1}| ${2})' - TARGET : '(std::vector::map)' + TARGET : '(vector::map)' TYPE : 'fun <$T, $U>(vector<$T>, |$T| -> $U): vector<$U>' Method 'map_ref!()' INSERT TEXT: 'map_ref!(|${1}| ${2})' - TARGET : '(std::vector::map_ref)' + TARGET : '(vector::map_ref)' TYPE : 'fun <$T, $U>(&vector<$T>, |&$T| -> $U): vector<$U>' Method 'partition!()' INSERT TEXT: 'partition!(|${1}| ${2})' - TARGET : '(std::vector::partition)' + TARGET : '(vector::partition)' TYPE : 'fun <$T>(vector<$T>, |&$T| -> bool): (vector<$T>, vector<$T>)' Method 'pop_back()' INSERT TEXT: 'pop_back()' - TARGET : '(std::vector::pop_back)' + TARGET : '(vector::pop_back)' TYPE : 'fun (&mut vector): Element' Method 'push_back()' INSERT TEXT: 'push_back(${1:e})' - TARGET : '(std::vector::push_back)' + TARGET : '(vector::push_back)' TYPE : 'fun (&mut vector, Element)' Method 'remove()' INSERT TEXT: 'remove(${1:i})' - TARGET : '(std::vector::remove)' + TARGET : '(vector::remove)' TYPE : 'fun (&mut vector, u64): Element' Method 'reverse()' INSERT TEXT: 'reverse()' - TARGET : '(std::vector::reverse)' + TARGET : '(vector::reverse)' TYPE : 'fun (&mut vector)' Method 'swap()' INSERT TEXT: 'swap(${1:i}, ${2:j})' - TARGET : '(std::vector::swap)' + TARGET : '(vector::swap)' TYPE : 'fun (&mut vector, u64, u64)' Method 'swap_remove()' INSERT TEXT: 'swap_remove(${1:i})' - TARGET : '(std::vector::swap_remove)' + TARGET : '(vector::swap_remove)' TYPE : 'fun (&mut vector, u64): Element' Method 'to_ascii_string()' INSERT TEXT: 'to_ascii_string()' @@ -182,26 +182,26 @@ Method 'try_to_string()' TYPE : 'fun (vector): Option' Method 'zip_do!()' INSERT TEXT: 'zip_do!(${1:v2}, |${2}, ${3}| ${4})' - TARGET : '(std::vector::zip_do)' + TARGET : '(vector::zip_do)' TYPE : 'fun <$T1, $T2>(vector<$T1>, vector<$T2>, |$T1, $T2| -> ())' Method 'zip_do_mut!()' INSERT TEXT: 'zip_do_mut!(${1:v2}, |${2}, ${3}| ${4})' - TARGET : '(std::vector::zip_do_mut)' + TARGET : '(vector::zip_do_mut)' TYPE : 'fun <$T1, $T2>(&mut vector<$T1>, &mut vector<$T2>, |&mut $T1, &mut $T2| -> ())' Method 'zip_do_ref!()' INSERT TEXT: 'zip_do_ref!(${1:v2}, |${2}, ${3}| ${4})' - TARGET : '(std::vector::zip_do_ref)' + TARGET : '(vector::zip_do_ref)' TYPE : 'fun <$T1, $T2>(&vector<$T1>, &vector<$T2>, |&$T1, &$T2| -> ())' Method 'zip_do_reverse!()' INSERT TEXT: 'zip_do_reverse!(${1:v2}, |${2}, ${3}| ${4})' - TARGET : '(std::vector::zip_do_reverse)' + TARGET : '(vector::zip_do_reverse)' TYPE : 'fun <$T1, $T2>(vector<$T1>, vector<$T2>, |$T1, $T2| -> ())' Method 'zip_map!()' INSERT TEXT: 'zip_map!(${1:v2}, |${2}, ${3}| ${4})' - TARGET : '(std::vector::zip_map)' + TARGET : '(vector::zip_map)' TYPE : 'fun <$T1, $T2, $U>(vector<$T1>, vector<$T2>, |$T1, $T2| -> $U): vector<$U>' Method 'zip_map_ref!()' INSERT TEXT: 'zip_map_ref!(${1:v2}, |${2}, ${3}| ${4})' - TARGET : '(std::vector::zip_map_ref)' + TARGET : '(vector::zip_map_ref)' TYPE : 'fun <$T1, $T2, $U>(&vector<$T1>, &vector<$T2>, |&$T1, &$T2| -> $U): vector<$U>' diff --git a/external-crates/move/crates/move-analyzer/tests/enums.exp b/external-crates/move/crates/move-analyzer/tests/enums.exp index 7c5b9fefb9353..3c08331893ae9 100644 --- a/external-crates/move/crates/move-analyzer/tests/enums.exp +++ b/external-crates/move/crates/move-analyzer/tests/enums.exp @@ -391,15 +391,22 @@ public enum Enums::variant_match::SomeEnum has drop { } -- test 11 ------------------- +use line: 15, use_ndx: 2 +Use: 'PositionalFields', start: 24, end: 40 +Def: 'PositionalFields', line: 7, def char: 8 +TypeDef: no info +On Hover: +Enums::variant_match::SomeEnum::PositionalFields(u64, Enums::variant_match::SomeStruct) + +-- test 12 ------------------- use line: 15, use_ndx: 3 Use: 's', start: 45, end: 46 -Def: 'SomeStruct', line: 7, def char: 30 +Def: 's', line: 12, def char: 29 TypeDef: 'SomeStruct', line: 2, char: 18 On Hover: -Enums::variant_match::SomeEnum -1: Enums::variant_match::SomeStruct +s: Enums::variant_match::SomeStruct --- test 12 ------------------- +-- test 13 ------------------- use line: 19, use_ndx: 0 Use: 'e', start: 20, end: 21 Def: 'e', line: 14, def char: 16 @@ -407,7 +414,7 @@ TypeDef: 'SomeEnum', line: 6, char: 16 On Hover: let mut e: Enums::variant_match::SomeEnum --- test 13 ------------------- +-- test 14 ------------------- use line: 20, use_ndx: 0 Use: 'SomeEnum', start: 12, end: 20 Def: 'SomeEnum', line: 6, def char: 16 @@ -419,7 +426,7 @@ public enum Enums::variant_match::SomeEnum has drop { PositionalFields( /* ... */ ) } --- test 14 ------------------- +-- test 15 ------------------- use line: 20, use_ndx: 1 Use: 'PositionalFields', start: 22, end: 38 Def: 'PositionalFields', line: 7, def char: 8 @@ -427,7 +434,7 @@ TypeDef: no info On Hover: Enums::variant_match::SomeEnum::PositionalFields(u64, Enums::variant_match::SomeStruct) --- test 15 ------------------- +-- test 16 ------------------- use line: 20, use_ndx: 2 Use: 'num', start: 39, end: 42 Def: 'u64', line: 7, def char: 25 @@ -436,7 +443,7 @@ On Hover: Enums::variant_match::SomeEnum 0: u64 --- test 16 ------------------- +-- test 17 ------------------- use line: 20, use_ndx: 3 Use: 's', start: 44, end: 45 Def: 'SomeStruct', line: 7, def char: 30 @@ -445,7 +452,7 @@ On Hover: Enums::variant_match::SomeEnum 1: Enums::variant_match::SomeStruct --- test 17 ------------------- +-- test 18 ------------------- use line: 21, use_ndx: 0 Use: 'num', start: 17, end: 20 Def: 'num', line: 19, def char: 39 @@ -453,7 +460,7 @@ TypeDef: no info On Hover: num: &mut u64 --- test 18 ------------------- +-- test 19 ------------------- use line: 21, use_ndx: 1 Use: 's', start: 23, end: 24 Def: 's', line: 19, def char: 44 @@ -461,7 +468,7 @@ TypeDef: 'SomeStruct', line: 2, char: 18 On Hover: s: &mut Enums::variant_match::SomeStruct --- test 19 ------------------- +-- test 20 ------------------- use line: 21, use_ndx: 2 Use: 'some_field', start: 25, end: 35 Def: 'some_field', line: 3, def char: 8 @@ -470,7 +477,7 @@ On Hover: Enums::variant_match::SomeStruct some_field: u64 --- test 20 ------------------- +-- test 21 ------------------- use line: 24, use_ndx: 0 Use: 'SE', start: 12, end: 14 Def: 'SomeEnum', line: 6, def char: 16 @@ -482,7 +489,7 @@ public enum Enums::variant_match::SomeEnum has drop { PositionalFields( /* ... */ ) } --- test 21 ------------------- +-- test 22 ------------------- use line: 24, use_ndx: 1 Use: 'NamedFields', start: 16, end: 27 Def: 'NamedFields', line: 8, def char: 8 @@ -490,7 +497,7 @@ TypeDef: no info On Hover: Enums::variant_match::SomeEnum::NamedFields{num1: u64, num2: u64, s: Enums::variant_match::SomeStruct} --- test 22 ------------------- +-- test 23 ------------------- use line: 24, use_ndx: 2 Use: 'num1', start: 30, end: 34 Def: 'num1', line: 8, def char: 21 @@ -499,7 +506,7 @@ On Hover: Enums::variant_match::SomeEnum num1: u64 --- test 23 ------------------- +-- test 24 ------------------- use line: 24, use_ndx: 3 Use: 'num2', start: 36, end: 40 Def: 'num2', line: 8, def char: 32 @@ -508,7 +515,7 @@ On Hover: Enums::variant_match::SomeEnum num2: u64 --- test 24 ------------------- +-- test 25 ------------------- use line: 24, use_ndx: 4 Use: 's', start: 46, end: 47 Def: 's', line: 8, def char: 43 @@ -517,7 +524,7 @@ On Hover: Enums::variant_match::SomeEnum s: Enums::variant_match::SomeStruct --- test 25 ------------------- +-- test 26 ------------------- use line: 24, use_ndx: 5 Use: 'num1', start: 55, end: 59 Def: 'num1', line: 23, def char: 30 @@ -525,7 +532,7 @@ TypeDef: no info On Hover: num1: &u64 --- test 26 ------------------- +-- test 27 ------------------- use line: 24, use_ndx: 6 Use: 's', start: 62, end: 63 Def: 's', line: 23, def char: 46 @@ -533,7 +540,7 @@ TypeDef: 'SomeStruct', line: 2, char: 18 On Hover: s: &Enums::variant_match::SomeStruct --- test 27 ------------------- +-- test 28 ------------------- use line: 24, use_ndx: 8 Use: 'num1', start: 79, end: 83 Def: 'num1', line: 23, def char: 30 @@ -541,7 +548,7 @@ TypeDef: no info On Hover: num1: &u64 --- test 28 ------------------- +-- test 29 ------------------- use line: 24, use_ndx: 9 Use: 'local', start: 86, end: 91 Def: 'local', line: 16, def char: 12 @@ -549,7 +556,7 @@ TypeDef: no info On Hover: let local: u64 --- test 29 ------------------- +-- test 30 ------------------- use line: 25, use_ndx: 0 Use: 's', start: 16, end: 17 Def: 's', line: 23, def char: 46 @@ -557,7 +564,7 @@ TypeDef: 'SomeStruct', line: 2, char: 18 On Hover: s: &mut Enums::variant_match::SomeStruct --- test 30 ------------------- +-- test 31 ------------------- use line: 25, use_ndx: 2 Use: 'num1', start: 32, end: 36 Def: 'num1', line: 23, def char: 30 @@ -565,7 +572,7 @@ TypeDef: no info On Hover: num1: &mut u64 --- test 31 ------------------- +-- test 32 ------------------- use line: 25, use_ndx: 3 Use: 'num2', start: 40, end: 44 Def: 'num2', line: 23, def char: 36 diff --git a/external-crates/move/crates/move-analyzer/tests/enums.ide b/external-crates/move/crates/move-analyzer/tests/enums.ide index 020a1a48b43ef..b03e94fb866a5 100644 --- a/external-crates/move/crates/move-analyzer/tests/enums.ide +++ b/external-crates/move/crates/move-analyzer/tests/enums.ide @@ -49,6 +49,10 @@ "use_line": 15, "use_ndx": 1 }, + { + "use_line": 15, + "use_ndx": 2 + }, { "use_line": 15, "use_ndx": 3 diff --git a/external-crates/move/crates/move-analyzer/tests/implicit_uses.exp b/external-crates/move/crates/move-analyzer/tests/implicit_uses.exp index 1ae18e4ecb1c0..2bc1f62e993ed 100644 --- a/external-crates/move/crates/move-analyzer/tests/implicit_uses.exp +++ b/external-crates/move/crates/move-analyzer/tests/implicit_uses.exp @@ -5,13 +5,12 @@ Use: 'Option', start: 13, end: 19 Def: 'Option', line: 7, def char: 18 TypeDef: 'Option', line: 7, char: 18 On Hover: -public struct std::option::Option has copy, drop, store { +public struct Option has copy, drop, store { vec: vector } -Abstraction of a value that may or may not be present. Implemented with a vector of size -zero or one because Move bytecode does not have ADTs. - + Abstraction of a value that may or may not be present. Implemented with a vector of size + zero or one because Move bytecode does not have ADTs. -- test 1 ------------------- use line: 8, use_ndx: 2 @@ -19,8 +18,7 @@ Use: 'option', start: 26, end: 32 Def: 'option', line: 4, def char: 12 TypeDef: no info On Hover: -module std::option - -This module defines the Option type and its methods to represent and handle an optional value. +module option + This module defines the Option type and its methods to represent and handle an optional value. diff --git a/external-crates/move/crates/move-analyzer/tests/imports.exp b/external-crates/move/crates/move-analyzer/tests/imports.exp index 9fde0cfd229db..56c9c662d5fa0 100644 --- a/external-crates/move/crates/move-analyzer/tests/imports.exp +++ b/external-crates/move/crates/move-analyzer/tests/imports.exp @@ -7,8 +7,7 @@ TypeDef: no info On Hover: module Symbols::M9 -A module doc comment - + A module doc comment -- test 1 ------------------- use line: 8, use_ndx: 0 @@ -69,20 +68,20 @@ module Symbols::M2 -- test 8 ------------------- use line: 11, use_ndx: 1 Use: 'SomeOtherStruct', start: 22, end: 37 -Def: 'SomeOtherStruct', line: 2, def char: 11 -TypeDef: 'SomeOtherStruct', line: 2, char: 11 +Def: 'SomeOtherStruct', line: 2, def char: 18 +TypeDef: 'SomeOtherStruct', line: 2, char: 18 On Hover: -struct Symbols::M2::SomeOtherStruct has drop { +public struct Symbols::M2::SomeOtherStruct has drop { some_field: u64 } -- test 9 ------------------- use line: 11, use_ndx: 2 Use: 'S', start: 41, end: 42 -Def: 'SomeOtherStruct', line: 2, def char: 11 -TypeDef: 'SomeOtherStruct', line: 2, char: 11 +Def: 'SomeOtherStruct', line: 2, def char: 18 +TypeDef: 'SomeOtherStruct', line: 2, char: 18 On Hover: -struct Symbols::M2::SomeOtherStruct has drop { +public struct Symbols::M2::SomeOtherStruct has drop { some_field: u64 } @@ -105,10 +104,10 @@ module Symbols::M1 -- test 12 ------------------- use line: 38, use_ndx: 1 Use: 'S', start: 27, end: 28 -Def: 'SomeOtherStruct', line: 2, def char: 11 -TypeDef: 'SomeOtherStruct', line: 2, char: 11 +Def: 'SomeOtherStruct', line: 2, def char: 18 +TypeDef: 'SomeOtherStruct', line: 2, char: 18 On Hover: -struct Symbols::M2::SomeOtherStruct has drop { +public struct Symbols::M2::SomeOtherStruct has drop { some_field: u64 } diff --git a/external-crates/move/crates/move-analyzer/tests/macros.exp b/external-crates/move/crates/move-analyzer/tests/macros.exp index d0e0324404abf..27774a855df2a 100644 --- a/external-crates/move/crates/move-analyzer/tests/macros.exp +++ b/external-crates/move/crates/move-analyzer/tests/macros.exp @@ -38,11 +38,10 @@ Use: 'n foo(', start: 12, end: 18 Def: 'vector', line: 6, def char: 12 TypeDef: no info On Hover: -module std::vector - -A variable-sized container that can hold any type. Indexing is 0-based, and -vectors are growable. This module has many native functions. +module vector + A variable-sized container that can hold any type. Indexing is 0-based, and + vectors are growable. This module has many native functions. -- test 1 ------------------- use line: 7, use_ndx: 1 @@ -50,7 +49,10 @@ Use: 'foo', start: 14, end: 17 Def: 'foo', line: 6, def char: 14 TypeDef: no info On Hover: -macro fun Macros::macros::foo($i: u64, $body: |u64| -> u64): u64 +macro fun Macros::macros::foo( + $i: u64, + $body: |u64| -> u64 +): u64 -- test 2 ------------------- use line: 7, use_ndx: 2 @@ -74,7 +76,10 @@ Use: 'bar', start: 14, end: 17 Def: 'bar', line: 14, def char: 14 TypeDef: no info On Hover: -macro fun Macros::macros::bar($i: Macros::macros::SomeStruct, $body: |Macros::macros::SomeStruct| -> Macros::macros::SomeStruct): Macros::macros::SomeStruct +macro fun Macros::macros::bar( + $i: Macros::macros::SomeStruct, + $body: |Macros::macros::SomeStruct| -> Macros::macros::SomeStruct +): Macros::macros::SomeStruct -- test 5 ------------------- use line: 15, use_ndx: 1 @@ -138,7 +143,10 @@ Use: 'for_each', start: 14, end: 22 Def: 'for_each', line: 18, def char: 14 TypeDef: no info On Hover: -macro fun Macros::macros::for_each<$T>($v: &vector<$T>, $body: |&$T| -> ()) +macro fun Macros::macros::for_each<$T>( + $v: &vector<$T>, + $body: |&$T| -> () +) -- test 12 ------------------- use line: 19, use_ndx: 1 @@ -194,7 +202,10 @@ Use: 'foo', start: 24, end: 27 Def: 'foo', line: 6, def char: 14 TypeDef: no info On Hover: -macro fun Macros::macros::foo($i: u64, $body: |u64| -> u64): u64 +macro fun Macros::macros::foo( + $i: u64, + $body: |u64| -> u64 +): u64 -- test 19 ------------------- use line: 33, use_ndx: 2 @@ -234,7 +245,10 @@ Use: 'foo', start: 68, end: 71 Def: 'foo', line: 6, def char: 14 TypeDef: no info On Hover: -macro fun Macros::macros::foo($i: u64, $body: |u64| -> u64): u64 +macro fun Macros::macros::foo( + $i: u64, + $body: |u64| -> u64 +): u64 -- test 24 ------------------- use line: 38, use_ndx: 8 @@ -282,7 +296,10 @@ Use: 'feach', start: 11, end: 16 Def: 'for_each', line: 18, def char: 14 TypeDef: no info On Hover: -macro fun Macros::macros::for_each<$T>($v: &vector<$T>, $body: |&$T| -> ()) +macro fun Macros::macros::for_each<$T>( + $v: &vector<$T>, + $body: |&$T| -> () +) -- test 30 ------------------- use line: 52, use_ndx: 2 diff --git a/external-crates/move/crates/move-analyzer/tests/mod_access.exp b/external-crates/move/crates/move-analyzer/tests/mod_access.exp index 6296b548ccba4..983479d8aee8a 100644 --- a/external-crates/move/crates/move-analyzer/tests/mod_access.exp +++ b/external-crates/move/crates/move-analyzer/tests/mod_access.exp @@ -7,8 +7,7 @@ TypeDef: no info On Hover: module Symbols::M9 -A module doc comment - + A module doc comment -- test 1 ------------------- use line: 20, use_ndx: 0 @@ -18,8 +17,7 @@ TypeDef: no info On Hover: module Symbols::M9 -A module doc comment - + A module doc comment -- test 2 ------------------- use line: 20, use_ndx: 3 @@ -29,8 +27,7 @@ TypeDef: no info On Hover: module Symbols::M9 -A module doc comment - + A module doc comment -- test 3 ------------------- use line: 23, use_ndx: 2 @@ -40,8 +37,7 @@ TypeDef: no info On Hover: module Symbols::M9 -A module doc comment - + A module doc comment -- test 4 ------------------- use line: 24, use_ndx: 0 @@ -51,8 +47,7 @@ TypeDef: no info On Hover: module Symbols::M9 -A module doc comment - + A module doc comment -- test 5 ------------------- use line: 28, use_ndx: 2 @@ -86,6 +81,5 @@ TypeDef: no info On Hover: module Symbols::M9 -A module doc comment - + A module doc comment diff --git a/external-crates/move/crates/move-analyzer/tests/move-2024/sources/structs.move b/external-crates/move/crates/move-analyzer/tests/move-2024/sources/structs.move index 8247c40af8dc2..ab3809311e16e 100644 --- a/external-crates/move/crates/move-analyzer/tests/move-2024/sources/structs.move +++ b/external-crates/move/crates/move-analyzer/tests/move-2024/sources/structs.move @@ -8,4 +8,41 @@ module Move2024::structs { (positional.0, positional.1) } + public struct Named has drop, copy { + some_field: u64, + another_field: SomeStruct, + } + + public fun pack_named(val1: u64, another_field: SomeStruct): Named { + Named { + some_field: val1, + another_field, + } + } + + public fun pack_positional(val1: u64, val2: SomeStruct): Positional { + Positional(val1, val2) + } + + public fun unpack_named(named: Named): (u64, SomeStruct) { + let Named { + some_field: val1, + another_field, + } = named; + (val1, another_field) + } + + public fun unpack_positional(positional: Positional): (u64, SomeStruct) { + let Positional(val1, val2) = positional; + (val1, val2) + } + + public fun borrow_named(named: Named): u64 { + named.some_field + } + + public fun borrow_positional(positional: Positional): u64 { + positional.0 + } + } diff --git a/external-crates/move/crates/move-analyzer/tests/structs.exp b/external-crates/move/crates/move-analyzer/tests/structs.exp index 628947df92668..14ebc0949f3d6 100644 --- a/external-crates/move/crates/move-analyzer/tests/structs.exp +++ b/external-crates/move/crates/move-analyzer/tests/structs.exp @@ -63,3 +63,125 @@ On Hover: Move2024::structs::Positional 1: Move2024::structs::SomeStruct +-- test 7 ------------------- +use line: 12, use_ndx: 0 +Use: 'some_field', start: 8, end: 18 +Def: 'some_field', line: 11, def char: 8 +TypeDef: no info +On Hover: +Move2024::structs::Named +some_field: u64 + +-- test 8 ------------------- +use line: 13, use_ndx: 0 +Use: 'another_field', start: 8, end: 21 +Def: 'another_field', line: 12, def char: 8 +TypeDef: 'SomeStruct', line: 2, char: 18 +On Hover: +Move2024::structs::Named +another_field: Move2024::structs::SomeStruct + +-- test 9 ------------------- +use line: 18, use_ndx: 0 +Use: 'some_field', start: 12, end: 22 +Def: 'some_field', line: 11, def char: 8 +TypeDef: no info +On Hover: +Move2024::structs::Named +some_field: u64 + +-- test 10 ------------------- +use line: 18, use_ndx: 1 +Use: 'val1', start: 24, end: 28 +Def: 'val1', line: 15, def char: 26 +TypeDef: no info +On Hover: +val1: u64 + +-- test 11 ------------------- +use line: 19, use_ndx: 0 +Use: 'another_field', start: 12, end: 25 +Def: 'another_field', line: 12, def char: 8 +TypeDef: 'SomeStruct', line: 2, char: 18 +On Hover: +Move2024::structs::Named +another_field: Move2024::structs::SomeStruct + +-- test 12 ------------------- +use line: 24, use_ndx: 1 +Use: 'val1', start: 19, end: 23 +Def: 'val1', line: 22, def char: 31 +TypeDef: no info +On Hover: +val1: u64 + +-- test 13 ------------------- +use line: 24, use_ndx: 2 +Use: 'val2', start: 25, end: 29 +Def: 'val2', line: 22, def char: 42 +TypeDef: 'SomeStruct', line: 2, char: 18 +On Hover: +val2: Move2024::structs::SomeStruct + +-- test 14 ------------------- +use line: 29, use_ndx: 0 +Use: 'some_field', start: 12, end: 22 +Def: 'some_field', line: 11, def char: 8 +TypeDef: no info +On Hover: +Move2024::structs::Named +some_field: u64 + +-- test 15 ------------------- +use line: 29, use_ndx: 1 +Use: 'val1', start: 24, end: 28 +Def: 'val1', line: 28, def char: 24 +TypeDef: no info +On Hover: +val1: u64 + +-- test 16 ------------------- +use line: 30, use_ndx: 0 +Use: 'another_field', start: 12, end: 25 +Def: 'another_field', line: 12, def char: 8 +TypeDef: 'SomeStruct', line: 2, char: 18 +On Hover: +Move2024::structs::Named +another_field: Move2024::structs::SomeStruct + +-- test 17 ------------------- +use line: 36, use_ndx: 1 +Use: 'v', start: 23, end: 24 +Def: 'u64', line: 4, def char: 29 +TypeDef: no info +On Hover: +Move2024::structs::Positional +0: u64 + +-- test 18 ------------------- +use line: 36, use_ndx: 2 +Use: 'v', start: 29, end: 30 +Def: 'SomeStruct', line: 4, def char: 34 +TypeDef: 'SomeStruct', line: 2, char: 18 +On Hover: +Move2024::structs::Positional +1: Move2024::structs::SomeStruct + +-- test 19 ------------------- +use line: 41, use_ndx: 1 +Use: 'INVALID USE IDENT', start: 14, end: 24 +Def: 'some_field', line: 11, def char: 8 +TypeDef: no info +On Hover: +Move2024::structs::Named +some_field: u64 + +-- test 20 ------------------- +use line: 45, use_ndx: 1 +Use: 'INVALID USE IDENT', start: 19, end: 20 +Def: 'u64', line: 4, def char: 29 +TypeDef: no info +On Hover: +Move2024::structs::Positional +0: u64 + diff --git a/external-crates/move/crates/move-analyzer/tests/structs.ide b/external-crates/move/crates/move-analyzer/tests/structs.ide index 8ecf41b6c138c..faa0b14e4584e 100644 --- a/external-crates/move/crates/move-analyzer/tests/structs.ide +++ b/external-crates/move/crates/move-analyzer/tests/structs.ide @@ -32,6 +32,62 @@ { "use_line": 8, "use_ndx": 3 + }, + { + "use_line": 12, + "use_ndx": 0 + }, + { + "use_line": 13, + "use_ndx": 0 + }, + { + "use_line": 18, + "use_ndx": 0 + }, + { + "use_line": 18, + "use_ndx": 1 + }, + { + "use_line": 19, + "use_ndx": 0 + }, + { + "use_line": 24, + "use_ndx": 1 + }, + { + "use_line": 24, + "use_ndx": 2 + }, + { + "use_line": 29, + "use_ndx": 0 + }, + { + "use_line": 29, + "use_ndx": 1 + }, + { + "use_line": 30, + "use_ndx": 0 + }, + { + "use_line": 36, + "use_ndx": 1 + }, + { + "use_line": 36, + "use_ndx": 2 + }, + { + "use_line": 41, + "use_ndx": 1 + }, + { + "use_line": 45, + "use_ndx": 1 } ] } diff --git a/external-crates/move/crates/move-analyzer/tests/symbols.exp b/external-crates/move/crates/move-analyzer/tests/symbols.exp index 7e06c2c210085..9ba63a796dff1 100644 --- a/external-crates/move/crates/move-analyzer/tests/symbols.exp +++ b/external-crates/move/crates/move-analyzer/tests/symbols.exp @@ -1,11 +1,11 @@ == M1.move ======================================================== -- test 0 ------------------- use line: 3, use_ndx: 0 -Use: 'SomeStruct', start: 11, end: 21 -Def: 'SomeStruct', line: 2, def char: 11 -TypeDef: 'SomeStruct', line: 2, char: 11 +Use: 'SomeStruct', start: 18, end: 28 +Def: 'SomeStruct', line: 2, def char: 18 +TypeDef: 'SomeStruct', line: 2, char: 18 On Hover: -struct Symbols::M1::SomeStruct has drop, store, key { +public struct Symbols::M1::SomeStruct has drop, store { some_field: u64 } @@ -23,33 +23,35 @@ Use: 'unpack', start: 8, end: 14 Def: 'unpack', line: 9, def char: 8 TypeDef: no info On Hover: -fun Symbols::M1::unpack(s: Symbols::M1::SomeStruct): u64 +fun Symbols::M1::unpack( + s: Symbols::M1::SomeStruct +): u64 -- test 3 ------------------- use line: 10, use_ndx: 1 Use: 's', start: 15, end: 16 Def: 's', line: 9, def char: 15 -TypeDef: 'SomeStruct', line: 2, char: 11 +TypeDef: 'SomeStruct', line: 2, char: 18 On Hover: s: Symbols::M1::SomeStruct -- test 4 ------------------- use line: 10, use_ndx: 2 Use: 'SomeStruct', start: 18, end: 28 -Def: 'SomeStruct', line: 2, def char: 11 -TypeDef: 'SomeStruct', line: 2, char: 11 +Def: 'SomeStruct', line: 2, def char: 18 +TypeDef: 'SomeStruct', line: 2, char: 18 On Hover: -struct Symbols::M1::SomeStruct has drop, store, key { +public struct Symbols::M1::SomeStruct has drop, store { some_field: u64 } -- test 5 ------------------- use line: 11, use_ndx: 0 Use: 'SomeStruct', start: 12, end: 22 -Def: 'SomeStruct', line: 2, def char: 11 -TypeDef: 'SomeStruct', line: 2, char: 11 +Def: 'SomeStruct', line: 2, def char: 18 +TypeDef: 'SomeStruct', line: 2, char: 18 On Hover: -struct Symbols::M1::SomeStruct has drop, store, key { +public struct Symbols::M1::SomeStruct has drop, store { some_field: u64 } @@ -74,7 +76,7 @@ value: u64 use line: 11, use_ndx: 3 Use: 's', start: 47, end: 48 Def: 's', line: 9, def char: 15 -TypeDef: 'SomeStruct', line: 2, char: 11 +TypeDef: 'SomeStruct', line: 2, char: 18 On Hover: s: Symbols::M1::SomeStruct @@ -89,20 +91,20 @@ value: u64 -- test 10 ------------------- use line: 20, use_ndx: 1 Use: 'SomeStruct', start: 16, end: 26 -Def: 'SomeStruct', line: 2, def char: 11 -TypeDef: 'SomeStruct', line: 2, char: 11 +Def: 'SomeStruct', line: 2, def char: 18 +TypeDef: 'SomeStruct', line: 2, char: 18 On Hover: -struct Symbols::M1::SomeStruct has drop, store, key { +public struct Symbols::M1::SomeStruct has drop, store { some_field: u64 } -- test 11 ------------------- use line: 21, use_ndx: 1 Use: 'SomeStruct', start: 18, end: 28 -Def: 'SomeStruct', line: 2, def char: 11 -TypeDef: 'SomeStruct', line: 2, char: 11 +Def: 'SomeStruct', line: 2, def char: 18 +TypeDef: 'SomeStruct', line: 2, char: 18 On Hover: -struct Symbols::M1::SomeStruct has drop, store, key { +public struct Symbols::M1::SomeStruct has drop, store { some_field: u64 } @@ -126,10 +128,10 @@ const Symbols::M1::SOME_CONST: u64 = 42 -- test 14 ------------------- use line: 25, use_ndx: 2 Use: 'SomeOtherStruct', start: 41, end: 56 -Def: 'SomeOtherStruct', line: 2, def char: 11 -TypeDef: 'SomeOtherStruct', line: 2, char: 11 +Def: 'SomeOtherStruct', line: 2, def char: 18 +TypeDef: 'SomeOtherStruct', line: 2, char: 18 On Hover: -struct Symbols::M2::SomeOtherStruct has drop { +public struct Symbols::M2::SomeOtherStruct has drop { some_field: u64 } @@ -139,7 +141,9 @@ Use: 'some_other_struct', start: 21, end: 38 Def: 'some_other_struct', line: 6, def char: 15 TypeDef: no info On Hover: -public fun Symbols::M2::some_other_struct(v: u64): Symbols::M2::SomeOtherStruct +public fun Symbols::M2::some_other_struct( + v: u64 +): Symbols::M2::SomeOtherStruct -- test 16 ------------------- use line: 26, use_ndx: 2 @@ -152,10 +156,10 @@ const Symbols::M1::SOME_CONST: u64 = 42 -- test 17 ------------------- use line: 31, use_ndx: 1 Use: 'SomeOtherStruct', start: 35, end: 50 -Def: 'SomeOtherStruct', line: 2, def char: 11 -TypeDef: 'SomeOtherStruct', line: 2, char: 11 +Def: 'SomeOtherStruct', line: 2, def char: 18 +TypeDef: 'SomeOtherStruct', line: 2, char: 18 On Hover: -struct Symbols::M2::SomeOtherStruct has drop { +public struct Symbols::M2::SomeOtherStruct has drop { some_field: u64 } @@ -165,7 +169,9 @@ Use: 'acq', start: 8, end: 11 Def: 'acq', line: 34, def char: 8 TypeDef: no info On Hover: -fun Symbols::M1::acq(uint: u64): u64 +fun Symbols::M1::acq( + uint: u64 +): u64 -- test 19 ------------------- use line: 41, use_ndx: 2 @@ -194,20 +200,20 @@ fun Symbols::M1::vec(): vector -- test 22 ------------------- use line: 46, use_ndx: 0 Use: 'SomeStruct', start: 15, end: 25 -Def: 'SomeStruct', line: 2, def char: 11 -TypeDef: 'SomeStruct', line: 2, char: 11 +Def: 'SomeStruct', line: 2, def char: 18 +TypeDef: 'SomeStruct', line: 2, char: 18 On Hover: -struct Symbols::M1::SomeStruct has drop, store, key { +public struct Symbols::M1::SomeStruct has drop, store { some_field: u64 } -- test 23 ------------------- use line: 46, use_ndx: 1 Use: 'SomeStruct', start: 27, end: 37 -Def: 'SomeStruct', line: 2, def char: 11 -TypeDef: 'SomeStruct', line: 2, char: 11 +Def: 'SomeStruct', line: 2, def char: 18 +TypeDef: 'SomeStruct', line: 2, char: 18 On Hover: -struct Symbols::M1::SomeStruct has drop, store, key { +public struct Symbols::M1::SomeStruct has drop, store { some_field: u64 } @@ -224,17 +230,17 @@ some_field: u64 use line: 46, use_ndx: 3 Use: 's', start: 57, end: 58 Def: 's', line: 44, def char: 12 -TypeDef: 'SomeStruct', line: 2, char: 11 +TypeDef: 'SomeStruct', line: 2, char: 18 On Hover: let s: Symbols::M1::SomeStruct -- test 26 ------------------- use line: 57, use_ndx: 1 Use: 'tmp', start: 21, end: 24 -Def: 'tmp', line: 55, def char: 12 +Def: 'tmp', line: 55, def char: 16 TypeDef: no info On Hover: -let tmp: u64 +let mut tmp: u64 -- test 27 ------------------- use line: 58, use_ndx: 0 @@ -258,7 +264,10 @@ Use: 'ret', start: 8, end: 11 Def: 'ret', line: 61, def char: 8 TypeDef: no info On Hover: -fun Symbols::M1::ret(p1: bool, p2: u64): u64 +fun Symbols::M1::ret( + p1: bool, + p2: u64 +): u64 -- test 30 ------------------- use line: 64, use_ndx: 0 @@ -312,7 +321,7 @@ const Symbols::M1::SOME_CONST: u64 = 42 use line: 95, use_ndx: 0 Use: 'outer', start: 8, end: 13 Def: 'outer', line: 93, def char: 12 -TypeDef: 'OuterStruct', line: 87, char: 11 +TypeDef: 'OuterStruct', line: 87, char: 18 On Hover: let outer: Symbols::M1::OuterStruct @@ -320,7 +329,7 @@ let outer: Symbols::M1::OuterStruct use line: 95, use_ndx: 1 Use: 'some_struct', start: 14, end: 25 Def: 'some_struct', line: 88, def char: 8 -TypeDef: 'SomeStruct', line: 2, char: 11 +TypeDef: 'SomeStruct', line: 2, char: 18 On Hover: Symbols::M1::OuterStruct some_struct: Symbols::M1::SomeStruct @@ -338,7 +347,7 @@ some_field: u64 use line: 103, use_ndx: 0 Use: 'some_struct', start: 10, end: 21 Def: 'some_struct', line: 88, def char: 8 -TypeDef: 'SomeStruct', line: 2, char: 11 +TypeDef: 'SomeStruct', line: 2, char: 18 On Hover: Symbols::M1::OuterStruct some_struct: Symbols::M1::SomeStruct @@ -347,7 +356,7 @@ some_struct: Symbols::M1::SomeStruct use line: 109, use_ndx: 1 Use: 'outer', start: 17, end: 22 Def: 'outer', line: 107, def char: 12 -TypeDef: 'OuterStruct', line: 87, char: 11 +TypeDef: 'OuterStruct', line: 87, char: 18 On Hover: let outer: Symbols::M1::OuterStruct @@ -355,7 +364,7 @@ let outer: Symbols::M1::OuterStruct use line: 109, use_ndx: 2 Use: 'some_struct', start: 23, end: 34 Def: 'some_struct', line: 88, def char: 8 -TypeDef: 'SomeStruct', line: 2, char: 11 +TypeDef: 'SomeStruct', line: 2, char: 18 On Hover: Symbols::M1::OuterStruct some_struct: Symbols::M1::SomeStruct @@ -389,7 +398,7 @@ const Symbols::M1::SOME_CONST: u64 = 42 use line: 123, use_ndx: 1 Use: 'p', start: 21, end: 22 Def: 'p', line: 122, def char: 21 -TypeDef: 'SomeOtherStruct', line: 2, char: 11 +TypeDef: 'SomeOtherStruct', line: 2, char: 18 On Hover: p: Symbols::M2::SomeOtherStruct @@ -397,7 +406,7 @@ p: Symbols::M2::SomeOtherStruct use line: 124, use_ndx: 0 Use: 'INVALID USE IDENT', start: 8, end: 9 Def: 'p', line: 122, def char: 21 -TypeDef: 'SomeOtherStruct', line: 2, char: 11 +TypeDef: 'SomeOtherStruct', line: 2, char: 18 On Hover: p: Symbols::M2::SomeOtherStruct @@ -405,7 +414,7 @@ p: Symbols::M2::SomeOtherStruct use line: 128, use_ndx: 0 Use: 'tmp', start: 12, end: 15 Def: 'tmp', line: 127, def char: 12 -TypeDef: 'SomeOtherStruct', line: 2, char: 11 +TypeDef: 'SomeOtherStruct', line: 2, char: 18 On Hover: let tmp: Symbols::M2::SomeOtherStruct @@ -413,15 +422,15 @@ let tmp: Symbols::M2::SomeOtherStruct use line: 130, use_ndx: 0 Use: 'INVALID USE IDENT', start: 12, end: 15 Def: 'tmp', line: 127, def char: 12 -TypeDef: 'SomeOtherStruct', line: 2, char: 11 +TypeDef: 'SomeOtherStruct', line: 2, char: 18 On Hover: let tmp: Symbols::M2::SomeOtherStruct == M3.move ======================================================== -- test 0 ------------------- use line: 3, use_ndx: 1 -Use: 'T', start: 23, end: 24 -Def: 'T', line: 2, def char: 23 +Use: 'T', start: 30, end: 31 +Def: 'T', line: 2, def char: 30 TypeDef: no info On Hover: T @@ -429,7 +438,7 @@ T -- test 1 ------------------- use line: 4, use_ndx: 1 Use: 'T', start: 20, end: 21 -Def: 'T', line: 2, def char: 23 +Def: 'T', line: 2, def char: 30 TypeDef: no info On Hover: T @@ -486,7 +495,7 @@ T use line: 12, use_ndx: 0 Use: 'INVALID USE IDENT', start: 8, end: 13 Def: 'param', line: 10, def char: 33 -TypeDef: 'ParamStruct', line: 2, char: 11 +TypeDef: 'ParamStruct', line: 2, char: 18 On Hover: param: Symbols::M3::ParamStruct @@ -501,17 +510,17 @@ T -- test 10 ------------------- use line: 24, use_ndx: 1 Use: 'ParamStruct', start: 20, end: 31 -Def: 'ParamStruct', line: 2, def char: 11 -TypeDef: 'ParamStruct', line: 2, char: 11 +Def: 'ParamStruct', line: 2, def char: 18 +TypeDef: 'ParamStruct', line: 2, char: 18 On Hover: -struct Symbols::M3::ParamStruct { +public struct Symbols::M3::ParamStruct { some_field: T } -- test 11 ------------------- use line: 24, use_ndx: 2 Use: 'T', start: 32, end: 33 -Def: 'T', line: 22, def char: 30 +Def: 'T', line: 22, def char: 37 TypeDef: no info On Hover: T @@ -552,18 +561,18 @@ let tmp: u64 -- test 4 ------------------- use line: 21, use_ndx: 0 Use: 'tmp', start: 15, end: 18 -Def: 'tmp', line: 18, def char: 12 +Def: 'tmp', line: 18, def char: 16 TypeDef: no info On Hover: -let tmp: u64 +let mut tmp: u64 -- test 5 ------------------- use line: 24, use_ndx: 1 Use: 'tmp', start: 26, end: 29 -Def: 'tmp', line: 18, def char: 12 +Def: 'tmp', line: 18, def char: 16 TypeDef: no info On Hover: -let tmp: u64 +let mut tmp: u64 -- test 6 ------------------- use line: 25, use_ndx: 1 @@ -576,10 +585,10 @@ let tmp: u64 -- test 7 ------------------- use line: 27, use_ndx: 0 Use: 'tmp', start: 12, end: 15 -Def: 'tmp', line: 18, def char: 12 +Def: 'tmp', line: 18, def char: 16 TypeDef: no info On Hover: -let tmp: u64 +let mut tmp: u64 -- test 8 ------------------- use line: 41, use_ndx: 1 @@ -592,10 +601,10 @@ let tmp: u64 -- test 9 ------------------- use line: 44, use_ndx: 0 Use: 'tmp', start: 16, end: 19 -Def: 'tmp', line: 34, def char: 12 +Def: 'tmp', line: 34, def char: 16 TypeDef: no info On Hover: -let tmp: u64 +let mut tmp: u64 -- test 10 ------------------- use line: 56, use_ndx: 0 @@ -605,3 +614,91 @@ TypeDef: no info On Hover: const Symbols::M5::SOME_CONST: u64 = 7 +-- test 11 ------------------- +use line: 58, use_ndx: 0 +Use: 'long_param_list', start: 15, end: 30 +Def: 'long_param_list', line: 57, def char: 15 +TypeDef: no info +On Hover: +public fun Symbols::M5::long_param_list( + foo: u64, + bar: u64, + baz: u64, + qux: u64 +) + +-- test 12 ------------------- +use line: 60, use_ndx: 0 +Use: 'short_type_param_list', start: 15, end: 36 +Def: 'short_type_param_list', line: 59, def char: 15 +TypeDef: no info +On Hover: +public fun Symbols::M5::short_type_param_list() + +-- test 13 ------------------- +use line: 62, use_ndx: 0 +Use: 'long_type_param_list', start: 15, end: 35 +Def: 'long_type_param_list', line: 61, def char: 15 +TypeDef: no info +On Hover: +public fun Symbols::M5::long_type_param_list< + TYPE1, + TYPE2, + TYPE3 +>() + +-- test 14 ------------------- +use line: 64, use_ndx: 0 +Use: 'combined_short_type_param_list', start: 15, end: 45 +Def: 'combined_short_type_param_list', line: 63, def char: 15 +TypeDef: no info +On Hover: +public fun Symbols::M5::combined_short_type_param_list( + foo: u64, + bar: u64, + baz: u64, + qux: u64 +) + +-- test 15 ------------------- +use line: 68, use_ndx: 0 +Use: 'combined_long_type_param_list', start: 15, end: 44 +Def: 'combined_long_type_param_list', line: 67, def char: 15 +TypeDef: no info +On Hover: +public fun Symbols::M5::combined_long_type_param_list< + TYPE1, + TYPE2, + TYPE3 +>( + foo: u64, + bar: u64, + baz: u64, + qux: u64 +) + +-- test 16 ------------------- +use line: 75, use_ndx: 2 +Use: 'extract', start: 37, end: 44 +Def: 'extract', line: 80, def char: 15 +TypeDef: no info +On Hover: +public fun option::extract( + t: &mut Option +): Element + + Convert a `some` option to a `none` by removing and returning the value stored inside `t` + Aborts if `t` does not hold a value + +-- test 17 ------------------- +use line: 78, use_ndx: 1 +Use: 'singleton', start: 21, end: 30 +Def: 'singleton', line: 64, def char: 15 +TypeDef: no info +On Hover: +public fun vector::singleton( + e: Element +): vector + + Return an vector of size one containing element `e`. + diff --git a/external-crates/move/crates/move-analyzer/tests/symbols.ide b/external-crates/move/crates/move-analyzer/tests/symbols.ide index bce3e5c8683f6..7bf47ee1ef15a 100644 --- a/external-crates/move/crates/move-analyzer/tests/symbols.ide +++ b/external-crates/move/crates/move-analyzer/tests/symbols.ide @@ -295,6 +295,34 @@ { "use_line": 56, "use_ndx": 0 + }, + { + "use_line": 58, + "use_ndx": 0 + }, + { + "use_line": 60, + "use_ndx": 0 + }, + { + "use_line": 62, + "use_ndx": 0 + }, + { + "use_line": 64, + "use_ndx": 0 + }, + { + "use_line": 68, + "use_ndx": 0 + }, + { + "use_line": 75, + "use_ndx": 2 + }, + { + "use_line": 78, + "use_ndx": 1 } ] } diff --git a/external-crates/move/crates/move-analyzer/tests/symbols/Move.toml b/external-crates/move/crates/move-analyzer/tests/symbols/Move.toml index 858e952648a42..98589032fe06c 100644 --- a/external-crates/move/crates/move-analyzer/tests/symbols/Move.toml +++ b/external-crates/move/crates/move-analyzer/tests/symbols/Move.toml @@ -1,6 +1,6 @@ [package] name = "Symbols" -edition = "legacy" +edition = "2024.alpha" [dependencies] MoveStdlib = { local = "../../../move-stdlib/", addr_subst = { "std" = "0x1" } } diff --git a/external-crates/move/crates/move-analyzer/tests/symbols/sources/M1.move b/external-crates/move/crates/move-analyzer/tests/symbols/sources/M1.move index b1cd4139af6d8..8e72efa783939 100644 --- a/external-crates/move/crates/move-analyzer/tests/symbols/sources/M1.move +++ b/external-crates/move/crates/move-analyzer/tests/symbols/sources/M1.move @@ -1,6 +1,6 @@ module Symbols::M1 { - struct SomeStruct has key, drop, store { + public struct SomeStruct has drop, store { some_field: u64, } @@ -52,8 +52,8 @@ module Symbols::M1 { value } - fun mut(): u64 { - let tmp = 7; + fun mutable(): u64 { + let mut tmp = 7; let r = &mut tmp; *r = SOME_CONST; tmp @@ -85,7 +85,7 @@ module Symbols::M1 { *tmp } - struct OuterStruct has key, drop { + public struct OuterStruct has drop { some_struct: SomeStruct, } diff --git a/external-crates/move/crates/move-analyzer/tests/symbols/sources/M2.move b/external-crates/move/crates/move-analyzer/tests/symbols/sources/M2.move index ad95754648c24..d674772c265a1 100644 --- a/external-crates/move/crates/move-analyzer/tests/symbols/sources/M2.move +++ b/external-crates/move/crates/move-analyzer/tests/symbols/sources/M2.move @@ -1,6 +1,6 @@ module Symbols::M2 { - struct SomeOtherStruct has drop { + public struct SomeOtherStruct has drop { some_field: u64, } diff --git a/external-crates/move/crates/move-analyzer/tests/symbols/sources/M3.move b/external-crates/move/crates/move-analyzer/tests/symbols/sources/M3.move index db264354600e4..85125136a0452 100644 --- a/external-crates/move/crates/move-analyzer/tests/symbols/sources/M3.move +++ b/external-crates/move/crates/move-analyzer/tests/symbols/sources/M3.move @@ -1,6 +1,6 @@ module Symbols::M3 { - struct ParamStruct { + public struct ParamStruct { some_field: T, } @@ -20,7 +20,7 @@ module Symbols::M3 { param } - struct AnotherParamStruct { + public struct AnotherParamStruct { some_field: ParamStruct, } diff --git a/external-crates/move/crates/move-analyzer/tests/symbols/sources/M4.move b/external-crates/move/crates/move-analyzer/tests/symbols/sources/M4.move index de30eea4afd37..abacd0215edd2 100644 --- a/external-crates/move/crates/move-analyzer/tests/symbols/sources/M4.move +++ b/external-crates/move/crates/move-analyzer/tests/symbols/sources/M4.move @@ -16,10 +16,10 @@ module Symbols::M4 { fun while_loop(): u64 { - let tmp = 7; + let mut tmp = 7; while (tmp > 0) { - let tmp2 = 1; + let mut tmp2 = 1; { let tmp = tmp; tmp2 = tmp - tmp2; @@ -32,10 +32,10 @@ module Symbols::M4 { fun loop_loop(): u64 { - let tmp = 7; + let mut tmp = 7; loop { - let tmp2 = 1; + let mut tmp2 = 1; { let tmp = tmp; tmp2 = tmp - tmp2; @@ -55,4 +55,27 @@ module Symbols::M5 { const SOME_CONST: u64 = 7; + public fun long_param_list(foo: u64, bar: u64, baz: u64, qux: u64) {} + + public fun short_type_param_list() {} + + public fun long_type_param_list() {} + + public fun combined_short_type_param_list( + foo: u64, bar: u64, baz: u64, qux: u64 + ) {} + + public fun combined_long_type_param_list( + foo: u64, bar: u64, baz: u64, qux: u64 + ) {} + + public fun stripped_types(mut opt: std::option::Option): vector { + // hovering over `extract` should strip `std::option` from parameter type + // `std` from the (qualified) function name + let elem: u64 = std::option::extract(&mut opt); + // hovering over `singleton` should strip `std` from the (qualified) + // function name + std::vector::singleton(elem) + } + } diff --git a/external-crates/move/crates/move-analyzer/tests/symbols/sources/M6.move b/external-crates/move/crates/move-analyzer/tests/symbols/sources/M6.move index 1b60cf1a8971a..0dd75702da8ef 100644 --- a/external-crates/move/crates/move-analyzer/tests/symbols/sources/M6.move +++ b/external-crates/move/crates/move-analyzer/tests/symbols/sources/M6.move @@ -2,7 +2,7 @@ module Symbols::M6 { /// This is a documented struct /// With a multi-line docstring - struct DocumentedStruct has key, drop, store { + public struct DocumentedStruct has drop, store { /// A documented field documented_field: u64, } @@ -45,4 +45,47 @@ module Symbols::M6 { param } + /// A documented function with code block + /// (should preserve indentation in the code block) + /// + /// ```rust + /// fun foo() { + /// 42 + /// } + /// ``` + fun code_block_doc_slash() {} + + /** + A documented function with code block + (should preserve indentation in the code block) + + ```rust + fun foo() { + 42 + } + ``` + */ + fun code_block_doc_star() {} + + /** + Misformatted docstring to have fewer whitespace in the body than + at the ending marker. + + + Beginning of this string should not disappear. + +Beginning of this string should not disappear either. + + */ + fun misformatted_docstring() {} + + + /// Docstring before attributes + #[test_only] + fun attributes_after_docstring() {} + + #[test_only] + /// Docstring after attributes + fun attributes_before_docstring() {} + } diff --git a/external-crates/move/crates/move-analyzer/tests/symbols/sources/M7.move b/external-crates/move/crates/move-analyzer/tests/symbols/sources/M7.move index b4165da101290..2eba2d7eabae9 100644 --- a/external-crates/move/crates/move-analyzer/tests/symbols/sources/M7.move +++ b/external-crates/move/crates/move-analyzer/tests/symbols/sources/M7.move @@ -1,7 +1,7 @@ module Symbols::M7 { /// Documented struct in another module - struct OtherDocStruct has drop { + public struct OtherDocStruct has drop { /// Documented field in another module some_field: u64, } diff --git a/external-crates/move/crates/move-analyzer/tests/symbols/sources/M8.move b/external-crates/move/crates/move-analyzer/tests/symbols/sources/M8.move index 49b1aefebf1f4..b07af0f362635 100644 --- a/external-crates/move/crates/move-analyzer/tests/symbols/sources/M8.move +++ b/external-crates/move/crates/move-analyzer/tests/symbols/sources/M8.move @@ -29,4 +29,12 @@ module Symbols::M8 { const EQUAL: bool = 1 == 1; const ANOTHER_USE_CONST: bool = Symbols::M8::EQUAL == false; + + #[error] + const ERROR_CONST: u64 = 42; + + public fun clever_assert() { + assert!(ERROR_CONST == 42, ERROR_CONST); + } + } diff --git a/external-crates/move/crates/move-analyzer/tests/symbols/sources/M9.move b/external-crates/move/crates/move-analyzer/tests/symbols/sources/M9.move index 39ee5c5c95742..680f22f18e14d 100644 --- a/external-crates/move/crates/move-analyzer/tests/symbols/sources/M9.move +++ b/external-crates/move/crates/move-analyzer/tests/symbols/sources/M9.move @@ -12,7 +12,7 @@ module Symbols::M9 { const SOME_CONST: u64 = 42; - struct SomeStruct has drop, store { + public struct SomeStruct has drop, store { some_field: u64, } diff --git a/external-crates/move/crates/move-analyzer/trace-adapter/src/adapter.ts b/external-crates/move/crates/move-analyzer/trace-adapter/src/adapter.ts index 0d3193eb6e610..311155f5e2874 100644 --- a/external-crates/move/crates/move-analyzer/trace-adapter/src/adapter.ts +++ b/external-crates/move/crates/move-analyzer/trace-adapter/src/adapter.ts @@ -13,7 +13,14 @@ import { } from '@vscode/debugadapter'; import { DebugProtocol } from '@vscode/debugprotocol'; import * as path from 'path'; -import { Runtime, RuntimeEvents, IRuntimeVariableScope } from './runtime'; +import { + Runtime, + RuntimeEvents, + RuntimeValueType, + IRuntimeVariableScope, + CompoundType +} from './runtime'; +import { run } from 'node:test'; const enum LogLevel { Log = 'log', @@ -80,19 +87,16 @@ export class MoveDebugSession extends LoggingDebugSession { private runtime: Runtime; /** - * Handles to create variable scopes - * (ideally we would use numbers but DAP package does not like it) - * + * Handles to create variable scopes and compound variable values. */ - private variableHandles: Handles; - + private variableHandles: Handles; public constructor() { super(); this.setDebuggerLinesStartAt1(false); this.setDebuggerColumnsStartAt1(false); this.runtime = new Runtime(); - this.variableHandles = new Handles(); + this.variableHandles = new Handles(); // setup event handlers @@ -236,16 +240,17 @@ export class MoveDebugSession extends LoggingDebugSession { } const scopes: DebugProtocol.Scope[] = []; if (frame.locals.length > 0) { - const localScopeReference = this.variableHandles.create({ locals: frame.locals[0] }); - const localScope = new Scope(`locals: ${frame.name}`, localScopeReference, false); - scopes.push(localScope); - // TODO: finish shadowed variables support - for (let i = 1; i < frame.locals.length; i++) { + for (let i = frame.locals.length - 1; i > 0; i--) { const shadowedScopeReference = this.variableHandles.create({ locals: frame.locals[i] }); const shadowedScope = new Scope(`shadowed(${i}): ${frame.name}`, shadowedScopeReference, false); scopes.push(shadowedScope); } } + // don't have to check if scope 0 exists as it's created whenever a new frame is created + // and it's never disposed of + const localScopeReference = this.variableHandles.create({ locals: frame.locals[0] }); + const localScope = new Scope(`locals: ${frame.name}`, localScopeReference, false); + scopes.push(localScope); return scopes; } @@ -267,27 +272,66 @@ export class MoveDebugSession extends LoggingDebugSession { this.sendResponse(response); } + /** + * Converts a runtime value to a DAP variable. + * + * @param value variable value + * @param name variable name + * @param type optional variable type + * @returns a DAP variable. + */ + private convertRuntimeValue( + value: RuntimeValueType, + name: string, + type?: string + ): DebugProtocol.Variable { + if (typeof value === 'string') { + return { + name, + type, + value, + variablesReference: 0 + }; + } else if (Array.isArray(value)) { + const compoundValueReference = this.variableHandles.create(value); + return { + name, + type, + value: '(' + value.length + ')[...]', + variablesReference: compoundValueReference + }; + } else { + const compoundValueReference = this.variableHandles.create(value); + const accessChainParts = value.type.split('::'); + const datatypeName = accessChainParts[accessChainParts.length - 1]; + return { + name, + type: value.variantName + ? value.type + '::' + value.variantName + : value.type, + value: (value.variantName + ? datatypeName + '::' + value.variantName + : datatypeName + ) + '{...}', + variablesReference: compoundValueReference + }; + } + } + /** * Converts runtime variables to DAP variables. * * @param runtimeScope runtime variables scope, - * @returns an array of variables. + * @returns an array of DAP variables. */ private convertRuntimeVariables(runtimeScope: IRuntimeVariableScope): DebugProtocol.Variable[] { const variables: DebugProtocol.Variable[] = []; const runtimeVariables = runtimeScope.locals; - for (let i = 0; i < runtimeVariables.length; i++) { - const v = runtimeVariables[i]; + runtimeVariables.forEach(v => { if (v) { - variables.push({ - name: v.name, - type: v.type, - value: v.value, - variablesReference: 0 - }); + variables.push(this.convertRuntimeValue(v.value, v.name, v.type)); } - } - + }); return variables; } @@ -295,13 +339,27 @@ export class MoveDebugSession extends LoggingDebugSession { response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments ): void { - const handle = this.variableHandles.get(args.variablesReference); - if (!handle) { - this.sendResponse(response); - return; - } try { - const variables = this.convertRuntimeVariables(handle); + const variableHandle = this.variableHandles.get(args.variablesReference); + let variables: DebugProtocol.Variable[] = []; + if (variableHandle) { + if ('locals' in variableHandle) { + // we are dealing with a sccope + variables = this.convertRuntimeVariables(variableHandle); + } else { + // we are dealing with a compound value + if (Array.isArray(variableHandle)) { + for (let i = 0; i < variableHandle.length; i++) { + const v = variableHandle[i]; + variables.push(this.convertRuntimeValue(v, String(i))); + } + } else { + variableHandle.fields.forEach(([fname, fvalue]) => { + variables.push(this.convertRuntimeValue(fvalue, fname)); + }); + } + } + } if (variables.length > 0) { response.body = { variables @@ -311,7 +369,6 @@ export class MoveDebugSession extends LoggingDebugSession { response.success = false; response.message = err instanceof Error ? err.message : String(err); } - this.sendResponse(response); } @@ -322,11 +379,7 @@ export class MoveDebugSession extends LoggingDebugSession { ): void { let terminate = false; try { - terminate = this.runtime.step( - /* next */ true, - /* stopAtCloseFrame */ false, - /* nextLineSkip */ true - ); + terminate = this.runtime.step(/* next */ true, /* stopAtCloseFrame */ false); } catch (err) { response.success = false; response.message = err instanceof Error ? err.message : String(err); @@ -343,11 +396,7 @@ export class MoveDebugSession extends LoggingDebugSession { ): void { let terminate = false; try { - terminate = this.runtime.step( - /* next */ false, - /* stopAtCloseFrame */ false, - /* nextLineSkip */ true - ); + terminate = this.runtime.step(/* next */ false, /* stopAtCloseFrame */ false); } catch (err) { response.success = false; response.message = err instanceof Error ? err.message : String(err); @@ -363,7 +412,7 @@ export class MoveDebugSession extends LoggingDebugSession { _args: DebugProtocol.StepOutArguments ): void { try { - const steppedOut = this.runtime.stepOut(); + const steppedOut = this.runtime.stepOut(/* next */ false); if (!steppedOut) { logger.log(`Cannot step out`); } @@ -374,46 +423,13 @@ export class MoveDebugSession extends LoggingDebugSession { this.sendResponse(response); } - protected stepBackRequest( - response: DebugProtocol.StepBackResponse, - _args: DebugProtocol.StepBackArguments - ): void { - try { - const steppedBack = this.runtime.stepBack(); - if (!steppedBack) { - logger.log(`Cannot step back`); - } - } catch (err) { - response.success = false; - response.message = err instanceof Error ? err.message : String(err); - } - this.sendResponse(response); - } - protected continueRequest( response: DebugProtocol.ContinueResponse, _args: DebugProtocol.ContinueArguments ): void { let terminate = false; try { - terminate = this.runtime.continue(/* reverse */ false); - } catch (err) { - response.success = false; - response.message = err instanceof Error ? err.message : String(err); - } - if (terminate) { - this.sendEvent(new TerminatedEvent()); - } - this.sendResponse(response); - } - - protected reverseContinueRequest( - response: DebugProtocol.ReverseContinueResponse, - _args: DebugProtocol.ReverseContinueArguments - ): void { - let terminate = false; - try { - terminate = this.runtime.continue(/* reverse */ true); + terminate = this.runtime.continue(); } catch (err) { response.success = false; response.message = err instanceof Error ? err.message : String(err); diff --git a/external-crates/move/crates/move-analyzer/trace-adapter/src/runtime.ts b/external-crates/move/crates/move-analyzer/trace-adapter/src/runtime.ts index bc7204a898f15..233e561c3fa41 100644 --- a/external-crates/move/crates/move-analyzer/trace-adapter/src/runtime.ts +++ b/external-crates/move/crates/move-analyzer/trace-adapter/src/runtime.ts @@ -7,7 +7,7 @@ import * as fs from 'fs'; import * as path from 'path'; import toml from 'toml'; import { ISourceMap, IFileInfo, readAllSourceMaps } from './source_map_utils'; -import { TraceEffectKind, TraceEvent, TraceEventKind, TraceLocKind, TraceValKind, TraceValue, readTrace } from './trace_utils'; +import { TraceEffectKind, TraceEvent, TraceEventKind, TraceInstructionKind, TraceLocKind, TraceValKind, TraceValue, readTrace } from './trace_utils'; import { ModuleInfo } from './utils'; /** @@ -18,12 +18,33 @@ export interface IRuntimeVariableScope { locals: (IRuntimeVariable | undefined)[]; } +/** + * A compound type: + * - a vector (converted to an array of values) + * - a struct/enum (converted to an array of string/field value pairs) + */ +export type CompoundType = RuntimeValueType[] | IRuntimeCompundValue; + +/** + * A runtime value can have any of the following types: + * - boolean, number, string (converted to string) + * - compound type (vector, struct, enum) + */ +export type RuntimeValueType = string | CompoundType; + +export interface IRuntimeCompundValue { + fields: [string, RuntimeValueType][]; + type: string; + variantName?: string; + variantTag?: number; +} + /** * Describes a runtime local variable. */ interface IRuntimeVariable { name: string; - value: string; + value: RuntimeValueType; type: string; } @@ -47,6 +68,11 @@ interface IRuntimeStackFrame { // Local variables per scope (local scope at 0 and then following block scopes), // indexed by variable frame index. locals: (IRuntimeVariable | undefined)[][]; + /** + * Line of the last call instruction that was processed in this frame. + * It's needed to make sure that step/next into/over call works correctly. + */ + lastCallInstructionLine: number | undefined; } /** @@ -72,19 +98,29 @@ export enum RuntimeEvents { */ export class Runtime extends EventEmitter { - // Trace being viewed. + /** + * Trace being viewed. + */ private trace = { events: [] as TraceEvent[], localLifetimeEnds: new Map() }; - // Index of the current trace event being processed. + /** + * Index of the current trace event being processed. + */ private eventIndex = 0; - // Current frame stack. + /** + * Current frame stack. + */ private frameStack = { frames: [] as IRuntimeStackFrame[] }; - // Map of file hashes to file info. + /** + * Map of file hashes to file info. + */ private filesMap = new Map(); - // Map of stringified module info to source maps. + /** + * Map of stringified module info to source maps. + */ private sourceMapsMap = new Map(); /** @@ -139,7 +175,7 @@ export class Runtime extends EventEmitter { this.frameStack = { frames: [newFrame] }; - this.step(/* next */ false, /* stopAtCloseFrame */ false, /* nextLineSkip */ true); + this.step(/* next */ false, /* stopAtCloseFrame */ false); } /** @@ -160,11 +196,7 @@ export class Runtime extends EventEmitter { * @returns `true` if the trace viewing session is finished, `false` otherwise. * @throws Error with a descriptive error message if the step event cannot be handled. */ - public step( - next: boolean, - stopAtCloseFrame: boolean, - nextLineSkip: boolean - ): boolean { + public step(next: boolean, stopAtCloseFrame: boolean): boolean { this.eventIndex++; if (this.eventIndex >= this.trace.events.length) { this.sendEvent(RuntimeEvents.stopOnStep); @@ -172,9 +204,85 @@ export class Runtime extends EventEmitter { } let currentEvent = this.trace.events[this.eventIndex]; if (currentEvent.type === TraceEventKind.Instruction) { - let sameLine = this.instruction(currentEvent); - if (sameLine && nextLineSkip) { - return this.step(next, stopAtCloseFrame, nextLineSkip); + const stackHeight = this.frameStack.frames.length; + if (stackHeight <= 0) { + throw new Error('No frame on the stack when processing Instruction event at PC: ' + + currentEvent.pc); + } + const currentFrame = this.frameStack.frames[stackHeight - 1]; + // remember last call instruction line before it (potentially) changes + // in the `instruction` call below + const lastCallInstructionLine = currentFrame.lastCallInstructionLine; + let [sameLine, currentLine] = this.instruction(currentFrame, currentEvent); + if (sameLine) { + if (!next && (currentEvent.kind === TraceInstructionKind.CALL + || currentEvent.kind === TraceInstructionKind.CALL_GENERIC) + && lastCallInstructionLine === currentLine) { + // We are about to step into another call on the same line + // but we should wait for user action to do so rather than + // having debugger step into it automatically. If we don't + // the user will observe a weird effect. For example, + // consider the following code: + // ``` + // foo(); + // assert(bar() == baz()); + // ``` + // In the code above, after executing `foo()`, the user + // will move to the next line and will expect to only + // step into `bar` rather than having debugger to step + // immediately into `baz` as well. At the same time, + // if the user intended to step over functions using `next`, + // we shuld skip over all calls on the same line (both `bar` + // and `baz` in the example above). + // + // The following explains a bit more formally what needs + // to happen both on on `next` and `step` actions when + // call and non-call instructions are interleaved: + // + // When `step` is called: + // + // When there is only one call on the same line, we want to + // stop on the first instruction of this line, then after + // user `step` action enter the call, and then after + // exiting the call go to the instruction on the next line: + // 6: instruction + // 7: instruction // stop here + // 7: call // enter call here + // 7: instruction + // 8: instruction // stop here + // + // When there is more than one call on the same line, we + // want to stop on the first instruction of this line, + // then after user `step` action enter the call, then + // after exiting the call stop on the next call instruction + // and waitl for another `step` action from the user: + // 6: instruction + // 7: instruction // stop here + // 7: call // enter call here + // 7: instruction + // 7: call // stop and then enter call here + // 7: instruction + // 8: instruction // stop here + // + // When `next` is called, things have to happen differently, + // particularly when there are multiple calls on the same line: + // 6: instruction + // 7: instruction // stop here + // 7: call + // 7: instruction + // 7: call + // 7: instruction + // 8: instruction // stop here + // + // To support this, we need to keep track of the line number when + // the last call instruction in a give frame happened, and + // also we need to make `stepOut` aware of whether it is executed + // as part of `next` (which is how `next` is implemented) or not. + this.sendEvent(RuntimeEvents.stopOnStep); + return false; + } else { + return this.step(next, stopAtCloseFrame); + } } this.sendEvent(RuntimeEvents.stopOnStep); return false; @@ -195,10 +303,10 @@ export class Runtime extends EventEmitter { if (next) { // step out of the frame right away - this.stepOut(); + this.stepOut(next); return false; } else { - return this.step(next, stopAtCloseFrame, nextLineSkip); + return this.step(next, stopAtCloseFrame); } } else if (currentEvent.type === TraceEventKind.CloseFrame) { if (stopAtCloseFrame) { @@ -212,7 +320,7 @@ export class Runtime extends EventEmitter { + currentEvent.id); } this.frameStack.frames.pop(); - return this.step(next, stopAtCloseFrame, nextLineSkip); + return this.step(next, stopAtCloseFrame); } } else if (currentEvent.type === TraceEventKind.Effect) { const effect = currentEvent.effect; @@ -228,20 +336,21 @@ export class Runtime extends EventEmitter { localWrite(currentFrame, traceLocation.localIndex, traceValue); } } - return this.step(next, stopAtCloseFrame, nextLineSkip); + return this.step(next, stopAtCloseFrame); } else { // ignore other events - return this.step(next, stopAtCloseFrame, nextLineSkip); + return this.step(next, stopAtCloseFrame); } } /** * Handles "step out" adapter action. * + * @param next determines if it's part of `next` (or otherwise `step`) action. * @returns `true` if was able to step out of the frame, `false` otherwise. * @throws Error with a descriptive error message if the step out event cannot be handled. */ - public stepOut(): boolean { + public stepOut(next: boolean): boolean { const stackHeight = this.frameStack.frames.length; if (stackHeight <= 1) { // do nothing as there is no frame to step out to @@ -254,7 +363,11 @@ export class Runtime extends EventEmitter { // skip all events until the corresponding CloseFrame event, // pop the top frame from the stack, and proceed to the next event while (true) { - if (this.step(/* next */ false, /* stopAtCloseFrame */ true, /* nextLineSkip */ true)) { + // when calling `step` in the loop below, we need to avoid + // skipping over calls next-style otherwise we can miss seeing + // the actual close frame event that we are looking for + // and have the loop execute too far + if (this.step(/* next */ false, /* stopAtCloseFrame */ true)) { // trace viewing session finished throw new Error('Cannot find corresponding CloseFrame event for function: ' + currentFrame.name); @@ -270,103 +383,7 @@ export class Runtime extends EventEmitter { } } } - - // Do not skip to same line when stepping out as this may lead - // to unusual behavior if multiple bytcode instructions are on the same line. - // For example, consider the following code: - // ``` - // assert(foo() == bar()); - // ``` - // In the code above if we enter `foo` and then step out of it, - // we want to end up on the same line (where the next instruction is) - // but we don't want to call `bar` in the same debugging step. - return this.step(/* next */ false, /* stopAtCloseFrame */ false, /* nextLineSkip */ false); - } - /** - * Handles "step back" adapter action. - * @returns `true` if was able to step back, `false` otherwise. - * @throws Error with a descriptive error message if the step back event cannot be handled. - */ - public stepBack(): boolean { - if (this.eventIndex <= 1) { - // no where to step back to (event 0 is the `OpenFrame` event for the first frame) - // and is processed in runtime.start() which is executed only once - this.sendEvent(RuntimeEvents.stopOnStep); - return false; - } - let currentEvent = this.trace.events[this.eventIndex - 1]; - if (currentEvent.type === TraceEventKind.CloseFrame) { - // cannot step back into or over function calls - this.sendEvent(RuntimeEvents.stopOnStep); - return false; - } else { - this.eventIndex--; - if (currentEvent.type === TraceEventKind.Instruction) { - let sameLine = this.instruction(currentEvent); - if (sameLine) { - this.stepBack(); - return true; - } - this.sendEvent(RuntimeEvents.stopOnStep); - return true; - } else if (currentEvent.type === TraceEventKind.OpenFrame) { - const stackHeight = this.frameStack.frames.length; - if (stackHeight <= 0) { - // should never happen but better to signal than crash - throw new Error('Error stepping back to caller function ' - + currentEvent.name - + ' as there is no frame on the stack' - ); - } - if (stackHeight <= 1) { - // should never happen as we never step back out of the outermost function - // (never step back to event 0 as per first conditional in this function) - throw new Error('Error stepping back to caller function ' - + currentEvent.name - + ' from callee ' - + this.frameStack.frames[stackHeight - 1].name - + ' as there would be no frame on the stack afterwards' - ); - } - // pop the top frame from the stack - this.frameStack.frames.pop(); - // cannot simply call stepBack as we are stepping back to the same line - // that is now in the current frame, which would result in unintentionally - // recursing to previous events - if (this.eventIndex <= 1) { - // no where to step back to - this.sendEvent(RuntimeEvents.stopOnStep); - return true; // we actually stepped back just can't step back further - } - this.eventIndex--; - let prevCurrentEvent = this.trace.events[this.eventIndex]; - if (prevCurrentEvent.type !== TraceEventKind.Instruction) { - throw new Error('Expected an Instruction event before OpenFrame event in function' - + currentEvent.name - ); - } - if (!this.instruction(prevCurrentEvent)) { - // we should be steppping back to the instruction on the same line - // as the one in the current frame - throw new Error('Wrong line of an instruction (at PC ' + prevCurrentEvent.pc + ')' - + ' in the caller function ' - + currentEvent.name - + ' to step back to from callee ' - + this.frameStack.frames[stackHeight - 1].name - + ' as there would be no frame on the stack afterwards' - ); - } - this.sendEvent(RuntimeEvents.stopOnStep); - return true; - } else if (currentEvent.type === TraceEventKind.Effect) { - // TODO: implement reverting writes when stepping back - return this.stepBack(); - } else { - // ignore other events - this.stepBack(); - return true; - } - } + return this.step(next, /* stopAtCloseFrame */ false); } /** @@ -374,25 +391,12 @@ export class Runtime extends EventEmitter { * @returns `true` if the trace viewing session is finished, `false` otherwise. * @throws Error with a descriptive error message if the continue event cannot be handled. */ - public continue(reverse: boolean): boolean { - if (reverse) { - while (true) { - if (!this.stepBack()) { - return false; - } - } - } else { - while (true) { - if (this.step( - /* next */ false, - /* stopAtCloseFrame */ false, - /* nextLineSkip */ true) - ) { - return true; - } + public continue(): boolean { + while (true) { + if (this.step(/* next */ false, /* stopAtCloseFrame */ false)) { + return true; } } - } /** @@ -403,14 +407,10 @@ export class Runtime extends EventEmitter { * `false` otherwise (so that instructions on the same line can be skipped). * @throws Error with a descriptive error message if instruction event cannot be handled. */ - private instruction(instructionEvent: Extract): boolean { - const stackHeight = this.frameStack.frames.length; - if (stackHeight <= 0) { - throw new Error('No frame on the stack when processing Instruction event at PC: ' - + instructionEvent.pc); - } - // newest frame is at the top of the stack - const currentFrame = this.frameStack.frames[stackHeight - 1]; + private instruction( + currentFrame: IRuntimeStackFrame, + instructionEvent: Extract + ): [boolean, number] { const currentFun = currentFrame.sourceMap.functions.get(currentFrame.name); if (!currentFun) { throw new Error(`Cannot find function: ${currentFrame.name} in source map`); @@ -433,21 +433,36 @@ export class Runtime extends EventEmitter { // local variable array const frameLocalLifetimeEnds = this.trace.localLifetimeEnds.get(currentFrame.id); if (frameLocalLifetimeEnds) { - for (let i = 0; i < currentFrame.locals.length; i++) { + const localsLength = currentFrame.locals.length; + for (let i = 0; i < localsLength; i++) { for (let j = 0; j < currentFrame.locals[i].length; j++) { if (frameLocalLifetimeEnds[j] === instructionEvent.pc) { currentFrame.locals[i][j] = undefined; } } } + // trim shadowed scopes that have no live variables in them + for (let i = localsLength - 1; i > 0; i--) { + const liveVar = currentFrame.locals[i].find(runtimeVar => { + return runtimeVar !== undefined; + }); + if (!liveVar) { + currentFrame.locals.pop(); + } + } + } + + if (instructionEvent.kind === TraceInstructionKind.CALL || + instructionEvent.kind === TraceInstructionKind.CALL_GENERIC) { + currentFrame.lastCallInstructionLine = currentPCLoc.line; } if (currentPCLoc.line === currentFrame.line) { // so that instructions on the same line can be bypassed - return true; + return [true, currentPCLoc.line]; } else { currentFrame.line = currentPCLoc.line; - return false; + return [false, currentPCLoc.line]; } } @@ -492,7 +507,8 @@ export class Runtime extends EventEmitter { file: currentFile.path, line: 0, // line will be updated when next event (Instruction) is processed localsTypes, - locals + locals, + lastCallInstructionLine: undefined, }; if (this.trace.events.length <= this.eventIndex + 1 || @@ -555,9 +571,36 @@ function localWrite( + ' in function: ' + currentFrame.name); } - // TODO: if a variable has the same name but a different index (it is shadowed) - // it has to be put in a different scope (e.g., locals[1], locals[2], etc.) - currentFrame.locals[0][localIndex] = { name, value, type }; + + const scopesCount = currentFrame.locals.length; + if (scopesCount <= 0) { + throw new Error("There should be at least one variable scope in functon" + + currentFrame.name); + } + // If a variable has the same name but a different index (it is shadowed) + // it has to be put in a different scope (e.g., locals[1], locals[2], etc.). + // Find scope already containing variable name, if any, starting from + // the outermost one + let existingVarScope = -1; + for (let i = scopesCount - 1; i >= 0; i--) { + const existingVarIndex = currentFrame.locals[i].findIndex(runtimeVar => { + return runtimeVar && runtimeVar.name === name; + }); + if (existingVarIndex !== -1 && existingVarIndex !== localIndex) { + existingVarScope = i; + break; + } + } + if (existingVarScope >= 0) { + const shadowedScope = currentFrame.locals[existingVarScope + 1]; + if (!shadowedScope) { + currentFrame.locals.push([]); + } + currentFrame.locals[existingVarScope + 1][localIndex] = { name, value, type }; + } else { + // put variable in the "main" locals scope + currentFrame.locals[0][localIndex] = { name, value, type }; + } } /** diff --git a/external-crates/move/crates/move-analyzer/trace-adapter/src/source_map_utils.ts b/external-crates/move/crates/move-analyzer/trace-adapter/src/source_map_utils.ts index 978f28e81a444..23edbd8424175 100644 --- a/external-crates/move/crates/move-analyzer/trace-adapter/src/source_map_utils.ts +++ b/external-crates/move/crates/move-analyzer/trace-adapter/src/source_map_utils.ts @@ -7,40 +7,40 @@ import { ModuleInfo } from './utils'; // Data types corresponding to source map file JSON schema. -interface ISrcDefinitionLocation { +interface JSONSrcDefinitionLocation { file_hash: number[]; start: number; end: number; } -interface ISrcStructSourceMapEntry { - definition_location: ISrcDefinitionLocation; - type_parameters: [string, ISrcDefinitionLocation][]; - fields: ISrcDefinitionLocation[]; +interface JSONSrcStructSourceMapEntry { + definition_location: JSONSrcDefinitionLocation; + type_parameters: [string, JSONSrcDefinitionLocation][]; + fields: JSONSrcDefinitionLocation[]; } -interface ISrcEnumSourceMapEntry { - definition_location: ISrcDefinitionLocation; - type_parameters: [string, ISrcDefinitionLocation][]; - variants: [[string, ISrcDefinitionLocation], ISrcDefinitionLocation[]][]; +interface JSONSrcEnumSourceMapEntry { + definition_location: JSONSrcDefinitionLocation; + type_parameters: [string, JSONSrcDefinitionLocation][]; + variants: [[string, JSONSrcDefinitionLocation], JSONSrcDefinitionLocation[]][]; } -interface ISrcFunctionMapEntry { - definition_location: ISrcDefinitionLocation; - type_parameters: [string, ISrcDefinitionLocation][]; - parameters: [string, ISrcDefinitionLocation][]; - locals: [string, ISrcDefinitionLocation][]; +interface JSONSrcFunctionMapEntry { + definition_location: JSONSrcDefinitionLocation; + type_parameters: [string, JSONSrcDefinitionLocation][]; + parameters: [string, JSONSrcDefinitionLocation][]; + locals: [string, JSONSrcDefinitionLocation][]; nops: Record; - code_map: Record; + code_map: Record; is_native: boolean; } -interface ISrcRootObject { - definition_location: ISrcDefinitionLocation; +interface JSONSrcRootObject { + definition_location: JSONSrcDefinitionLocation; module_name: string[]; - struct_map: Record; - enum_map: Record; - function_map: Record; + struct_map: Record; + enum_map: Record; + function_map: Record; constant_map: Record; } @@ -126,7 +126,7 @@ export function readAllSourceMaps( * @throws Error if with a descriptive error message if the source map cannot be read. */ function readSourceMap(sourceMapPath: string, filesMap: Map): ISourceMap { - const sourceMapJSON: ISrcRootObject = JSON.parse(fs.readFileSync(sourceMapPath, 'utf8')); + const sourceMapJSON: JSONSrcRootObject = JSON.parse(fs.readFileSync(sourceMapPath, 'utf8')); const fileHash = Buffer.from(sourceMapJSON.definition_location.file_hash).toString('base64'); const modInfo: ModuleInfo = { @@ -212,7 +212,7 @@ function readSourceMap(sourceMapPath: string, filesMap: Map): * @param sourceMapLines */ function prePopulateSourceMapLines( - sourceMapJSON: ISrcRootObject, + sourceMapJSON: JSONSrcRootObject, fileInfo: IFileInfo, sourceMapLines: Set ): void { @@ -265,7 +265,7 @@ function prePopulateSourceMapLines( * @param sourceMapLines set of source file lines. */ function addLinesForLocation( - loc: ISrcDefinitionLocation, + loc: JSONSrcDefinitionLocation, fileInfo: IFileInfo, sourceMapLines: Set ): void { diff --git a/external-crates/move/crates/move-analyzer/trace-adapter/src/trace_utils.ts b/external-crates/move/crates/move-analyzer/trace-adapter/src/trace_utils.ts index 3f9e576210600..b4f48e9d9b25a 100644 --- a/external-crates/move/crates/move-analyzer/trace-adapter/src/trace_utils.ts +++ b/external-crates/move/crates/move-analyzer/trace-adapter/src/trace_utils.ts @@ -3,117 +3,172 @@ import * as fs from 'fs'; import { FRAME_LIFETIME, ModuleInfo } from './utils'; +import { IRuntimeCompundValue, RuntimeValueType } from './runtime'; + // Data types corresponding to trace file JSON schema. -interface ITraceModule { +interface JSONTraceModule { + address: string; + name: string; +} + +interface JSONStructTypeDescription { address: string; + module: string; name: string; + type_args: string[]; +} + +interface JSONStructType { + struct: JSONStructTypeDescription; +} + +interface JSONVectorType { + vector: JSONBaseType; } -interface ITraceType { +type JSONBaseType = string | JSONStructType | JSONVectorType; + +interface JSONTraceType { ref_type: string | null; - type_: string; + type_: JSONBaseType; +} + +type JSONTraceValueType = boolean | number | string | JSONTraceValueType[] | JSONTraceCompound; + +interface JSONTraceFields { + [key: string]: JSONTraceValueType; +} + +interface JSONTraceCompound { + fields: JSONTraceFields; + type: string; + variant_name?: string; + variant_tag?: number; } -interface ITraceRuntimeValue { - value: any; +interface JSONTraceRuntimeValue { + value: JSONTraceValueType; } -interface ITraceValue { - RuntimeValue: ITraceRuntimeValue; +interface JSONTraceValue { + RuntimeValue: JSONTraceRuntimeValue; } -interface ITraceFrame { +interface JSONTraceFrame { binary_member_index: number; frame_id: number; function_name: string; is_native: boolean; - locals_types: ITraceType[]; - module: ITraceModule; - parameters: ITraceValue[]; - return_types: ITraceType[]; + locals_types: JSONTraceType[]; + module: JSONTraceModule; + parameters: JSONTraceValue[]; + return_types: JSONTraceType[]; type_instantiation: string[]; } -interface ITraceOpenFrame { - frame: ITraceFrame; +interface JSONTraceOpenFrame { + frame: JSONTraceFrame; gas_left: number; } -interface ITraceInstruction { +interface JSONTraceInstruction { gas_left: number; instruction: string; pc: number; type_parameters: any[]; } -interface ITraceLocation { +interface JSONTraceLocalLocation { Local: [number, number]; } -interface ITraceWriteEffect { - location: ITraceLocation; - root_value_after_write: ITraceValue; +interface JSONTraceIndexedLocation { + Indexed: [JSONTraceLocalLocation, number]; +} + +type JSONTraceLocation = JSONTraceLocalLocation | JSONTraceIndexedLocation; + +interface JSONTraceWriteEffect { + location: JSONTraceLocation; + root_value_after_write: JSONTraceValue; } -interface ITraceReadEffect { - location: ITraceLocation; +interface JSONTraceReadEffect { + location: JSONTraceLocation; moved: boolean; - root_value_read: ITraceValue; + root_value_read: JSONTraceValue; } -interface ITracePushEffect { - RuntimeValue?: ITraceRuntimeValue; +interface JSONTracePushEffect { + RuntimeValue?: JSONTraceRuntimeValue; MutRef?: { - location: ITraceLocation; + location: JSONTraceLocation; snapshot: any[]; }; } -interface ITracePopEffect { - RuntimeValue?: ITraceRuntimeValue; +interface JSONTracePopEffect { + RuntimeValue?: JSONTraceRuntimeValue; MutRef?: { - location: ITraceLocation; + location: JSONTraceLocation; snapshot: any[]; }; } -interface ITraceEffect { - Push?: ITracePushEffect; - Pop?: ITracePopEffect; - Write?: ITraceWriteEffect; - Read?: ITraceReadEffect; +interface JSONTraceEffect { + Push?: JSONTracePushEffect; + Pop?: JSONTracePopEffect; + Write?: JSONTraceWriteEffect; + Read?: JSONTraceReadEffect; } -interface ITraceCloseFrame { +interface JSONTraceCloseFrame { frame_id: number; gas_left: number; - return_: ITraceRuntimeValue[]; + return_: JSONTraceRuntimeValue[]; } -interface ITraceEvent { - OpenFrame?: ITraceOpenFrame; - Instruction?: ITraceInstruction; - Effect?: ITraceEffect; - CloseFrame?: ITraceCloseFrame; +interface JSONTraceEvent { + OpenFrame?: JSONTraceOpenFrame; + Instruction?: JSONTraceInstruction; + Effect?: JSONTraceEffect; + CloseFrame?: JSONTraceCloseFrame; } -interface ITraceRootObject { - events: ITraceEvent[]; +interface JSONTraceRootObject { + events: JSONTraceEvent[]; version: number; } // Runtime data types. +/** + * Kind of instruction in the trace. Enum member names correspond to instruction names. + * (other than UNKNOWN which is used for instructions whose kind does not matter). + */ +export enum TraceInstructionKind { + /** + * Call instruction. + */ + CALL, + /** + * Generic call instruction. + */ + CALL_GENERIC, + // for now we don't care about other kinds of instructions + UNKNOWN +} + /** * Kind of a trace event. */ export enum TraceEventKind { - OpenFrame = 'OpenFrame', - CloseFrame = 'CloseFrame', - Instruction = 'Instruction', - Effect = 'Effect' + OpenFrame, + CloseFrame, + Instruction, + Effect } /** @@ -129,7 +184,7 @@ export type TraceEvent = paramValues: TraceValue[] } | { type: TraceEventKind.CloseFrame, id: number } - | { type: TraceEventKind.Instruction, pc: number } + | { type: TraceEventKind.Instruction, pc: number, kind: TraceInstructionKind } | { type: TraceEventKind.Effect, effect: EventEffect }; /** @@ -158,7 +213,7 @@ export enum TraceValKind { * Value in the trace. */ export type TraceValue = - | { type: TraceValKind.Runtime, value: string }; + | { type: TraceValKind.Runtime, value: RuntimeValueType }; /** * Kind of an effect of an instruction. @@ -188,7 +243,6 @@ interface ITrace { localLifetimeEnds: Map; } - /** * Reads a Move VM execution trace from a JSON file. * @@ -196,7 +250,7 @@ interface ITrace { * @returns execution trace. */ export function readTrace(traceFilePath: string): ITrace { - const traceJSON: ITraceRootObject = JSON.parse(fs.readFileSync(traceFilePath, 'utf8')); + const traceJSON: JSONTraceRootObject = JSON.parse(fs.readFileSync(traceFilePath, 'utf8')); const events: TraceEvent[] = []; // We compute the end of lifetime for a local variable as follows. // When a given local variable is read or written in an effect, we set the end of its lifetime @@ -224,7 +278,7 @@ export function readTrace(traceFilePath: string): ITrace { const localsTypes = []; const frame = event.OpenFrame.frame; for (const type of frame.locals_types) { - localsTypes.push(type.type_); + localsTypes.push(JSONTraceTypeToString(type.type_)); } // process parameters - store their values in trace and set their // initial lifetimes @@ -234,7 +288,10 @@ export function readTrace(traceFilePath: string): ITrace { const value = frame.parameters[i]; if (value) { const runtimeValue: TraceValue = - { type: TraceValKind.Runtime, value: JSON.stringify(value.RuntimeValue.value) }; + { + type: TraceValKind.Runtime, + value: traceValueFromJSON(value.RuntimeValue.value) + }; paramValues.push(runtimeValue); lifetimeEnds[i] = FRAME_LIFETIME; } @@ -259,9 +316,13 @@ export function readTrace(traceFilePath: string): ITrace { }); frameIDs.pop(); } else if (event.Instruction) { + const name = event.Instruction.instruction; events.push({ type: TraceEventKind.Instruction, - pc: event.Instruction.pc + pc: event.Instruction.pc, + kind: name in TraceInstructionKind + ? TraceInstructionKind[name as keyof typeof TraceInstructionKind] + : TraceInstructionKind.UNKNOWN }); // Set end of lifetime for all locals to the max instruction PC ever seen // for a given local (if they are live after this instructions, they will @@ -288,36 +349,117 @@ export function readTrace(traceFilePath: string): ITrace { // if a local is read or written, set its end of lifetime // to infinite (end of frame) const location = effect.Write ? effect.Write.location : effect.Read!.location; - const frameId = location.Local[0]; - const localIndex = location.Local[1]; - const lifetimeEnds = localLifetimeEnds.get(frameId) || []; - lifetimeEnds[localIndex] = FRAME_LIFETIME; - localLifetimeEnds.set(frameId, lifetimeEnds); - + // there must be at least one frame on the stack when processing a write effect + // so we can safely access the last frame ID + const currentFrameID = frameIDs[frameIDs.length - 1]; + const localIndex = processJSONLocation(location, localLifetimeEnds, currentFrameID); + if (localIndex === undefined) { + continue; + } if (effect.Write) { - const value = JSON.stringify(effect.Write.root_value_after_write.RuntimeValue.value); + const value = traceValueFromJSON(effect.Write.root_value_after_write.RuntimeValue.value); const traceValue: TraceValue = { type: TraceValKind.Runtime, value }; - const TraceLocation: TraceLocation = { + const traceLocation: TraceLocation = { type: TraceLocKind.Local, - frameId, + frameId: currentFrameID, localIndex }; events.push({ type: TraceEventKind.Effect, effect: { type: TraceEffectKind.Write, - location: TraceLocation, + location: traceLocation, value: traceValue } }); } } + } + } + return { events, localLifetimeEnds }; +} + +/** + * Converts a JSON trace type to a string representation. + */ +function JSONTraceTypeToString(type: JSONBaseType): string { + if (typeof type === 'string') { + return type; + } else if ('vector' in type) { + return `vector<${JSONTraceTypeToString(type.vector)}>`; + } else { + return JSONTraceAddressToHexString(type.struct.address) + + "::" + + type.struct.module + + "::" + + type.struct.name; + } +} +/** + * Attempts to convert an address found in the trace (which is a string + * representing a 32-byte number) to a shorter and more readable hex string. + * Returns original string address if conversion fails. + */ +function JSONTraceAddressToHexString(address: string): string { + try { + const number = BigInt(address); + const hexAddress = number.toString(16); + return `0x${hexAddress}`; + } catch (error) { + // Return the original string if it's not a valid number + return address; + } +} +/// Processes a location in a JSON trace (sets the end of lifetime for a local variable) +/// and returns the local index if the location is a local variable in the current frame. +function processJSONLocation( + location: JSONTraceLocation, + localLifetimeEnds: Map, + currentFrameID: number +): number | undefined { + // TODO: handle Global and Indexed for other frames + if ('Local' in location) { + const frameId = location.Local[0]; + const localIndex = location.Local[1]; + const lifetimeEnds = localLifetimeEnds.get(frameId) || []; + lifetimeEnds[localIndex] = FRAME_LIFETIME; + localLifetimeEnds.set(frameId, lifetimeEnds); + return localIndex; + } else if ('Indexed' in location) { + const frameId = location.Indexed[0].Local[0]; + if (frameId === currentFrameID) { + const localIndex = location.Indexed[0].Local[1]; + const lifetimeEnds = localLifetimeEnds.get(frameId) || []; + lifetimeEnds[localIndex] = FRAME_LIFETIME; + localLifetimeEnds.set(frameId, lifetimeEnds); + return localIndex; } } - return { events, localLifetimeEnds }; + return undefined; +} + +/// Converts a JSON trace value to a runtime trace value. +function traceValueFromJSON(value: JSONTraceValueType): RuntimeValueType { + if (typeof value === 'boolean' + || typeof value === 'number' + || typeof value === 'string') { + return String(value); + } else if (Array.isArray(value)) { + return value.map(item => traceValueFromJSON(item)); + } else { + const fields: [string, RuntimeValueType][] = + Object.entries(value.fields).map(([key, value]) => [key, traceValueFromJSON(value)]); + const compoundValue: IRuntimeCompundValue = { + fields, + type: value.type, + variantName: value.variant_name, + variantTag: value.variant_tag + }; + return compoundValue; + } } diff --git a/external-crates/move/crates/move-analyzer/trace-debug/src/extension.ts b/external-crates/move/crates/move-analyzer/trace-debug/src/extension.ts index 2b94f50f38b35..e0c5ad03c02bd 100644 --- a/external-crates/move/crates/move-analyzer/trace-debug/src/extension.ts +++ b/external-crates/move/crates/move-analyzer/trace-debug/src/extension.ts @@ -5,7 +5,13 @@ import * as fs from 'fs'; import * as vscode from 'vscode'; import * as path from 'path'; import { StackFrame } from '@vscode/debugadapter'; -import { WorkspaceFolder, DebugConfiguration, CancellationToken } from 'vscode'; +import { + WorkspaceFolder, + DebugConfiguration, + CancellationToken, + TextDocument, + Position +} from 'vscode'; /** * Log level for the debug adapter. @@ -17,6 +23,19 @@ const LOG_LEVEL = 'log'; */ const DEBUGGER_TYPE = 'move-debug'; +/** + * Provider of on-hover information during debug session. + */ +class MoveEvaluatableExpressionProvider { + // TODO: implement a more sophisticated provider that actually provides correct on-hover information, + // at least for variable definitions whose locations are readily available in the source map + // (user can always use go-to-def to see the definition and the value) + provideEvaluatableExpression(_document: TextDocument, _position: Position, _token: CancellationToken) { + // suppress debug-time on hover information for now + return null; + } +} + /** * Called when the extension is activated. */ @@ -82,20 +101,29 @@ export function activate(context: vscode.ExtensionContext) { editor.setDecorations(decorationType, decorationsArray); } - } } } - }), - vscode.debug.onDidTerminateDebugSession(() => { - // reset all decorations when the debug session is terminated - // to avoid showing lines for code that was optimized away - const editor = vscode.window.activeTextEditor; - if (editor) { - editor.setDecorations(decorationType, []); - } }) ); + + // register a provider of on-hover information during debug session + const langSelector = { scheme: 'file', language: 'move' }; + context.subscriptions.push( + vscode.languages.registerEvaluatableExpressionProvider( + langSelector, + new MoveEvaluatableExpressionProvider() + ) + ); + + context.subscriptions.push(vscode.debug.onDidTerminateDebugSession(() => { + // reset all decorations when the debug session is terminated + // to avoid showing lines for code that was optimized away + const editor = vscode.window.activeTextEditor; + if (editor) { + editor.setDecorations(decorationType, []); + } + })); } /** diff --git a/external-crates/move/crates/move-binary-format/src/compatibility.rs b/external-crates/move/crates/move-binary-format/src/compatibility.rs index 4ab2aa043d0f3..47f2918e1f8a8 100644 --- a/external-crates/move/crates/move-binary-format/src/compatibility.rs +++ b/external-crates/move/crates/move-binary-format/src/compatibility.rs @@ -2,23 +2,21 @@ // Copyright (c) The Move Contributors // SPDX-License-Identifier: Apache-2.0 -use std::collections::BTreeSet; - use crate::{ + compatibility_mode::{CompatibilityMode, ExecutionCompatibilityMode}, errors::{PartialVMError, PartialVMResult}, - file_format::{AbilitySet, DatatypeTyParameter, Visibility}, + file_format::{Ability, AbilitySet, DatatypeTyParameter, Visibility}, file_format_common::VERSION_5, normalized::Module, }; use move_core_types::vm_status::StatusCode; - // *************************************************************************** // ******************* IMPORTANT NOTE ON COMPATIBILITY *********************** // *************************************************************************** // -// If `check_datatype_layout` and/or `check_datatype_and_pub_function_linking` is false, type -// safety over a series of upgrades cannot be guaranteed for either structs or enums. This is -// because the type could first be removed, and then re-introduced with a diferent layout and/or +// If `check_datatype_layout` is false, type safety over a series of upgrades cannot be guaranteed +// for either structs or enums. +// This is because the type could first be removed, and then re-introduced with a diferent layout and/or // additional variants in a later upgrade. E.g., // * For enums you could add a new variant even if `disallow_new_variants` is true, by first // removing the enum in an upgrade, and then reintroducing it with a new variant in a later @@ -30,41 +28,26 @@ use move_core_types::vm_status::StatusCode; /// The result of a linking and layout compatibility check. /// /// Here is what the different combinations of the compatibility flags mean: -/// `{ check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: true }`: fully backward compatible -/// `{ check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: true, check_private_entry_linking: false }`: Backwards compatible, private entry function signatures can change -/// `{ check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: true }`: Backward compatible, exclude the friend module declare and friend functions -/// `{ check_datatype_and_pub_function_linking: true, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: false }`: Backward compatible, exclude the friend module declarations, friend functions, and private and friend entry function -/// `{ check_datatype_and_pub_function_linking: false, check_datatype_layout: true, check_friend_linking: false, check_private_entry_linking: _ }`: Dependent modules that reference functions or types in this module may not link. However, fixing, recompiling, and redeploying all dependent modules will work--no data migration needed. -/// `{ check_datatype_and_pub_function_linking: true, check_datatype_layout: false, check_friend_linking: true, check_private_entry_linking: _ }`: Attempting to read structs published by this module will now fail at runtime. However, dependent modules will continue to link. Requires data migration, but no changes to dependent modules. -/// `{ check_datatype_and_pub_function_linking: false, check_datatype_layout: false, check_friend_linking: false, check_private_entry_linking: _ }`: Everything is broken. Need both a data migration and changes to dependent modules. +/// `{ check_datatype_layout: true, check_private_entry_linking: true }`: fully backward compatible +/// `{ check_datatype_layout: true, check_private_entry_linking: false }`: Backwards compatible, private entry function signatures can change +/// `{ check_datatype_layout: true, check_private_entry_linking: true }`: Backward compatible, exclude the friend module declare and friend functions +/// `{ check_datatype_layout: true, check_private_entry_linking: false }`: Backward compatible, exclude the friend module declarations, friend functions, and private and friend entry function #[derive(PartialEq, Eq, Debug, Clone, Copy)] pub struct Compatibility { - /// if false, do not ensure the dependent modules that reference public functions or structs in this module can link - pub check_datatype_and_pub_function_linking: bool, /// if false, do not ensure the struct layout capability pub check_datatype_layout: bool, - /// if false, treat `friend` as `private` when `check_datatype_and_pub_function_linking`. - pub check_friend_linking: bool, - /// if false, treat `entry` as `private` when `check_datatype_and_pub_function_linking`. + /// if false, treat `entry` as `private` pub check_private_entry_linking: bool, /// The set of abilities that cannot be added to an already exisiting type. pub disallowed_new_abilities: AbilitySet, - /// Don't allow generic type parameters in structs to change their abilities or constraints. - pub disallow_change_datatype_type_params: bool, - /// Don't allow adding new variants at the end of an enum. - pub disallow_new_variants: bool, } impl Default for Compatibility { fn default() -> Self { Self { - check_datatype_and_pub_function_linking: true, check_datatype_layout: true, - check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: AbilitySet::EMPTY, - disallow_change_datatype_type_params: true, - disallow_new_variants: true, } } } @@ -76,13 +59,35 @@ impl Compatibility { pub fn no_check() -> Self { Self { - check_datatype_and_pub_function_linking: false, check_datatype_layout: false, - check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: AbilitySet::EMPTY, - disallow_change_datatype_type_params: false, - disallow_new_variants: false, + } + } + + /// Check compatibility for userspace module upgrades + pub fn upgrade_check() -> Self { + Self { + check_datatype_layout: true, + check_private_entry_linking: false, + disallowed_new_abilities: AbilitySet::ALL, + } + } + + /// Check compatibility for system module upgrades + pub fn framework_upgrade_check() -> Self { + Self { + check_datatype_layout: true, + // Checking `entry` linkage is required because system packages are updated in-place, and a + // transaction that was rolled back to make way for reconfiguration should still be runnable + // after a reconfiguration that upgraded the framework. + // + // A transaction that calls a system function that was previously `entry` and is now private + // will fail because its entrypoint became no longer callable. A transaction that calls a + // system function that was previously `public entry` and is now just `public` could also + // fail if one of its mutable inputs was being used in another private `entry` function. + check_private_entry_linking: true, + disallowed_new_abilities: AbilitySet::singleton(Ability::Key), } } @@ -92,15 +97,25 @@ impl Compatibility { /// Check compatibility for `new_module` relative to old module `old_module`. pub fn check(&self, old_module: &Module, new_module: &Module) -> PartialVMResult<()> { - let mut datatype_and_function_linking = true; - let mut datatype_layout = true; - let mut friend_linking = true; - let mut entry_linking = true; - let mut no_new_variants = true; + self.check_with_mode::(old_module, new_module) + .map_err(|_| PartialVMError::new(StatusCode::BACKWARD_INCOMPATIBLE_MODULE_UPDATE)) + } + + pub fn check_with_mode( + &self, + old_module: &Module, + new_module: &Module, + ) -> Result<(), M::Error> { + let mut context = M::default(); // module's name and address are unchanged if old_module.address != new_module.address || old_module.name != new_module.name { - datatype_and_function_linking = false; + context.module_id_mismatch( + &old_module.address, + &old_module.name, + &new_module.address, + &new_module.name, + ); } // old module's structs are a subset of the new module's structs @@ -109,21 +124,24 @@ impl Compatibility { // Struct not present in new . Existing modules that depend on this struct will fail to link with the new version of the module. // Also, struct layout cannot be guaranteed transitively, because after // removing the struct, it could be re-added later with a different layout. - datatype_and_function_linking = false; - datatype_layout = false; - break; + context.struct_missing(name, old_struct); + continue; }; if !datatype_abilities_compatible( self.disallowed_new_abilities, old_struct.abilities, new_struct.abilities, - ) || !datatype_type_parameters_compatible( - self.disallow_change_datatype_type_params, + ) { + context.struct_ability_mismatch(name, old_struct, new_struct); + } + + if !datatype_type_parameters_compatible( + self.check_datatype_layout, &old_struct.type_parameters, &new_struct.type_parameters, ) { - datatype_and_function_linking = false; + context.struct_type_param_mismatch(name, old_struct, new_struct); } if new_struct.fields != old_struct.fields { // Fields changed. Code in this module will fail at runtime if it tries to @@ -132,7 +150,8 @@ impl Compatibility { // choose that changing the name (but not position or type) of a field is // compatible. The VM does not care about the name of a field // (it's purely informational), but clients presumably do. - datatype_layout = false + + context.struct_field_mismatch(name, old_struct, new_struct); } } @@ -141,37 +160,37 @@ impl Compatibility { // Enum not present in new. Existing modules that depend on this enum will fail to link with the new version of the module. // Also, enum layout cannot be guaranteed transitively, because after // removing the enum, it could be re-added later with a different layout. - datatype_and_function_linking = false; - datatype_layout = false; - break; + + context.enum_missing(name, old_enum); + continue; }; if !datatype_abilities_compatible( self.disallowed_new_abilities, old_enum.abilities, new_enum.abilities, - ) || !datatype_type_parameters_compatible( - self.disallow_change_datatype_type_params, + ) { + context.enum_ability_mismatch(name, old_enum, new_enum); + } + + if !datatype_type_parameters_compatible( + self.check_datatype_layout, &old_enum.type_parameters, &new_enum.type_parameters, ) { - datatype_and_function_linking = false; + context.enum_type_param_mismatch(name, old_enum, new_enum); } if new_enum.variants.len() > old_enum.variants.len() { - no_new_variants = false; - } - - if new_enum.variants.len() < old_enum.variants.len() { - datatype_layout = false; + context.enum_new_variant(name, old_enum, new_enum); } for (tag, old_variant) in old_enum.variants.iter().enumerate() { // If the new enum has fewer variants than the old one, datatype_layout is false // and we don't need to check the rest of the variants. let Some(new_variant) = new_enum.variants.get(tag) else { - datatype_layout = false; - break; + context.enum_variant_missing(name, old_enum, tag); + continue; }; if new_variant.name != old_variant.name { // TODO: Variant renamed. This is a stricter definition than required. @@ -179,7 +198,7 @@ impl Compatibility { // type) of a variant is compatible. The VM does not care about the name of a // variant if it's non-public (it's purely informational), but clients // presumably would. - datatype_layout = false; + context.enum_variant_mismatch(name, old_enum, new_enum, tag); } if new_variant.fields != old_variant.fields { // Fields changed. Code in this module will fail at runtime if it tries to @@ -188,7 +207,7 @@ impl Compatibility { // choose that changing the name (but not position or type) of a field is // compatible. The VM does not care about the name of a field // (it's purely informational), but clients presumably do. - datatype_layout = false + context.enum_variant_mismatch(name, old_enum, new_enum, tag); } } } @@ -199,46 +218,36 @@ impl Compatibility { // (i.e. we cannot remove or change public functions) // - old module's script functions are a subset of the new module's script functions // (i.e. we cannot remove or change script functions) - // - for any friend function that is removed or changed in the old module - // - if the function visibility is upgraded to public, it is OK - // - otherwise, it is considered as incompatible. - // - // NOTE: it is possible to relax the compatibility checking for a friend function, i.e., - // we can remove/change a friend function if the function is not used by any module in the - // friend list. But for simplicity, we decided to go to the more restrictive form now and - // we may revisit this in the future. for (name, old_func) in &old_module.functions { + // Check for removed public functions let Some(new_func) = new_module.functions.get(name) else { - if old_func.visibility == Visibility::Friend { - friend_linking = false; - } else if old_func.visibility != Visibility::Private { - datatype_and_function_linking = false; + if old_func.visibility == Visibility::Public { + context.function_missing_public(name, old_func); } else if old_func.is_entry && self.check_private_entry_linking { // This must be a private entry function. So set the link breakage if we're // checking for that. - entry_linking = false; + context.function_missing_entry(name, old_func); } continue; }; // Check visibility compatibility - match (old_func.visibility, new_func.visibility) { - (Visibility::Public, Visibility::Private | Visibility::Friend) => { - datatype_and_function_linking = false - } - (Visibility::Friend, Visibility::Private) => friend_linking = false, - _ => (), + if old_func.visibility == Visibility::Public + && new_func.visibility != Visibility::Public + { + context.function_lost_public_visibility(name, old_func); } // Check entry compatibility + #[allow(clippy::if_same_then_else)] if old_module.file_format_version < VERSION_5 && new_module.file_format_version < VERSION_5 && old_func.visibility != Visibility::Private && old_func.is_entry != new_func.is_entry { - entry_linking = false + context.function_entry_compatibility(name, old_func, new_func); } else if old_func.is_entry && !new_func.is_entry { - entry_linking = false; + context.function_entry_compatibility(name, old_func, new_func); } // Check signature compatibility @@ -249,55 +258,11 @@ impl Compatibility { &new_func.type_parameters, ) { - match old_func.visibility { - Visibility::Friend => friend_linking = false, - Visibility::Public => datatype_and_function_linking = false, - Visibility::Private => (), - } - - if old_func.is_entry { - entry_linking = false; - } + context.function_signature_mismatch(name, old_func, new_func); } } - // check friend declarations compatibility - // - // - additions to the list are allowed - // - removals are not allowed - // - let old_friend_module_ids: BTreeSet<_> = old_module.friends.iter().cloned().collect(); - let new_friend_module_ids: BTreeSet<_> = new_module.friends.iter().cloned().collect(); - if !old_friend_module_ids.is_subset(&new_friend_module_ids) { - friend_linking = false; - } - - if self.check_datatype_and_pub_function_linking && !datatype_and_function_linking { - return Err(PartialVMError::new( - StatusCode::BACKWARD_INCOMPATIBLE_MODULE_UPDATE, - )); - } - if self.check_datatype_layout && !datatype_layout { - return Err(PartialVMError::new( - StatusCode::BACKWARD_INCOMPATIBLE_MODULE_UPDATE, - )); - } - if self.check_friend_linking && !friend_linking { - return Err(PartialVMError::new( - StatusCode::BACKWARD_INCOMPATIBLE_MODULE_UPDATE, - )); - } - if self.check_private_entry_linking && !entry_linking { - return Err(PartialVMError::new( - StatusCode::BACKWARD_INCOMPATIBLE_MODULE_UPDATE, - )); - } - if self.disallow_new_variants && !no_new_variants { - return Err(PartialVMError::new( - StatusCode::BACKWARD_INCOMPATIBLE_MODULE_UPDATE, - )); - } - Ok(()) + context.finish(self) } } diff --git a/external-crates/move/crates/move-binary-format/src/compatibility_mode.rs b/external-crates/move/crates/move-binary-format/src/compatibility_mode.rs new file mode 100644 index 0000000000000..a0848682ae405 --- /dev/null +++ b/external-crates/move/crates/move-binary-format/src/compatibility_mode.rs @@ -0,0 +1,259 @@ +use crate::compatibility::Compatibility; +use crate::file_format::Visibility; +use crate::normalized::{Enum, Function, Struct}; +use move_core_types::account_address::AccountAddress; +use move_core_types::identifier::{IdentStr, Identifier}; + +/// A trait which will allow accumulating the information necessary for checking upgrade compatibility between two modules, +/// while allowing flexibility in the error type that is returned. +/// Gathers the errors and accumulates them into a single error. +/// The [`Compatibility`] struct's flags are used to determine the compatibility checks that are needed. +pub trait CompatibilityMode: Default { + /// The error type that will be returned when [`CompatibilityMode::finish`] is called, returning the accumulated result. + type Error; + + /// The module id mismatch error occurs when the module id of the old and new modules do not match. + fn module_id_mismatch( + &mut self, + old_addr: &AccountAddress, + old_name: &IdentStr, + new_addr: &AccountAddress, + new_name: &IdentStr, + ); + + /// The struct missing error occurs when a struct is present in the old module but not in the new module. + fn struct_missing(&mut self, name: &Identifier, old_struct: &Struct); + + /// The struct ability mismatch error occurs when the abilities of a struct are outside of the + /// allowed new abilities. Adding an ability is fine as long as it's not in the disallowed_new_abilities set. + fn struct_ability_mismatch( + &mut self, + name: &Identifier, + old_struct: &Struct, + new_struct: &Struct, + ); + + /// Struct type parameters mismatch error occurs when the type parameters of a struct are not the same. + fn struct_type_param_mismatch( + &mut self, + name: &Identifier, + old_struct: &Struct, + new_struct: &Struct, + ); + + /// Struct field mismatch error occurs when the fields of a struct are not the same. + fn struct_field_mismatch( + &mut self, + name: &Identifier, + old_struct: &Struct, + new_struct: &Struct, + ); + + /// Enum missing error occurs when an enum is present in the old module but not in the new module. + fn enum_missing(&mut self, name: &Identifier, old_enum: &Enum); + + /// Enum ability mismatch error occurs when the abilities of an enum are outside of the + /// allowed new abilities. Adding an ability is fine as long as it's not in the disallowed_new_abilities set. + fn enum_ability_mismatch(&mut self, name: &Identifier, old_enum: &Enum, new_enum: &Enum); + + /// Enum type parameters mismatch error occurs when the type parameters of an enum are not the same. + fn enum_type_param_mismatch(&mut self, name: &Identifier, old_enum: &Enum, new_enum: &Enum); + + /// Enum new variant error occurs when a new variant is added to an enum. + fn enum_new_variant(&mut self, name: &Identifier, old_enum: &Enum, new_enum: &Enum); + + /// Enum variant missing error occurs when a variant is present in the old enum but not in the new enum. + fn enum_variant_missing(&mut self, name: &Identifier, old_enum: &Enum, tag: usize); + + /// Enum variant mismatch error occurs when a variant is present in the old enum but not in the new enum. + fn enum_variant_mismatch( + &mut self, + name: &Identifier, + old_enum: &Enum, + new_enum: &Enum, + tag: usize, + ); + + /// Function missing public error occurs when a public function is present in the old module but not in the new module. + fn function_missing_public(&mut self, name: &Identifier, old_func: &Function); + + /// Function missing entry error occurs when an entry function is present in the old module but not in the new module. + fn function_missing_entry(&mut self, name: &Identifier, old_func: &Function); + + /// Function signature mismatch error occurs when the signature of a function changes. + fn function_signature_mismatch( + &mut self, + name: &Identifier, + old_func: &Function, + new_func: &Function, + ); + + /// Function lost public visibility error occurs when a function loses its public visibility. + fn function_lost_public_visibility(&mut self, name: &Identifier, old_func: &Function); + + /// Function entry compatibility error occurs when an entry function is not compatible. + fn function_entry_compatibility( + &mut self, + name: &Identifier, + old_func: &Function, + new_func: &Function, + ); + + /// Finish the compatibility check and return the error if one has been accumulated from individual errors. + fn finish(&self, _: &Compatibility) -> Result<(), Self::Error>; +} + +/// Compatibility mode impl for execution compatibility checks. +/// These flags are set when a type safety check is violated. see [`Compatibility`] for more information. +pub struct ExecutionCompatibilityMode { + /// This can never be overridden with a flag, and thus has no associated [`Compatibility`] flag. + /// In other words public linking can never be broken. all other flags + datatype_and_function_linking: bool, + datatype_layout: bool, + entry_linking: bool, + no_new_variants: bool, +} + +impl Default for ExecutionCompatibilityMode { + fn default() -> Self { + Self { + datatype_and_function_linking: true, + datatype_layout: true, + entry_linking: true, + no_new_variants: true, + } + } +} + +impl CompatibilityMode for ExecutionCompatibilityMode { + /// Unit error type for execution compatibility mode. + /// We only need to know if an error has occurred. + type Error = (); + + fn module_id_mismatch( + &mut self, + _old_addr: &AccountAddress, + _old_name: &IdentStr, + _new_addr: &AccountAddress, + _new_name: &IdentStr, + ) { + self.datatype_and_function_linking = false; + } + + fn struct_missing(&mut self, _name: &Identifier, _old_struct: &Struct) { + self.datatype_and_function_linking = false; + self.datatype_layout = false; + } + + fn struct_ability_mismatch( + &mut self, + _name: &Identifier, + _old_struct: &Struct, + _new_struct: &Struct, + ) { + self.datatype_and_function_linking = false; + } + + fn struct_type_param_mismatch( + &mut self, + _name: &Identifier, + _old_struct: &Struct, + _new_struct: &Struct, + ) { + self.datatype_and_function_linking = false; + } + + fn struct_field_mismatch( + &mut self, + _name: &Identifier, + _old_struct: &Struct, + _new_struct: &Struct, + ) { + self.datatype_layout = false; + } + + fn enum_missing(&mut self, _name: &Identifier, _old_enum: &Enum) { + self.datatype_and_function_linking = false; + self.datatype_layout = false; + } + + fn enum_ability_mismatch(&mut self, _name: &Identifier, _old_enum: &Enum, _new_enum: &Enum) { + self.datatype_and_function_linking = false; + } + + fn enum_type_param_mismatch(&mut self, _name: &Identifier, _old_enum: &Enum, _new_enum: &Enum) { + self.datatype_and_function_linking = false; + } + + fn enum_new_variant(&mut self, _name: &Identifier, _old_enum: &Enum, _new_enum: &Enum) { + self.no_new_variants = false; + } + + fn enum_variant_missing(&mut self, _name: &Identifier, _old_enum: &Enum, _tag: usize) { + self.datatype_layout = false; + } + + fn enum_variant_mismatch( + &mut self, + _name: &Identifier, + _old_enum: &Enum, + _new_enum: &Enum, + _tag: usize, + ) { + self.datatype_layout = false; + } + + fn function_missing_public(&mut self, _name: &Identifier, _old_func: &Function) { + self.datatype_and_function_linking = false; + } + + fn function_missing_entry(&mut self, _name: &Identifier, _old_func: &Function) { + self.entry_linking = false; + } + + fn function_signature_mismatch( + &mut self, + _name: &Identifier, + old_func: &Function, + _new_func: &Function, + ) { + if old_func.visibility == Visibility::Public { + self.datatype_and_function_linking = false; + } + + if old_func.is_entry { + self.entry_linking = false; + } + } + + fn function_lost_public_visibility(&mut self, _name: &Identifier, _old_func: &Function) { + self.datatype_and_function_linking = false; + } + + fn function_entry_compatibility( + &mut self, + _name: &Identifier, + _old_func: &Function, + _new_func: &Function, + ) { + self.entry_linking = false; + } + + /// Finish by comparing against the compatibility flags. + fn finish(&self, compatability: &Compatibility) -> Result<(), ()> { + if !self.datatype_and_function_linking { + return Err(()); + } + if compatability.check_datatype_layout && !self.datatype_layout { + return Err(()); + } + if compatability.check_private_entry_linking && !self.entry_linking { + return Err(()); + } + if compatability.check_datatype_layout && !self.no_new_variants { + return Err(()); + } + + Ok(()) + } +} diff --git a/external-crates/move/crates/move-binary-format/src/file_format_common.rs b/external-crates/move/crates/move-binary-format/src/file_format_common.rs index 7d006ef74f9f9..94447450d5b9d 100644 --- a/external-crates/move/crates/move-binary-format/src/file_format_common.rs +++ b/external-crates/move/crates/move-binary-format/src/file_format_common.rs @@ -529,11 +529,10 @@ pub const VERSION_MAX: u32 = VERSION_7; // TODO(#145): finish v4 compatibility; as of now, only metadata is implemented pub const VERSION_MIN: u32 = VERSION_5; -/// The encoding of the instruction is the serialized form of it, but disregarding the -/// serialization of the instruction's argument(s). -pub fn instruction_key(instruction: &Bytecode) -> u8 { +/// The corresponding opcode for each bytecode (disregards the argument). +pub fn instruction_opcode(instruction: &Bytecode) -> Opcodes { use Bytecode::*; - let opcode = match instruction { + match instruction { Pop => Opcodes::POP, Ret => Opcodes::RET, BrTrue(_) => Opcodes::BR_TRUE, @@ -621,6 +620,11 @@ pub fn instruction_key(instruction: &Bytecode) -> u8 { MutBorrowGlobalGenericDeprecated(_) => Opcodes::MUT_BORROW_GLOBAL_GENERIC_DEPRECATED, ImmBorrowGlobalDeprecated(_) => Opcodes::IMM_BORROW_GLOBAL_DEPRECATED, ImmBorrowGlobalGenericDeprecated(_) => Opcodes::IMM_BORROW_GLOBAL_GENERIC_DEPRECATED, - }; - opcode as u8 + } +} + +/// The encoding of the instruction is the serialized form of it, but disregarding the +/// serialization of the instruction's argument(s). +pub fn instruction_key(instruction: &Bytecode) -> u8 { + instruction_opcode(instruction) as u8 } diff --git a/external-crates/move/crates/move-binary-format/src/lib.rs b/external-crates/move/crates/move-binary-format/src/lib.rs index 78e3607f7dfc8..772c396ce14c4 100644 --- a/external-crates/move/crates/move-binary-format/src/lib.rs +++ b/external-crates/move/crates/move-binary-format/src/lib.rs @@ -9,6 +9,7 @@ use std::fmt; pub mod binary_config; pub mod check_bounds; pub mod compatibility; +pub mod compatibility_mode; #[macro_use] pub mod errors; pub mod constant; diff --git a/external-crates/move/crates/move-binary-format/src/unit_tests/compatibility_tests.rs b/external-crates/move/crates/move-binary-format/src/unit_tests/compatibility_tests.rs index 0e87b655da204..ad07ebe1efa64 100644 --- a/external-crates/move/crates/move-binary-format/src/unit_tests/compatibility_tests.rs +++ b/external-crates/move/crates/move-binary-format/src/unit_tests/compatibility_tests.rs @@ -623,26 +623,18 @@ fn private_entry_signature_change_allowed() { // allow updating signatures of private entry functions assert!(Compatibility { - check_datatype_and_pub_function_linking: true, check_datatype_layout: true, - check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: AbilitySet::EMPTY, - disallow_change_datatype_type_params: false, - disallow_new_variants: false, } .check(&module, &updated_module) .is_ok()); // allow updating signatures of private entry functions assert!(Compatibility { - check_datatype_and_pub_function_linking: true, check_datatype_layout: true, - check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: AbilitySet::EMPTY, - disallow_change_datatype_type_params: false, - disallow_new_variants: false, } .check(&updated_module, &module) .is_ok()); @@ -698,9 +690,6 @@ fn entry_fun_compat_tests() { (&public_entry_fun, &friend_entry_fun), (&public_entry_fun, &friend_fun), (&public_entry_fun, &no_fun), - (&friend_entry_fun, &no_fun), - (&friend_entry_fun, &private_fun), - (&friend_entry_fun, &entry_fun), ]; let invalid_private_entry_breakages = vec![ @@ -725,13 +714,9 @@ fn entry_fun_compat_tests() { // Every valid combo is valid under `check_private_entry_linking = false` for (prev, new) in valid_combos.into_iter() { assert!(Compatibility { - check_datatype_and_pub_function_linking: true, check_datatype_layout: true, - check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: AbilitySet::EMPTY, - disallow_change_datatype_type_params: false, - disallow_new_variants: false, } .check(prev, new) .is_ok()); @@ -745,39 +730,19 @@ fn entry_fun_compat_tests() { // Every valid combo is valid under `check_private_entry_linking = false` for (prev, new) in valid_entry_fun_changes_with_friend_api_breakage.into_iter() { assert!(Compatibility { - check_datatype_and_pub_function_linking: true, check_datatype_layout: true, - check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: AbilitySet::EMPTY, - disallow_change_datatype_type_params: false, - disallow_new_variants: false, } .check(prev, new) .is_ok()); - - assert!(Compatibility { - check_datatype_and_pub_function_linking: true, - check_datatype_layout: true, - check_friend_linking: true, - check_private_entry_linking: false, - disallowed_new_abilities: AbilitySet::EMPTY, - disallow_change_datatype_type_params: false, - disallow_new_variants: false, - } - .check(prev, new) - .is_err()); } for (prev, new) in invalid_combos.into_iter() { assert!(Compatibility { - check_datatype_and_pub_function_linking: true, check_datatype_layout: true, - check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: AbilitySet::EMPTY, - disallow_change_datatype_type_params: false, - disallow_new_variants: false, } .check(prev, new) .is_err()); @@ -797,37 +762,25 @@ fn public_entry_signature_change_disallowed() { .parameters = vec![Type::U64]; assert!(Compatibility { - check_datatype_and_pub_function_linking: true, check_datatype_layout: true, - check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: AbilitySet::EMPTY, - disallow_change_datatype_type_params: false, - disallow_new_variants: false, } .check(&module, &updated_module) .is_err()); assert!(Compatibility { - check_datatype_and_pub_function_linking: true, check_datatype_layout: true, - check_friend_linking: true, check_private_entry_linking: false, disallowed_new_abilities: AbilitySet::EMPTY, - disallow_change_datatype_type_params: false, - disallow_new_variants: false, } .check(&updated_module, &module) .is_err()); assert!(Compatibility { - check_datatype_and_pub_function_linking: true, check_datatype_layout: true, - check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: AbilitySet::EMPTY, - disallow_change_datatype_type_params: false, - disallow_new_variants: false, } .check(&module, &updated_module) .is_err()); @@ -845,49 +798,17 @@ fn friend_entry_signature_change_allowed() { .parameters = vec![Type::U64]; assert!(Compatibility { - check_datatype_and_pub_function_linking: true, check_datatype_layout: true, - check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities: AbilitySet::EMPTY, - disallow_change_datatype_type_params: false, - disallow_new_variants: false, } .check(&module, &updated_module) .is_ok()); assert!(Compatibility { - check_datatype_and_pub_function_linking: true, - check_datatype_layout: true, - check_friend_linking: true, - check_private_entry_linking: false, - disallowed_new_abilities: AbilitySet::EMPTY, - disallow_change_datatype_type_params: false, - disallow_new_variants: false, - } - .check(&module, &updated_module) - .is_err()); - - assert!(Compatibility { - check_datatype_and_pub_function_linking: true, - check_datatype_layout: true, - check_friend_linking: false, - check_private_entry_linking: true, - disallowed_new_abilities: AbilitySet::EMPTY, - disallow_change_datatype_type_params: false, - disallow_new_variants: false, - } - .check(&module, &updated_module) - .is_err()); - - assert!(Compatibility { - check_datatype_and_pub_function_linking: true, check_datatype_layout: true, - check_friend_linking: true, check_private_entry_linking: true, disallowed_new_abilities: AbilitySet::EMPTY, - disallow_change_datatype_type_params: false, - disallow_new_variants: false, } .check(&module, &updated_module) .is_err()); diff --git a/external-crates/move/crates/move-bytecode-source-map/Cargo.toml b/external-crates/move/crates/move-bytecode-source-map/Cargo.toml index c42d4a8e01f8c..4233763f94b0d 100644 --- a/external-crates/move/crates/move-bytecode-source-map/Cargo.toml +++ b/external-crates/move/crates/move-bytecode-source-map/Cargo.toml @@ -17,6 +17,7 @@ move-command-line-common.workspace = true bcs.workspace = true serde.workspace = true +serde_json.workspace = true [features] default = [] diff --git a/external-crates/move/crates/move-bytecode-source-map/src/source_map.rs b/external-crates/move/crates/move-bytecode-source-map/src/source_map.rs index 92d5cb220c191..32ae09de384b3 100644 --- a/external-crates/move/crates/move-bytecode-source-map/src/source_map.rs +++ b/external-crates/move/crates/move-bytecode-source-map/src/source_map.rs @@ -67,6 +67,9 @@ pub struct FunctionSourceMap { /// The names of the parameters to the function. pub parameters: Vec, + /// The locations of the return values + pub returns: Vec, + /// The index into the vector is the local's index. The corresponding `(Identifier, Location)` tuple /// is the name and location of the local. pub locals: Vec, @@ -206,6 +209,7 @@ impl FunctionSourceMap { definition_location, type_parameters: Vec::new(), parameters: Vec::new(), + returns: Vec::new(), locals: Vec::new(), code_map: BTreeMap::new(), is_native, @@ -252,6 +256,10 @@ impl FunctionSourceMap { self.parameters.push(name) } + /// add the locations of return values + pub fn add_return_mapping(&mut self, loc: Loc) { + self.returns.push(loc); + } /// Recall that we are using a segment tree. We therefore lookup the location for the code /// offset by performing a range query for the largest number less than or equal to the code /// offset passed in. @@ -451,6 +459,18 @@ impl SourceMap { Ok(()) } + pub fn add_return_mapping( + &mut self, + fdef_idx: FunctionDefinitionIndex, + loc: Loc, + ) -> Result<()> { + let func_entry = self.function_map.get_mut(&fdef_idx.0).ok_or_else(|| { + format_err!("Tried to add return mapping to undefined function index") + })?; + func_entry.add_return_mapping(loc); + Ok(()) + } + pub fn get_parameter_or_local_name( &self, fdef_idx: FunctionDefinitionIndex, diff --git a/external-crates/move/crates/move-bytecode-source-map/src/utils.rs b/external-crates/move/crates/move-bytecode-source-map/src/utils.rs index f73d1203451c3..191f57f2b6203 100644 --- a/external-crates/move/crates/move-bytecode-source-map/src/utils.rs +++ b/external-crates/move/crates/move-bytecode-source-map/src/utils.rs @@ -5,12 +5,19 @@ use crate::source_map::SourceMap; use anyhow::{format_err, Result}; use move_ir_types::location::Loc; -use std::{fs::File, io::Read, path::Path}; +use std::{ + fs::File, + io::{Read, Write}, + path::Path, +}; pub type Error = (Loc, String); pub type Errors = Vec; pub fn source_map_from_file(file_path: &Path) -> Result { + if file_path.extension().is_some_and(|ext| ext == "json") { + return deserialize_from_json(file_path); + } let mut bytes = Vec::new(); File::open(file_path) .ok() @@ -19,3 +26,31 @@ pub fn source_map_from_file(file_path: &Path) -> Result { bcs::from_bytes::(&bytes) .map_err(|_| format_err!("Error deserializing into source map")) } + +pub fn serialize_to_json(map: &SourceMap) -> Result> { + serde_json::to_vec(map).map_err(|e| format_err!("Error serializing to json: {}", e)) +} + +pub fn serialize_to_json_file(map: &SourceMap, file_path: &Path) -> Result<()> { + let json = serde_json::to_string_pretty(map) + .map_err(|e| format_err!("Error serializing to json: {}", e))?; + let mut f = + std::fs::File::create(file_path).map_err(|e| format_err!("Error creating file: {}", e))?; + f.write_all(json.as_bytes()) + .map_err(|e| format_err!("Error writing to file: {}", e))?; + Ok(()) +} + +pub fn deserialize_from_json(file_path: &Path) -> Result { + let mut file = File::open(file_path).map_err(|e| format_err!("Error opening file: {}", e))?; + let mut json = String::new(); + file.read_to_string(&mut json) + .map_err(|e| format_err!("Error reading file: {}", e))?; + serde_json::from_str(&json).map_err(|e| format_err!("Error deserializing from json: {}", e)) +} + +pub fn convert_to_json(file_path: &Path) -> Result<()> { + let map = source_map_from_file(file_path)?; + let json_file_path = file_path.with_extension("json"); + serialize_to_json_file(&map, &json_file_path) +} diff --git a/external-crates/move/crates/move-cli/Cargo.toml b/external-crates/move/crates/move-cli/Cargo.toml index d718f3727217e..128c9493155fa 100644 --- a/external-crates/move/crates/move-cli/Cargo.toml +++ b/external-crates/move/crates/move-cli/Cargo.toml @@ -58,5 +58,10 @@ harness = false name = "build_testsuite" harness = false +[[test]] +name = "tracing_testsuite" +harness = false + [features] tiered-gas = ["move-vm-test-utils/tiered-gas"] +gas-profiler = ["move-vm-runtime/gas-profiler"] diff --git a/external-crates/move/crates/move-cli/src/base/test.rs b/external-crates/move/crates/move-cli/src/base/test.rs index 68e8ace285981..e37309a8e9b1a 100644 --- a/external-crates/move/crates/move-cli/src/base/test.rs +++ b/external-crates/move/crates/move-cli/src/base/test.rs @@ -68,6 +68,10 @@ pub struct Test { /// The number of iterations to run each test that uses generated values (only used with #[random_test]). #[clap(name = "rand-num-iters", long = "rand-num-iters")] pub rand_num_iters: Option, + + // Enable tracing for tests + #[clap(long = "trace-execution", value_name = "PATH")] + pub trace_execution: Option>, } impl Test { @@ -108,6 +112,7 @@ impl Test { compute_coverage: _, seed, rand_num_iters, + trace_execution, } = self; UnitTestingConfig { gas_limit, @@ -118,6 +123,7 @@ impl Test { verbose: verbose_mode, seed, rand_num_iters, + trace_execution, ..UnitTestingConfig::default_with_bound(None) } } diff --git a/external-crates/move/crates/move-cli/src/sandbox/utils/mod.rs b/external-crates/move/crates/move-cli/src/sandbox/utils/mod.rs index fa49bc971751c..d89930c2a6513 100644 --- a/external-crates/move/crates/move-cli/src/sandbox/utils/mod.rs +++ b/external-crates/move/crates/move-cli/src/sandbox/utils/mod.rs @@ -163,13 +163,9 @@ pub(crate) fn explain_publish_error( let new_api = normalized::Module::new(module); if (Compatibility { - check_datatype_and_pub_function_linking: false, check_datatype_layout: true, - check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: AbilitySet::EMPTY, - disallow_change_datatype_type_params: false, - disallow_new_variants: false, }) .check(&old_api, &new_api) .is_err() @@ -178,13 +174,9 @@ pub(crate) fn explain_publish_error( // structs of this type. but probably a bad idea println!("Layout API for structs of module {} has changed. Need to do a data migration of published structs", module_id) } else if (Compatibility { - check_datatype_and_pub_function_linking: true, check_datatype_layout: false, - check_friend_linking: false, check_private_entry_linking: true, disallowed_new_abilities: AbilitySet::EMPTY, - disallow_change_datatype_type_params: false, - disallow_new_variants: false, }) .check(&old_api, &new_api) .is_err() diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/Move.toml b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/Move.toml new file mode 100644 index 0000000000000..b0a3ed435137d --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/Move.toml @@ -0,0 +1,9 @@ + [package] + name = "tracing_unit_tests" + edition = "2024.beta" # edition = "legacy" to use legacy (pre-2024) Move + + [dependencies] + MoveStdlib = { local = "../../../../move-stdlib" } + + [addresses] + std = "0x1" diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/NO_TEMPDIR b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/NO_TEMPDIR new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/args.exp b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/args.exp new file mode 100644 index 0000000000000..7f92068e1202a --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/args.exp @@ -0,0 +1,28 @@ +Command `test -t 1 --trace-execution new_traces`: +INCLUDING DEPENDENCY MoveStdlib +BUILDING tracing_unit_tests +Running Move unit tests +[ PASS ] 0x1::calls::test_call_order +[ PASS ] 0x1::calls::test_call_return_order +[ PASS ] 0x1::calls::test_complex_nested_calls +[ PASS ] 0x1::calls::test_return_order +[ PASS ] 0x1::errors::aborter +[ PASS ] 0x1::errors::bad_cast +[ PASS ] 0x1::errors::div_0 +[ PASS ] 0x1::errors::fail_during_abort +[ PASS ] 0x1::errors::overshift_l +[ PASS ] 0x1::errors::overshift_r +[ PASS ] 0x1::errors::underflow +[ PASS ] 0x1::natives::get_orig_type_name_test +[ PASS ] 0x1::natives::get_type_name_test +[ PASS ] 0x1::packs::test_gen_pack_order +[ PASS ] 0x1::packs::test_gen_unpack_order +[ PASS ] 0x1::packs::test_pack_order +[ PASS ] 0x1::packs::test_unpack_order +[ PASS ] 0x1::references::nested_struct_reference_mutation +[ PASS ] 0x1::references::pass_mut_assign_in_other_fn +[ PASS ] 0x1::references::test_struct_borrow +[ PASS ] 0x1::references::test_vector_mut_borrow +[ PASS ] 0x1::references::test_vector_mut_borrow_pop +Test result: OK. Total tests: 22; passed: 22; failed: 0 +External Command `diff -qr new_traces saved_traces`: diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/args.txt b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/args.txt new file mode 100644 index 0000000000000..9a232bf403fd0 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/args.txt @@ -0,0 +1,2 @@ +test -t 1 --trace-execution new_traces +> diff -qr new_traces saved_traces diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__calls__test_call_order.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__calls__test_call_order.json new file mode 100644 index 0000000000000..925a0e8a072b2 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__calls__test_call_order.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":0,"frame_id":0,"function_name":"test_call_order","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_TRUE","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999994,"instruction":"LD_U8","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999994,"instruction":"CALL","pc":3,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":1,"frame_id":8,"function_name":"f_test_call_order","is_native":false,"locals_types":[{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[{"RuntimeValue":{"value":1}},{"RuntimeValue":{"value":true}},{"RuntimeValue":{"value":1}}],"return_types":[],"type_instantiation":[]},"gas_left":999999994}},{"Instruction":{"gas_left":999994823,"instruction":"RET","pc":0,"type_parameters":[]}},{"CloseFrame":{"frame_id":8,"gas_left":999994823,"return_":[]}},{"Instruction":{"gas_left":999994184,"instruction":"RET","pc":4,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999994184,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__calls__test_call_return_order.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__calls__test_call_return_order.json new file mode 100644 index 0000000000000..7a997c124db28 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__calls__test_call_return_order.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":4,"frame_id":0,"function_name":"test_call_return_order","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":1000000000,"instruction":"CALL","pc":0,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":3,"frame_id":2,"function_name":"f_test_return_order","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[],"return_types":[{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u8"}],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999998865,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999998863,"instruction":"LD_TRUE","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999998861,"instruction":"LD_U8","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999998222,"instruction":"RET","pc":3,"type_parameters":[]}},{"CloseFrame":{"frame_id":2,"gas_left":999998222,"return_":[{"RuntimeValue":{"value":1}},{"RuntimeValue":{"value":true}},{"RuntimeValue":{"value":0}}]}},{"Instruction":{"gas_left":999998222,"instruction":"CALL","pc":1,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":1,"frame_id":12,"function_name":"f_test_call_order","is_native":false,"locals_types":[{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[{"RuntimeValue":{"value":1}},{"RuntimeValue":{"value":true}},{"RuntimeValue":{"value":0}}],"return_types":[],"type_instantiation":[]},"gas_left":999998222}},{"Instruction":{"gas_left":999993051,"instruction":"RET","pc":0,"type_parameters":[]}},{"CloseFrame":{"frame_id":12,"gas_left":999993051,"return_":[]}},{"Instruction":{"gas_left":999992412,"instruction":"RET","pc":2,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999992412,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__calls__test_complex_nested_calls.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__calls__test_complex_nested_calls.json new file mode 100644 index 0000000000000..bc9e09f22d027 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__calls__test_complex_nested_calls.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":5,"frame_id":0,"function_name":"test_complex_nested_calls","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":1000000000,"instruction":"CALL","pc":0,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":6,"frame_id":2,"function_name":"f","is_native":false,"locals_types":[{"ref_type":null,"type_":"u64"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999998867,"instruction":"CALL","pc":0,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":7,"frame_id":4,"function_name":"k","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[],"return_types":[{"ref_type":null,"type_":"u64"}],"type_instantiation":[]},"gas_left":999998867}},{"Instruction":{"gas_left":999997732,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999997093,"instruction":"RET","pc":1,"type_parameters":[]}},{"CloseFrame":{"frame_id":4,"gas_left":999997093,"return_":[{"RuntimeValue":{"value":1}}]}},{"Instruction":{"gas_left":999997091,"instruction":"LD_U64","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999997089,"instruction":"ADD","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999997057,"instruction":"ST_LOC","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Write":{"location":{"Local":[2,0]},"root_value_after_write":{"RuntimeValue":{"value":2}}}}},{"Instruction":{"gas_left":999997025,"instruction":"COPY_LOC","pc":4,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[2,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":2}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999997023,"instruction":"LD_U64","pc":5,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999997021,"instruction":"GT","pc":6,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999997019,"instruction":"BR_FALSE","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996987,"instruction":"MOVE_LOC","pc":8,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[2,0]},"moved":true,"root_value_read":{"RuntimeValue":{"value":2}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999996987,"instruction":"CALL","pc":9,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":8,"frame_id":33,"function_name":"g","is_native":false,"locals_types":[{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[{"RuntimeValue":{"value":2}}],"return_types":[],"type_instantiation":[]},"gas_left":999996987}},{"Instruction":{"gas_left":999994689,"instruction":"MOVE_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[33,0]},"moved":true,"root_value_read":{"RuntimeValue":{"value":2}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999994686,"instruction":"CAST_U8","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999994654,"instruction":"ST_LOC","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Write":{"location":{"Local":[33,1]},"root_value_after_write":{"RuntimeValue":{"value":2}}}}},{"Instruction":{"gas_left":999994604,"instruction":"LD_CONST","pc":3,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":[1,2,3]}}}},{"Instruction":{"gas_left":999994602,"instruction":"POP","pc":4,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":[1,2,3]}}}},{"Instruction":{"gas_left":999994586,"instruction":"LD_CONST","pc":5,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999994584,"instruction":"LD_TRUE","pc":6,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999994482,"instruction":"PACK","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"}}}}},{"Instruction":{"gas_left":999994480,"instruction":"LD_TRUE","pc":8,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999994324,"instruction":"PACK_GENERIC","pc":9,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"calls","name":"X","type_args":[]}},"bool"]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"x":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"},"y":true},"type":"0x1::calls::Y<0x1::calls::X, bool>"}}}}},{"Instruction":{"gas_left":999994324,"instruction":"CALL_GENERIC","pc":10,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":10,"frame_id":62,"function_name":"i","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"calls","name":"Y","type_args":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"calls","name":"X","type_args":[]}},"bool"]}}},{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"u8"},{"ref_type":"Mut","type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[{"RuntimeValue":{"value":{"fields":{"x":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"},"y":true},"type":"0x1::calls::Y<0x1::calls::X, bool>"}}}],"return_types":[{"ref_type":null,"type_":"u8"}],"type_instantiation":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"calls","name":"X","type_args":[]}},"bool"]},"gas_left":999994324}},{"Instruction":{"gas_left":999991888,"instruction":"MOVE_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[62,0]},"moved":true,"root_value_read":{"RuntimeValue":{"value":{"fields":{"x":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"},"y":true},"type":"0x1::calls::Y<0x1::calls::X, bool>"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"x":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"},"y":true},"type":"0x1::calls::Y<0x1::calls::X, bool>"}}}}},{"Instruction":{"gas_left":999991732,"instruction":"UNPACK_GENERIC","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"x":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"},"y":true},"type":"0x1::calls::Y<0x1::calls::X, bool>"}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999991730,"instruction":"POP","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999991728,"instruction":"POP","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"}}}}},{"Instruction":{"gas_left":999991726,"instruction":"LD_U64","pc":4,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999991694,"instruction":"ST_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Write":{"location":{"Local":[62,1]},"root_value_after_write":{"RuntimeValue":{"value":1}}}}},{"Instruction":{"gas_left":999991692,"instruction":"IMM_BORROW_LOC","pc":6,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[62,1]},"moved":false,"root_value_read":{"RuntimeValue":{"value":1}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[62,1]},"snapshot":1}}}},{"Instruction":{"gas_left":999991660,"instruction":"READ_REF","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[62,1]},"snapshot":1}}}},{"Effect":{"Read":{"location":{"Local":[62,1]},"moved":false,"root_value_read":{"RuntimeValue":{"value":1}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999991658,"instruction":"POP","pc":8,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999991656,"instruction":"LD_U8","pc":9,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999991624,"instruction":"ST_LOC","pc":10,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Write":{"location":{"Local":[62,2]},"root_value_after_write":{"RuntimeValue":{"value":1}}}}},{"Instruction":{"gas_left":999991621,"instruction":"MUT_BORROW_LOC","pc":11,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[62,2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":1}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[62,2]},"snapshot":1}}}},{"Instruction":{"gas_left":999991605,"instruction":"ST_LOC","pc":12,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[62,2]},"snapshot":1}}}},{"Effect":{"Write":{"location":{"Local":[62,3]},"root_value_after_write":{"MutRef":{"location":{"Local":[62,2]},"snapshot":1}}}}},{"Instruction":{"gas_left":999991603,"instruction":"LD_U8","pc":13,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999991587,"instruction":"COPY_LOC","pc":14,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[62,3]},"moved":false,"root_value_read":{"MutRef":{"location":{"Local":[62,2]},"snapshot":1}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[62,2]},"snapshot":1}}}},{"Instruction":{"gas_left":999991555,"instruction":"WRITE_REF","pc":15,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[62,2]},"snapshot":1}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Write":{"location":{"Local":[62,2]},"root_value_after_write":{"RuntimeValue":{"value":2}}}}},{"Instruction":{"gas_left":999991539,"instruction":"MOVE_LOC","pc":16,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[62,3]},"moved":true,"root_value_read":{"MutRef":{"location":{"Local":[62,2]},"snapshot":2}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[62,2]},"snapshot":2}}}},{"Instruction":{"gas_left":999991507,"instruction":"READ_REF","pc":17,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[62,2]},"snapshot":2}}}},{"Effect":{"Read":{"location":{"Local":[62,2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":2}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999990868,"instruction":"RET","pc":18,"type_parameters":[]}},{"CloseFrame":{"frame_id":62,"gas_left":999990868,"return_":[{"RuntimeValue":{"value":2}}]}},{"Instruction":{"gas_left":999990868,"instruction":"CALL","pc":11,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":9,"frame_id":118,"function_name":"h","is_native":false,"locals_types":[{"ref_type":null,"type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[{"RuntimeValue":{"value":2}}],"return_types":[],"type_instantiation":[]},"gas_left":999990868}},{"Instruction":{"gas_left":999987963,"instruction":"RET","pc":0,"type_parameters":[]}},{"CloseFrame":{"frame_id":118,"gas_left":999987963,"return_":[]}},{"Instruction":{"gas_left":999987931,"instruction":"MOVE_LOC","pc":12,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[33,1]},"moved":true,"root_value_read":{"RuntimeValue":{"value":2}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999987931,"instruction":"CALL","pc":13,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":9,"frame_id":125,"function_name":"h","is_native":false,"locals_types":[{"ref_type":null,"type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[{"RuntimeValue":{"value":2}}],"return_types":[],"type_instantiation":[]},"gas_left":999987931}},{"Instruction":{"gas_left":999985026,"instruction":"RET","pc":0,"type_parameters":[]}},{"CloseFrame":{"frame_id":125,"gas_left":999985026,"return_":[]}},{"Instruction":{"gas_left":999984387,"instruction":"RET","pc":14,"type_parameters":[]}},{"CloseFrame":{"frame_id":33,"gas_left":999984387,"return_":[]}},{"Instruction":{"gas_left":999983748,"instruction":"RET","pc":10,"type_parameters":[]}},{"CloseFrame":{"frame_id":2,"gas_left":999983748,"return_":[]}},{"Instruction":{"gas_left":999983109,"instruction":"RET","pc":1,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999983109,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__calls__test_return_order.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__calls__test_return_order.json new file mode 100644 index 0000000000000..da67d038efda7 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__calls__test_return_order.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":2,"frame_id":0,"function_name":"test_return_order","is_native":false,"locals_types":[{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":1000000000,"instruction":"CALL","pc":0,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":3,"frame_id":2,"function_name":"f_test_return_order","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[],"return_types":[{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u8"}],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999998865,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999998863,"instruction":"LD_TRUE","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999998861,"instruction":"LD_U8","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999998222,"instruction":"RET","pc":3,"type_parameters":[]}},{"CloseFrame":{"frame_id":2,"gas_left":999998222,"return_":[{"RuntimeValue":{"value":1}},{"RuntimeValue":{"value":true}},{"RuntimeValue":{"value":0}}]}},{"Instruction":{"gas_left":999998190,"instruction":"ST_LOC","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Write":{"location":{"Local":[0,2]},"root_value_after_write":{"RuntimeValue":{"value":0}}}}},{"Instruction":{"gas_left":999998158,"instruction":"ST_LOC","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Write":{"location":{"Local":[0,1]},"root_value_after_write":{"RuntimeValue":{"value":true}}}}},{"Instruction":{"gas_left":999998126,"instruction":"ST_LOC","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":1}}}}},{"Instruction":{"gas_left":999998094,"instruction":"MOVE_LOC","pc":4,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,2]},"moved":true,"root_value_read":{"RuntimeValue":{"value":0}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999998092,"instruction":"LD_U8","pc":5,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999998028,"instruction":"EQ","pc":6,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999998026,"instruction":"BR_FALSE","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999998024,"instruction":"BRANCH","pc":8,"type_parameters":[]}},{"Instruction":{"gas_left":999997992,"instruction":"MOVE_LOC","pc":11,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,1]},"moved":true,"root_value_read":{"RuntimeValue":{"value":true}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999997990,"instruction":"BR_FALSE","pc":12,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999997988,"instruction":"BRANCH","pc":13,"type_parameters":[]}},{"Instruction":{"gas_left":999997349,"instruction":"RET","pc":16,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999997349,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__aborter.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__aborter.json new file mode 100644 index 0000000000000..689f2c8ec07bf --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__aborter.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":0,"frame_id":0,"function_name":"aborter","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"errors"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999999996,"instruction":"ABORT","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"ExecutionError":"ABORTED"}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__bad_cast.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__bad_cast.json new file mode 100644 index 0000000000000..c68db2b6df295 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__bad_cast.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":3,"frame_id":0,"function_name":"bad_cast","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"errors"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":256}}}},{"Instruction":{"gas_left":999999995,"instruction":"CAST_U8","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":256}}}},{"Effect":{"ExecutionError":"ARITHMETIC_ERROR"}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__div_0.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__div_0.json new file mode 100644 index 0000000000000..d901830856afa --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__div_0.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":1,"frame_id":0,"function_name":"div_0","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"errors"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_U64","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999992,"instruction":"DIV","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"ExecutionError":"ARITHMETIC_ERROR"}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__fail_during_abort.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__fail_during_abort.json new file mode 100644 index 0000000000000..2b27b5e5b212d --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__fail_during_abort.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":6,"frame_id":0,"function_name":"fail_during_abort","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"errors"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_U64","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999992,"instruction":"DIV","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"ExecutionError":"ARITHMETIC_ERROR"}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__overshift_l.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__overshift_l.json new file mode 100644 index 0000000000000..61d23d2759dfc --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__overshift_l.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":4,"frame_id":0,"function_name":"overshift_l","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"errors"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U8","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_U8","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":20}}}},{"Instruction":{"gas_left":999999993,"instruction":"SHL","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":20}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"ExecutionError":"ARITHMETIC_ERROR"}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__overshift_r.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__overshift_r.json new file mode 100644 index 0000000000000..4222baf261255 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__overshift_r.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":5,"frame_id":0,"function_name":"overshift_r","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"errors"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U8","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_U8","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":20}}}},{"Instruction":{"gas_left":999999993,"instruction":"SHL","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":20}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"ExecutionError":"ARITHMETIC_ERROR"}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__underflow.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__underflow.json new file mode 100644 index 0000000000000..e0a985da8d255 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__errors__underflow.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":2,"frame_id":0,"function_name":"underflow","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"errors"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_U64","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":10}}}},{"Instruction":{"gas_left":999999994,"instruction":"SUB","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":10}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"ExecutionError":"ARITHMETIC_ERROR"}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__natives__get_orig_type_name_test.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__natives__get_orig_type_name_test.json new file mode 100644 index 0000000000000..dc92e46114e19 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__natives__get_orig_type_name_test.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":1,"frame_id":0,"function_name":"get_orig_type_name_test","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"type_name","name":"TypeName","type_args":[]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"natives"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":1000000000,"instruction":"CALL_GENERIC","pc":0,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":1,"frame_id":2,"function_name":"get_with_original_ids","is_native":true,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"type_name"},"parameters":[],"return_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"type_name","name":"TypeName","type_args":[]}}}],"type_instantiation":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"natives","name":"X","type_args":[]}}]},"gas_left":1000000000}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"CloseFrame":{"frame_id":2,"gas_left":999998834,"return_":[{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}]}},{"Instruction":{"gas_left":999998674,"instruction":"ST_LOC","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Instruction":{"gas_left":999998672,"instruction":"IMM_BORROW_LOC","pc":2,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999998672,"instruction":"CALL","pc":3,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":3,"frame_id":12,"function_name":"borrow_string","is_native":false,"locals_types":[{"ref_type":"Imm","type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"type_name","name":"TypeName","type_args":[]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"type_name"},"parameters":[{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}],"return_types":[{"ref_type":"Imm","type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"ascii","name":"String","type_args":[]}}}],"type_instantiation":[]},"gas_left":999998672}},{"Instruction":{"gas_left":999996390,"instruction":"MOVE_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[12,0]},"moved":true,"root_value_read":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999996388,"instruction":"IMM_BORROW_FIELD","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999995749,"instruction":"RET","pc":2,"type_parameters":[]}},{"CloseFrame":{"frame_id":12,"gas_left":999995749,"return_":[{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}]}},{"Instruction":{"gas_left":999995747,"instruction":"POP","pc":4,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999995587,"instruction":"MOVE_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":true,"root_value_read":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999995587,"instruction":"CALL","pc":6,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":6,"frame_id":27,"function_name":"into_string","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"type_name","name":"TypeName","type_args":[]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"type_name"},"parameters":[{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}],"return_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"ascii","name":"String","type_args":[]}}}],"type_instantiation":[]},"gas_left":999995587}},{"Instruction":{"gas_left":999993319,"instruction":"IMM_BORROW_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[27,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[27,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999993317,"instruction":"IMM_BORROW_FIELD","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[27,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[27,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999993161,"instruction":"READ_REF","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[27,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Local":[27,0]},0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}}}}},{"Instruction":{"gas_left":999992522,"instruction":"RET","pc":3,"type_parameters":[]}},{"CloseFrame":{"frame_id":27,"gas_left":999992522,"return_":[{"RuntimeValue":{"value":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}}}]}},{"Instruction":{"gas_left":999992520,"instruction":"POP","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}}}}},{"Instruction":{"gas_left":999991881,"instruction":"RET","pc":8,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999991881,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__natives__get_type_name_test.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__natives__get_type_name_test.json new file mode 100644 index 0000000000000..2c8e76ffa4fe2 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__natives__get_type_name_test.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":0,"frame_id":0,"function_name":"get_type_name_test","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"type_name","name":"TypeName","type_args":[]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"natives"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":1000000000,"instruction":"CALL_GENERIC","pc":0,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":0,"frame_id":2,"function_name":"get","is_native":true,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"type_name"},"parameters":[],"return_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"type_name","name":"TypeName","type_args":[]}}}],"type_instantiation":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"natives","name":"X","type_args":[]}}]},"gas_left":1000000000}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"CloseFrame":{"frame_id":2,"gas_left":999998834,"return_":[{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}]}},{"Instruction":{"gas_left":999998674,"instruction":"ST_LOC","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Instruction":{"gas_left":999998672,"instruction":"IMM_BORROW_LOC","pc":2,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999998672,"instruction":"CALL","pc":3,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":3,"frame_id":12,"function_name":"borrow_string","is_native":false,"locals_types":[{"ref_type":"Imm","type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"type_name","name":"TypeName","type_args":[]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"type_name"},"parameters":[{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}],"return_types":[{"ref_type":"Imm","type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"ascii","name":"String","type_args":[]}}}],"type_instantiation":[]},"gas_left":999998672}},{"Instruction":{"gas_left":999996390,"instruction":"MOVE_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[12,0]},"moved":true,"root_value_read":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999996388,"instruction":"IMM_BORROW_FIELD","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999995749,"instruction":"RET","pc":2,"type_parameters":[]}},{"CloseFrame":{"frame_id":12,"gas_left":999995749,"return_":[{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}]}},{"Instruction":{"gas_left":999995747,"instruction":"POP","pc":4,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999995587,"instruction":"MOVE_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":true,"root_value_read":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999995587,"instruction":"CALL","pc":6,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":6,"frame_id":27,"function_name":"into_string","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"type_name","name":"TypeName","type_args":[]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"type_name"},"parameters":[{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}],"return_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"ascii","name":"String","type_args":[]}}}],"type_instantiation":[]},"gas_left":999995587}},{"Instruction":{"gas_left":999993319,"instruction":"IMM_BORROW_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[27,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[27,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999993317,"instruction":"IMM_BORROW_FIELD","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[27,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[27,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999993161,"instruction":"READ_REF","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[27,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Local":[27,0]},0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}}}}},{"Instruction":{"gas_left":999992522,"instruction":"RET","pc":3,"type_parameters":[]}},{"CloseFrame":{"frame_id":27,"gas_left":999992522,"return_":[{"RuntimeValue":{"value":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}}}]}},{"Instruction":{"gas_left":999992520,"instruction":"POP","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}}}}},{"Instruction":{"gas_left":999991881,"instruction":"RET","pc":8,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999991881,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__packs__test_gen_pack_order.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__packs__test_gen_pack_order.json new file mode 100644 index 0000000000000..ace8cfe264562 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__packs__test_gen_pack_order.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":2,"frame_id":0,"function_name":"test_gen_pack_order","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"packs","name":"GenX","type_args":["u64","bool","u8"]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"packs"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_TRUE","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999994,"instruction":"LD_U8","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999841,"instruction":"PACK_GENERIC","pc":3,"type_parameters":["u64","bool","u8"]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}},{"Instruction":{"gas_left":999999741,"instruction":"ST_LOC","pc":4,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}}},{"Instruction":{"gas_left":999999739,"instruction":"IMM_BORROW_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}},{"Instruction":{"gas_left":999999737,"instruction":"IMM_BORROW_FIELD_GENERIC","pc":6,"type_parameters":["u64","bool","u8"]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},2]},"snapshot":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}},{"Instruction":{"gas_left":999999705,"instruction":"READ_REF","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},2]},"snapshot":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Local":[0,0]},2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999703,"instruction":"LD_U8","pc":8,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999639,"instruction":"EQ","pc":9,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999637,"instruction":"BR_FALSE","pc":10,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999635,"instruction":"BRANCH","pc":11,"type_parameters":[]}},{"Instruction":{"gas_left":999998996,"instruction":"RET","pc":16,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999998996,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__packs__test_gen_unpack_order.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__packs__test_gen_unpack_order.json new file mode 100644 index 0000000000000..0d2facae5bfd9 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__packs__test_gen_unpack_order.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":3,"frame_id":0,"function_name":"test_gen_unpack_order","is_native":false,"locals_types":[{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"packs"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_TRUE","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999994,"instruction":"LD_U8","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999841,"instruction":"PACK_GENERIC","pc":3,"type_parameters":["u64","bool","u8"]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}},{"Instruction":{"gas_left":999999688,"instruction":"UNPACK_GENERIC","pc":4,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999656,"instruction":"ST_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Write":{"location":{"Local":[0,3]},"root_value_after_write":{"RuntimeValue":{"value":0}}}}},{"Instruction":{"gas_left":999999624,"instruction":"ST_LOC","pc":6,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Write":{"location":{"Local":[0,2]},"root_value_after_write":{"RuntimeValue":{"value":true}}}}},{"Instruction":{"gas_left":999999592,"instruction":"ST_LOC","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Write":{"location":{"Local":[0,1]},"root_value_after_write":{"RuntimeValue":{"value":1}}}}},{"Instruction":{"gas_left":999999560,"instruction":"MOVE_LOC","pc":8,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,3]},"moved":true,"root_value_read":{"RuntimeValue":{"value":0}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999558,"instruction":"LD_U8","pc":9,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999494,"instruction":"EQ","pc":10,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999492,"instruction":"BR_FALSE","pc":11,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999460,"instruction":"MOVE_LOC","pc":12,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,2]},"moved":true,"root_value_read":{"RuntimeValue":{"value":true}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999428,"instruction":"ST_LOC","pc":13,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":true}}}}},{"Instruction":{"gas_left":999999426,"instruction":"BRANCH","pc":14,"type_parameters":[]}},{"Instruction":{"gas_left":999999394,"instruction":"MOVE_LOC","pc":17,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":true,"root_value_read":{"RuntimeValue":{"value":true}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999392,"instruction":"BR_FALSE","pc":18,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999390,"instruction":"BRANCH","pc":19,"type_parameters":[]}},{"Instruction":{"gas_left":999998751,"instruction":"RET","pc":22,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999998751,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__packs__test_pack_order.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__packs__test_pack_order.json new file mode 100644 index 0000000000000..5b176fc2625d8 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__packs__test_pack_order.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":0,"frame_id":0,"function_name":"test_pack_order","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"packs","name":"X","type_args":[]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"packs"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_TRUE","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999994,"instruction":"LD_U8","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999841,"instruction":"PACK","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}},{"Instruction":{"gas_left":999999741,"instruction":"ST_LOC","pc":4,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}}},{"Instruction":{"gas_left":999999739,"instruction":"IMM_BORROW_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}},{"Instruction":{"gas_left":999999737,"instruction":"IMM_BORROW_FIELD","pc":6,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},2]},"snapshot":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}},{"Instruction":{"gas_left":999999705,"instruction":"READ_REF","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},2]},"snapshot":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Local":[0,0]},2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999703,"instruction":"LD_U8","pc":8,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999639,"instruction":"EQ","pc":9,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999637,"instruction":"BR_FALSE","pc":10,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999635,"instruction":"BRANCH","pc":11,"type_parameters":[]}},{"Instruction":{"gas_left":999998996,"instruction":"RET","pc":16,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999998996,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__packs__test_unpack_order.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__packs__test_unpack_order.json new file mode 100644 index 0000000000000..e1f6e8893c539 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__packs__test_unpack_order.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":1,"frame_id":0,"function_name":"test_unpack_order","is_native":false,"locals_types":[{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"packs"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_TRUE","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999994,"instruction":"LD_U8","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999841,"instruction":"PACK","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}},{"Instruction":{"gas_left":999999688,"instruction":"UNPACK","pc":4,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999656,"instruction":"ST_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Write":{"location":{"Local":[0,3]},"root_value_after_write":{"RuntimeValue":{"value":0}}}}},{"Instruction":{"gas_left":999999624,"instruction":"ST_LOC","pc":6,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Write":{"location":{"Local":[0,2]},"root_value_after_write":{"RuntimeValue":{"value":true}}}}},{"Instruction":{"gas_left":999999592,"instruction":"ST_LOC","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Write":{"location":{"Local":[0,1]},"root_value_after_write":{"RuntimeValue":{"value":1}}}}},{"Instruction":{"gas_left":999999560,"instruction":"MOVE_LOC","pc":8,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,3]},"moved":true,"root_value_read":{"RuntimeValue":{"value":0}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999558,"instruction":"LD_U8","pc":9,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999494,"instruction":"EQ","pc":10,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999492,"instruction":"BR_FALSE","pc":11,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999460,"instruction":"MOVE_LOC","pc":12,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,2]},"moved":true,"root_value_read":{"RuntimeValue":{"value":true}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999428,"instruction":"ST_LOC","pc":13,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":true}}}}},{"Instruction":{"gas_left":999999426,"instruction":"BRANCH","pc":14,"type_parameters":[]}},{"Instruction":{"gas_left":999999394,"instruction":"MOVE_LOC","pc":17,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":true,"root_value_read":{"RuntimeValue":{"value":true}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999392,"instruction":"BR_FALSE","pc":18,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999390,"instruction":"BRANCH","pc":19,"type_parameters":[]}},{"Instruction":{"gas_left":999998751,"instruction":"RET","pc":22,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999998751,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__nested_struct_reference_mutation.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__nested_struct_reference_mutation.json new file mode 100644 index 0000000000000..ae31124c175af --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__nested_struct_reference_mutation.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":5,"frame_id":0,"function_name":"nested_struct_reference_mutation","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}}]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_U64","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999999994,"instruction":"LD_U64","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999999992,"instruction":"LD_U64","pc":3,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999999890,"instruction":"PACK_GENERIC","pc":4,"type_parameters":["u64"]}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}}}}},{"Instruction":{"gas_left":999999734,"instruction":"PACK_GENERIC","pc":5,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}}}}},{"Instruction":{"gas_left":999999524,"instruction":"PACK_GENERIC","pc":6,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}}]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999999384,"instruction":"ST_LOC","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Instruction":{"gas_left":999999382,"instruction":"IMM_BORROW_LOC","pc":8,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999999380,"instruction":"IMM_BORROW_FIELD_GENERIC","pc":9,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}}]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999999378,"instruction":"IMM_BORROW_FIELD_GENERIC","pc":10,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999999376,"instruction":"IMM_BORROW_FIELD_GENERIC","pc":11,"type_parameters":["u64"]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999999344,"instruction":"READ_REF","pc":12,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999999342,"instruction":"LD_U64","pc":13,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999999278,"instruction":"EQ","pc":14,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999276,"instruction":"BR_FALSE","pc":15,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999274,"instruction":"BRANCH","pc":16,"type_parameters":[]}},{"Instruction":{"gas_left":999999271,"instruction":"MUT_BORROW_LOC","pc":23,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999999269,"instruction":"MUT_BORROW_FIELD_GENERIC","pc":24,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}}]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999999269,"instruction":"CALL","pc":25,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":6,"frame_id":56,"function_name":"l0","is_native":false,"locals_types":[{"ref_type":"Mut","type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[{"MutRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}],"return_types":[],"type_instantiation":[]},"gas_left":999999269}},{"Instruction":{"gas_left":999996987,"instruction":"MOVE_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[56,0]},"moved":true,"root_value_read":{"MutRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999996985,"instruction":"MUT_BORROW_FIELD_GENERIC","pc":1,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}},{"Effect":{"Pop":{"MutRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999996985,"instruction":"CALL","pc":2,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":7,"frame_id":64,"function_name":"l1","is_native":false,"locals_types":[{"ref_type":"Mut","type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}],"return_types":[],"type_instantiation":[]},"gas_left":999996985}},{"Instruction":{"gas_left":999994703,"instruction":"MOVE_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[64,0]},"moved":true,"root_value_read":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999994701,"instruction":"MUT_BORROW_FIELD_GENERIC","pc":1,"type_parameters":["u64"]}},{"Effect":{"Pop":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999994701,"instruction":"CALL","pc":2,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":8,"frame_id":72,"function_name":"incr","is_native":false,"locals_types":[{"ref_type":"Mut","type_":"u64"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}],"return_types":[],"type_instantiation":[]},"gas_left":999994701}},{"Instruction":{"gas_left":999992419,"instruction":"COPY_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[72,0]},"moved":false,"root_value_read":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999992387,"instruction":"READ_REF","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999992385,"instruction":"LD_U64","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999992383,"instruction":"ADD","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999992367,"instruction":"MOVE_LOC","pc":4,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[72,0]},"moved":true,"root_value_read":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999992335,"instruction":"WRITE_REF","pc":5,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Write":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"root_value_after_write":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Instruction":{"gas_left":999991696,"instruction":"RET","pc":6,"type_parameters":[]}},{"CloseFrame":{"frame_id":72,"gas_left":999991696,"return_":[]}},{"Instruction":{"gas_left":999991057,"instruction":"RET","pc":3,"type_parameters":[]}},{"CloseFrame":{"frame_id":64,"gas_left":999991057,"return_":[]}},{"Instruction":{"gas_left":999990418,"instruction":"RET","pc":3,"type_parameters":[]}},{"CloseFrame":{"frame_id":56,"gas_left":999990418,"return_":[]}},{"Instruction":{"gas_left":999990416,"instruction":"IMM_BORROW_LOC","pc":26,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999990414,"instruction":"IMM_BORROW_FIELD_GENERIC","pc":27,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}}]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999990412,"instruction":"IMM_BORROW_FIELD_GENERIC","pc":28,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999990410,"instruction":"IMM_BORROW_FIELD_GENERIC","pc":29,"type_parameters":["u64"]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999990378,"instruction":"READ_REF","pc":30,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999990376,"instruction":"LD_U64","pc":31,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999990312,"instruction":"EQ","pc":32,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999990310,"instruction":"BR_FALSE","pc":33,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999990308,"instruction":"BRANCH","pc":34,"type_parameters":[]}},{"Instruction":{"gas_left":999989669,"instruction":"RET","pc":41,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999989669,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__pass_mut_assign_in_other_fn.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__pass_mut_assign_in_other_fn.json new file mode 100644 index 0000000000000..765d28809cbb8 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__pass_mut_assign_in_other_fn.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":0,"frame_id":0,"function_name":"pass_mut_assign_in_other_fn","is_native":false,"locals_types":[{"ref_type":null,"type_":"u64"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999966,"instruction":"ST_LOC","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":0}}}}},{"Instruction":{"gas_left":999999963,"instruction":"MUT_BORROW_LOC","pc":2,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":0}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":0}}}},{"Instruction":{"gas_left":999999961,"instruction":"LD_U64","pc":3,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999959,"instruction":"LD_U64","pc":4,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999999959,"instruction":"CALL","pc":5,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":1,"frame_id":14,"function_name":"assign_add","is_native":false,"locals_types":[{"ref_type":"Mut","type_":"u64"},{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"u64"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[{"MutRef":{"location":{"Local":[0,0]},"snapshot":0}},{"RuntimeValue":{"value":1}},{"RuntimeValue":{"value":2}}],"return_types":[],"type_instantiation":[]},"gas_left":999999959}},{"Instruction":{"gas_left":999995395,"instruction":"MOVE_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[14,1]},"moved":true,"root_value_read":{"RuntimeValue":{"value":1}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999995363,"instruction":"MOVE_LOC","pc":1,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[14,2]},"moved":true,"root_value_read":{"RuntimeValue":{"value":2}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999995361,"instruction":"ADD","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999995345,"instruction":"MOVE_LOC","pc":3,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[14,0]},"moved":true,"root_value_read":{"MutRef":{"location":{"Local":[0,0]},"snapshot":0}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":0}}}},{"Instruction":{"gas_left":999995313,"instruction":"WRITE_REF","pc":4,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":3}}}}},{"Instruction":{"gas_left":999994674,"instruction":"RET","pc":5,"type_parameters":[]}},{"CloseFrame":{"frame_id":14,"gas_left":999994674,"return_":[]}},{"Instruction":{"gas_left":999994642,"instruction":"MOVE_LOC","pc":6,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":true,"root_value_read":{"RuntimeValue":{"value":3}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999994640,"instruction":"LD_U64","pc":7,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999994576,"instruction":"EQ","pc":8,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999994574,"instruction":"BR_FALSE","pc":9,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999994572,"instruction":"BRANCH","pc":10,"type_parameters":[]}},{"Instruction":{"gas_left":999993933,"instruction":"RET","pc":13,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999993933,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__test_struct_borrow.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__test_struct_borrow.json new file mode 100644 index 0000000000000..6076e89f6399a --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__test_struct_borrow.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":2,"frame_id":0,"function_name":"test_struct_borrow","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"X","type_args":[]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_FALSE","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":false}}}},{"Instruction":{"gas_left":999999894,"instruction":"PACK","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":false}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}},{"Instruction":{"gas_left":999999826,"instruction":"ST_LOC","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}}},{"Instruction":{"gas_left":999999824,"instruction":"IMM_BORROW_LOC","pc":4,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}},{"Instruction":{"gas_left":999999822,"instruction":"IMM_BORROW_FIELD","pc":5,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},0]},"snapshot":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}},{"Instruction":{"gas_left":999999790,"instruction":"READ_REF","pc":6,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},0]},"snapshot":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Local":[0,0]},0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999788,"instruction":"LD_U64","pc":7,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999724,"instruction":"EQ","pc":8,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999722,"instruction":"BR_FALSE","pc":9,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999720,"instruction":"BRANCH","pc":10,"type_parameters":[]}},{"Instruction":{"gas_left":999999081,"instruction":"RET","pc":13,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999999081,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__test_vector_mut_borrow.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__test_vector_mut_borrow.json new file mode 100644 index 0000000000000..10cba16aeb1d3 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__test_vector_mut_borrow.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":3,"frame_id":0,"function_name":"test_vector_mut_borrow","is_native":false,"locals_types":[{"ref_type":null,"type_":"u64"},{"ref_type":"Mut","type_":"u64"},{"ref_type":null,"type_":{"vector":"u64"}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999966,"instruction":"ST_LOC","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":1}}}}},{"Instruction":{"gas_left":999999963,"instruction":"MUT_BORROW_LOC","pc":2,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":1}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}},{"Instruction":{"gas_left":999999947,"instruction":"ST_LOC","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}},{"Effect":{"Write":{"location":{"Local":[0,1]},"root_value_after_write":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}}},{"Instruction":{"gas_left":999999945,"instruction":"LD_U64","pc":4,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999999929,"instruction":"COPY_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,1]},"moved":false,"root_value_read":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}},{"Instruction":{"gas_left":999999897,"instruction":"WRITE_REF","pc":6,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":2}}}}},{"Instruction":{"gas_left":999999895,"instruction":"LD_U64","pc":7,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999999879,"instruction":"COPY_LOC","pc":8,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,1]},"moved":false,"root_value_read":{"MutRef":{"location":{"Local":[0,0]},"snapshot":2}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":2}}}},{"Instruction":{"gas_left":999999847,"instruction":"WRITE_REF","pc":9,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":2}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":3}}}}},{"Instruction":{"gas_left":999999831,"instruction":"COPY_LOC","pc":10,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,1]},"moved":false,"root_value_read":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}},{"Instruction":{"gas_left":999999799,"instruction":"READ_REF","pc":11,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":3}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999999714,"instruction":"VEC_PACK","pc":12,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Push":{"RuntimeValue":{"value":[3]}}}},{"Instruction":{"gas_left":999999698,"instruction":"ST_LOC","pc":13,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":[3]}}}},{"Effect":{"Write":{"location":{"Local":[0,2]},"root_value_after_write":{"RuntimeValue":{"value":[3]}}}}},{"Instruction":{"gas_left":999999696,"instruction":"LD_U64","pc":14,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999999693,"instruction":"MUT_BORROW_LOC","pc":15,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":[3]}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,2]},"snapshot":[3]}}}},{"Instruction":{"gas_left":999999691,"instruction":"LD_U64","pc":16,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999997788,"instruction":"VEC_MUT_BORROW","pc":17,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,2]},"snapshot":[3]}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Local":[0,2]},0]},"snapshot":[3]}}}},{"Instruction":{"gas_left":999997756,"instruction":"WRITE_REF","pc":18,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Indexed":[{"Local":[0,2]},0]},"snapshot":[3]}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Write":{"location":{"Indexed":[{"Local":[0,2]},0]},"root_value_after_write":{"RuntimeValue":{"value":[4]}}}}},{"Instruction":{"gas_left":999997754,"instruction":"IMM_BORROW_LOC","pc":19,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":[4]}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,2]},"snapshot":[4]}}}},{"Instruction":{"gas_left":999997752,"instruction":"LD_U64","pc":20,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999996417,"instruction":"VEC_IMM_BORROW","pc":21,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,2]},"snapshot":[4]}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,2]},0]},"snapshot":[4]}}}},{"Instruction":{"gas_left":999996385,"instruction":"READ_REF","pc":22,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,2]},0]},"snapshot":[4]}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Local":[0,2]},0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":[4]}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999996383,"instruction":"LD_U64","pc":23,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999996319,"instruction":"EQ","pc":24,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996317,"instruction":"BR_FALSE","pc":25,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996315,"instruction":"BRANCH","pc":26,"type_parameters":[]}},{"Instruction":{"gas_left":999996299,"instruction":"MOVE_LOC","pc":31,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,1]},"moved":true,"root_value_read":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}},{"Instruction":{"gas_left":999996267,"instruction":"READ_REF","pc":32,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":3}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999996265,"instruction":"LD_U64","pc":33,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999996201,"instruction":"EQ","pc":34,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996199,"instruction":"BR_FALSE","pc":35,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996197,"instruction":"BRANCH","pc":36,"type_parameters":[]}},{"Instruction":{"gas_left":999995558,"instruction":"RET","pc":39,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999995558,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__test_vector_mut_borrow_pop.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__test_vector_mut_borrow_pop.json new file mode 100644 index 0000000000000..40468e125fc3d --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/new_traces/0x1__references__test_vector_mut_borrow_pop.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":4,"frame_id":0,"function_name":"test_vector_mut_borrow_pop","is_native":false,"locals_types":[{"ref_type":null,"type_":"u64"},{"ref_type":"Mut","type_":"u64"},{"ref_type":null,"type_":{"vector":"u64"}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999966,"instruction":"ST_LOC","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":1}}}}},{"Instruction":{"gas_left":999999963,"instruction":"MUT_BORROW_LOC","pc":2,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":1}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}},{"Instruction":{"gas_left":999999947,"instruction":"ST_LOC","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}},{"Effect":{"Write":{"location":{"Local":[0,1]},"root_value_after_write":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}}},{"Instruction":{"gas_left":999999945,"instruction":"LD_U64","pc":4,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999999929,"instruction":"COPY_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,1]},"moved":false,"root_value_read":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}},{"Instruction":{"gas_left":999999897,"instruction":"WRITE_REF","pc":6,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":2}}}}},{"Instruction":{"gas_left":999999895,"instruction":"LD_U64","pc":7,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999999879,"instruction":"COPY_LOC","pc":8,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,1]},"moved":false,"root_value_read":{"MutRef":{"location":{"Local":[0,0]},"snapshot":2}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":2}}}},{"Instruction":{"gas_left":999999847,"instruction":"WRITE_REF","pc":9,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":2}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":3}}}}},{"Instruction":{"gas_left":999999831,"instruction":"MOVE_LOC","pc":10,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,1]},"moved":true,"root_value_read":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}},{"Instruction":{"gas_left":999999799,"instruction":"READ_REF","pc":11,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":3}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999999714,"instruction":"VEC_PACK","pc":12,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Push":{"RuntimeValue":{"value":[3]}}}},{"Instruction":{"gas_left":999999698,"instruction":"ST_LOC","pc":13,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":[3]}}}},{"Effect":{"Write":{"location":{"Local":[0,2]},"root_value_after_write":{"RuntimeValue":{"value":[3]}}}}},{"Instruction":{"gas_left":999999696,"instruction":"LD_U64","pc":14,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999999693,"instruction":"MUT_BORROW_LOC","pc":15,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":[3]}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,2]},"snapshot":[3]}}}},{"Instruction":{"gas_left":999999691,"instruction":"LD_U64","pc":16,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999997788,"instruction":"VEC_MUT_BORROW","pc":17,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,2]},"snapshot":[3]}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Local":[0,2]},0]},"snapshot":[3]}}}},{"Instruction":{"gas_left":999997756,"instruction":"WRITE_REF","pc":18,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Indexed":[{"Local":[0,2]},0]},"snapshot":[3]}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Write":{"location":{"Indexed":[{"Local":[0,2]},0]},"root_value_after_write":{"RuntimeValue":{"value":[4]}}}}},{"Instruction":{"gas_left":999997754,"instruction":"IMM_BORROW_LOC","pc":19,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":[4]}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,2]},"snapshot":[4]}}}},{"Instruction":{"gas_left":999997752,"instruction":"LD_U64","pc":20,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999996417,"instruction":"VEC_IMM_BORROW","pc":21,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,2]},"snapshot":[4]}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,2]},0]},"snapshot":[4]}}}},{"Instruction":{"gas_left":999996385,"instruction":"READ_REF","pc":22,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,2]},0]},"snapshot":[4]}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Local":[0,2]},0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":[4]}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999996383,"instruction":"LD_U64","pc":23,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999996319,"instruction":"EQ","pc":24,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996317,"instruction":"BR_FALSE","pc":25,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996315,"instruction":"BRANCH","pc":26,"type_parameters":[]}},{"Instruction":{"gas_left":999996312,"instruction":"MUT_BORROW_LOC","pc":29,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":[4]}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,2]},"snapshot":[4]}}}},{"Instruction":{"gas_left":999996084,"instruction":"VEC_POP_BACK","pc":30,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,2]},"snapshot":[4]}}}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999996082,"instruction":"LD_U64","pc":31,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999996018,"instruction":"EQ","pc":32,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996016,"instruction":"BR_FALSE","pc":33,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996014,"instruction":"BRANCH","pc":34,"type_parameters":[]}},{"Instruction":{"gas_left":999995375,"instruction":"RET","pc":37,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999995375,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__calls__test_call_order.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__calls__test_call_order.json new file mode 100644 index 0000000000000..925a0e8a072b2 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__calls__test_call_order.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":0,"frame_id":0,"function_name":"test_call_order","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_TRUE","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999994,"instruction":"LD_U8","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999994,"instruction":"CALL","pc":3,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":1,"frame_id":8,"function_name":"f_test_call_order","is_native":false,"locals_types":[{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[{"RuntimeValue":{"value":1}},{"RuntimeValue":{"value":true}},{"RuntimeValue":{"value":1}}],"return_types":[],"type_instantiation":[]},"gas_left":999999994}},{"Instruction":{"gas_left":999994823,"instruction":"RET","pc":0,"type_parameters":[]}},{"CloseFrame":{"frame_id":8,"gas_left":999994823,"return_":[]}},{"Instruction":{"gas_left":999994184,"instruction":"RET","pc":4,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999994184,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__calls__test_call_return_order.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__calls__test_call_return_order.json new file mode 100644 index 0000000000000..7a997c124db28 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__calls__test_call_return_order.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":4,"frame_id":0,"function_name":"test_call_return_order","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":1000000000,"instruction":"CALL","pc":0,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":3,"frame_id":2,"function_name":"f_test_return_order","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[],"return_types":[{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u8"}],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999998865,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999998863,"instruction":"LD_TRUE","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999998861,"instruction":"LD_U8","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999998222,"instruction":"RET","pc":3,"type_parameters":[]}},{"CloseFrame":{"frame_id":2,"gas_left":999998222,"return_":[{"RuntimeValue":{"value":1}},{"RuntimeValue":{"value":true}},{"RuntimeValue":{"value":0}}]}},{"Instruction":{"gas_left":999998222,"instruction":"CALL","pc":1,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":1,"frame_id":12,"function_name":"f_test_call_order","is_native":false,"locals_types":[{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[{"RuntimeValue":{"value":1}},{"RuntimeValue":{"value":true}},{"RuntimeValue":{"value":0}}],"return_types":[],"type_instantiation":[]},"gas_left":999998222}},{"Instruction":{"gas_left":999993051,"instruction":"RET","pc":0,"type_parameters":[]}},{"CloseFrame":{"frame_id":12,"gas_left":999993051,"return_":[]}},{"Instruction":{"gas_left":999992412,"instruction":"RET","pc":2,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999992412,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__calls__test_complex_nested_calls.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__calls__test_complex_nested_calls.json new file mode 100644 index 0000000000000..bc9e09f22d027 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__calls__test_complex_nested_calls.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":5,"frame_id":0,"function_name":"test_complex_nested_calls","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":1000000000,"instruction":"CALL","pc":0,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":6,"frame_id":2,"function_name":"f","is_native":false,"locals_types":[{"ref_type":null,"type_":"u64"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999998867,"instruction":"CALL","pc":0,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":7,"frame_id":4,"function_name":"k","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[],"return_types":[{"ref_type":null,"type_":"u64"}],"type_instantiation":[]},"gas_left":999998867}},{"Instruction":{"gas_left":999997732,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999997093,"instruction":"RET","pc":1,"type_parameters":[]}},{"CloseFrame":{"frame_id":4,"gas_left":999997093,"return_":[{"RuntimeValue":{"value":1}}]}},{"Instruction":{"gas_left":999997091,"instruction":"LD_U64","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999997089,"instruction":"ADD","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999997057,"instruction":"ST_LOC","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Write":{"location":{"Local":[2,0]},"root_value_after_write":{"RuntimeValue":{"value":2}}}}},{"Instruction":{"gas_left":999997025,"instruction":"COPY_LOC","pc":4,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[2,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":2}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999997023,"instruction":"LD_U64","pc":5,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999997021,"instruction":"GT","pc":6,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999997019,"instruction":"BR_FALSE","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996987,"instruction":"MOVE_LOC","pc":8,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[2,0]},"moved":true,"root_value_read":{"RuntimeValue":{"value":2}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999996987,"instruction":"CALL","pc":9,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":8,"frame_id":33,"function_name":"g","is_native":false,"locals_types":[{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[{"RuntimeValue":{"value":2}}],"return_types":[],"type_instantiation":[]},"gas_left":999996987}},{"Instruction":{"gas_left":999994689,"instruction":"MOVE_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[33,0]},"moved":true,"root_value_read":{"RuntimeValue":{"value":2}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999994686,"instruction":"CAST_U8","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999994654,"instruction":"ST_LOC","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Write":{"location":{"Local":[33,1]},"root_value_after_write":{"RuntimeValue":{"value":2}}}}},{"Instruction":{"gas_left":999994604,"instruction":"LD_CONST","pc":3,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":[1,2,3]}}}},{"Instruction":{"gas_left":999994602,"instruction":"POP","pc":4,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":[1,2,3]}}}},{"Instruction":{"gas_left":999994586,"instruction":"LD_CONST","pc":5,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999994584,"instruction":"LD_TRUE","pc":6,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999994482,"instruction":"PACK","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"}}}}},{"Instruction":{"gas_left":999994480,"instruction":"LD_TRUE","pc":8,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999994324,"instruction":"PACK_GENERIC","pc":9,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"calls","name":"X","type_args":[]}},"bool"]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"x":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"},"y":true},"type":"0x1::calls::Y<0x1::calls::X, bool>"}}}}},{"Instruction":{"gas_left":999994324,"instruction":"CALL_GENERIC","pc":10,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":10,"frame_id":62,"function_name":"i","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"calls","name":"Y","type_args":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"calls","name":"X","type_args":[]}},"bool"]}}},{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"u8"},{"ref_type":"Mut","type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[{"RuntimeValue":{"value":{"fields":{"x":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"},"y":true},"type":"0x1::calls::Y<0x1::calls::X, bool>"}}}],"return_types":[{"ref_type":null,"type_":"u8"}],"type_instantiation":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"calls","name":"X","type_args":[]}},"bool"]},"gas_left":999994324}},{"Instruction":{"gas_left":999991888,"instruction":"MOVE_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[62,0]},"moved":true,"root_value_read":{"RuntimeValue":{"value":{"fields":{"x":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"},"y":true},"type":"0x1::calls::Y<0x1::calls::X, bool>"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"x":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"},"y":true},"type":"0x1::calls::Y<0x1::calls::X, bool>"}}}}},{"Instruction":{"gas_left":999991732,"instruction":"UNPACK_GENERIC","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"x":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"},"y":true},"type":"0x1::calls::Y<0x1::calls::X, bool>"}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999991730,"instruction":"POP","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999991728,"instruction":"POP","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"x":1,"y":true},"type":"0x1::calls::X"}}}}},{"Instruction":{"gas_left":999991726,"instruction":"LD_U64","pc":4,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999991694,"instruction":"ST_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Write":{"location":{"Local":[62,1]},"root_value_after_write":{"RuntimeValue":{"value":1}}}}},{"Instruction":{"gas_left":999991692,"instruction":"IMM_BORROW_LOC","pc":6,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[62,1]},"moved":false,"root_value_read":{"RuntimeValue":{"value":1}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[62,1]},"snapshot":1}}}},{"Instruction":{"gas_left":999991660,"instruction":"READ_REF","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[62,1]},"snapshot":1}}}},{"Effect":{"Read":{"location":{"Local":[62,1]},"moved":false,"root_value_read":{"RuntimeValue":{"value":1}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999991658,"instruction":"POP","pc":8,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999991656,"instruction":"LD_U8","pc":9,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999991624,"instruction":"ST_LOC","pc":10,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Write":{"location":{"Local":[62,2]},"root_value_after_write":{"RuntimeValue":{"value":1}}}}},{"Instruction":{"gas_left":999991621,"instruction":"MUT_BORROW_LOC","pc":11,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[62,2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":1}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[62,2]},"snapshot":1}}}},{"Instruction":{"gas_left":999991605,"instruction":"ST_LOC","pc":12,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[62,2]},"snapshot":1}}}},{"Effect":{"Write":{"location":{"Local":[62,3]},"root_value_after_write":{"MutRef":{"location":{"Local":[62,2]},"snapshot":1}}}}},{"Instruction":{"gas_left":999991603,"instruction":"LD_U8","pc":13,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999991587,"instruction":"COPY_LOC","pc":14,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[62,3]},"moved":false,"root_value_read":{"MutRef":{"location":{"Local":[62,2]},"snapshot":1}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[62,2]},"snapshot":1}}}},{"Instruction":{"gas_left":999991555,"instruction":"WRITE_REF","pc":15,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[62,2]},"snapshot":1}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Write":{"location":{"Local":[62,2]},"root_value_after_write":{"RuntimeValue":{"value":2}}}}},{"Instruction":{"gas_left":999991539,"instruction":"MOVE_LOC","pc":16,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[62,3]},"moved":true,"root_value_read":{"MutRef":{"location":{"Local":[62,2]},"snapshot":2}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[62,2]},"snapshot":2}}}},{"Instruction":{"gas_left":999991507,"instruction":"READ_REF","pc":17,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[62,2]},"snapshot":2}}}},{"Effect":{"Read":{"location":{"Local":[62,2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":2}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999990868,"instruction":"RET","pc":18,"type_parameters":[]}},{"CloseFrame":{"frame_id":62,"gas_left":999990868,"return_":[{"RuntimeValue":{"value":2}}]}},{"Instruction":{"gas_left":999990868,"instruction":"CALL","pc":11,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":9,"frame_id":118,"function_name":"h","is_native":false,"locals_types":[{"ref_type":null,"type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[{"RuntimeValue":{"value":2}}],"return_types":[],"type_instantiation":[]},"gas_left":999990868}},{"Instruction":{"gas_left":999987963,"instruction":"RET","pc":0,"type_parameters":[]}},{"CloseFrame":{"frame_id":118,"gas_left":999987963,"return_":[]}},{"Instruction":{"gas_left":999987931,"instruction":"MOVE_LOC","pc":12,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[33,1]},"moved":true,"root_value_read":{"RuntimeValue":{"value":2}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999987931,"instruction":"CALL","pc":13,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":9,"frame_id":125,"function_name":"h","is_native":false,"locals_types":[{"ref_type":null,"type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[{"RuntimeValue":{"value":2}}],"return_types":[],"type_instantiation":[]},"gas_left":999987931}},{"Instruction":{"gas_left":999985026,"instruction":"RET","pc":0,"type_parameters":[]}},{"CloseFrame":{"frame_id":125,"gas_left":999985026,"return_":[]}},{"Instruction":{"gas_left":999984387,"instruction":"RET","pc":14,"type_parameters":[]}},{"CloseFrame":{"frame_id":33,"gas_left":999984387,"return_":[]}},{"Instruction":{"gas_left":999983748,"instruction":"RET","pc":10,"type_parameters":[]}},{"CloseFrame":{"frame_id":2,"gas_left":999983748,"return_":[]}},{"Instruction":{"gas_left":999983109,"instruction":"RET","pc":1,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999983109,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__calls__test_return_order.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__calls__test_return_order.json new file mode 100644 index 0000000000000..da67d038efda7 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__calls__test_return_order.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":2,"frame_id":0,"function_name":"test_return_order","is_native":false,"locals_types":[{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":1000000000,"instruction":"CALL","pc":0,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":3,"frame_id":2,"function_name":"f_test_return_order","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"calls"},"parameters":[],"return_types":[{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u8"}],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999998865,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999998863,"instruction":"LD_TRUE","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999998861,"instruction":"LD_U8","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999998222,"instruction":"RET","pc":3,"type_parameters":[]}},{"CloseFrame":{"frame_id":2,"gas_left":999998222,"return_":[{"RuntimeValue":{"value":1}},{"RuntimeValue":{"value":true}},{"RuntimeValue":{"value":0}}]}},{"Instruction":{"gas_left":999998190,"instruction":"ST_LOC","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Write":{"location":{"Local":[0,2]},"root_value_after_write":{"RuntimeValue":{"value":0}}}}},{"Instruction":{"gas_left":999998158,"instruction":"ST_LOC","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Write":{"location":{"Local":[0,1]},"root_value_after_write":{"RuntimeValue":{"value":true}}}}},{"Instruction":{"gas_left":999998126,"instruction":"ST_LOC","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":1}}}}},{"Instruction":{"gas_left":999998094,"instruction":"MOVE_LOC","pc":4,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,2]},"moved":true,"root_value_read":{"RuntimeValue":{"value":0}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999998092,"instruction":"LD_U8","pc":5,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999998028,"instruction":"EQ","pc":6,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999998026,"instruction":"BR_FALSE","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999998024,"instruction":"BRANCH","pc":8,"type_parameters":[]}},{"Instruction":{"gas_left":999997992,"instruction":"MOVE_LOC","pc":11,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,1]},"moved":true,"root_value_read":{"RuntimeValue":{"value":true}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999997990,"instruction":"BR_FALSE","pc":12,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999997988,"instruction":"BRANCH","pc":13,"type_parameters":[]}},{"Instruction":{"gas_left":999997349,"instruction":"RET","pc":16,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999997349,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__aborter.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__aborter.json new file mode 100644 index 0000000000000..689f2c8ec07bf --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__aborter.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":0,"frame_id":0,"function_name":"aborter","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"errors"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999999996,"instruction":"ABORT","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"ExecutionError":"ABORTED"}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__bad_cast.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__bad_cast.json new file mode 100644 index 0000000000000..c68db2b6df295 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__bad_cast.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":3,"frame_id":0,"function_name":"bad_cast","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"errors"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":256}}}},{"Instruction":{"gas_left":999999995,"instruction":"CAST_U8","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":256}}}},{"Effect":{"ExecutionError":"ARITHMETIC_ERROR"}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__div_0.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__div_0.json new file mode 100644 index 0000000000000..d901830856afa --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__div_0.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":1,"frame_id":0,"function_name":"div_0","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"errors"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_U64","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999992,"instruction":"DIV","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"ExecutionError":"ARITHMETIC_ERROR"}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__fail_during_abort.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__fail_during_abort.json new file mode 100644 index 0000000000000..2b27b5e5b212d --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__fail_during_abort.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":6,"frame_id":0,"function_name":"fail_during_abort","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"errors"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_U64","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999992,"instruction":"DIV","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"ExecutionError":"ARITHMETIC_ERROR"}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__overshift_l.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__overshift_l.json new file mode 100644 index 0000000000000..61d23d2759dfc --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__overshift_l.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":4,"frame_id":0,"function_name":"overshift_l","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"errors"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U8","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_U8","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":20}}}},{"Instruction":{"gas_left":999999993,"instruction":"SHL","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":20}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"ExecutionError":"ARITHMETIC_ERROR"}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__overshift_r.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__overshift_r.json new file mode 100644 index 0000000000000..4222baf261255 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__overshift_r.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":5,"frame_id":0,"function_name":"overshift_r","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"errors"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U8","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_U8","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":20}}}},{"Instruction":{"gas_left":999999993,"instruction":"SHL","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":20}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"ExecutionError":"ARITHMETIC_ERROR"}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__underflow.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__underflow.json new file mode 100644 index 0000000000000..e0a985da8d255 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__errors__underflow.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":2,"frame_id":0,"function_name":"underflow","is_native":false,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"errors"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_U64","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":10}}}},{"Instruction":{"gas_left":999999994,"instruction":"SUB","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":10}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"ExecutionError":"ARITHMETIC_ERROR"}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__natives__get_orig_type_name_test.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__natives__get_orig_type_name_test.json new file mode 100644 index 0000000000000..dc92e46114e19 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__natives__get_orig_type_name_test.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":1,"frame_id":0,"function_name":"get_orig_type_name_test","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"type_name","name":"TypeName","type_args":[]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"natives"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":1000000000,"instruction":"CALL_GENERIC","pc":0,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":1,"frame_id":2,"function_name":"get_with_original_ids","is_native":true,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"type_name"},"parameters":[],"return_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"type_name","name":"TypeName","type_args":[]}}}],"type_instantiation":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"natives","name":"X","type_args":[]}}]},"gas_left":1000000000}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"CloseFrame":{"frame_id":2,"gas_left":999998834,"return_":[{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}]}},{"Instruction":{"gas_left":999998674,"instruction":"ST_LOC","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Instruction":{"gas_left":999998672,"instruction":"IMM_BORROW_LOC","pc":2,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999998672,"instruction":"CALL","pc":3,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":3,"frame_id":12,"function_name":"borrow_string","is_native":false,"locals_types":[{"ref_type":"Imm","type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"type_name","name":"TypeName","type_args":[]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"type_name"},"parameters":[{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}],"return_types":[{"ref_type":"Imm","type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"ascii","name":"String","type_args":[]}}}],"type_instantiation":[]},"gas_left":999998672}},{"Instruction":{"gas_left":999996390,"instruction":"MOVE_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[12,0]},"moved":true,"root_value_read":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999996388,"instruction":"IMM_BORROW_FIELD","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999995749,"instruction":"RET","pc":2,"type_parameters":[]}},{"CloseFrame":{"frame_id":12,"gas_left":999995749,"return_":[{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}]}},{"Instruction":{"gas_left":999995747,"instruction":"POP","pc":4,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999995587,"instruction":"MOVE_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":true,"root_value_read":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999995587,"instruction":"CALL","pc":6,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":6,"frame_id":27,"function_name":"into_string","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"type_name","name":"TypeName","type_args":[]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"type_name"},"parameters":[{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}],"return_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"ascii","name":"String","type_args":[]}}}],"type_instantiation":[]},"gas_left":999995587}},{"Instruction":{"gas_left":999993319,"instruction":"IMM_BORROW_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[27,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[27,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999993317,"instruction":"IMM_BORROW_FIELD","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[27,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[27,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999993161,"instruction":"READ_REF","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[27,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Local":[27,0]},0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}}}}},{"Instruction":{"gas_left":999992522,"instruction":"RET","pc":3,"type_parameters":[]}},{"CloseFrame":{"frame_id":27,"gas_left":999992522,"return_":[{"RuntimeValue":{"value":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}}}]}},{"Instruction":{"gas_left":999992520,"instruction":"POP","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}}}}},{"Instruction":{"gas_left":999991881,"instruction":"RET","pc":8,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999991881,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__natives__get_type_name_test.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__natives__get_type_name_test.json new file mode 100644 index 0000000000000..2c8e76ffa4fe2 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__natives__get_type_name_test.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":0,"frame_id":0,"function_name":"get_type_name_test","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"type_name","name":"TypeName","type_args":[]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"natives"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":1000000000,"instruction":"CALL_GENERIC","pc":0,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":0,"frame_id":2,"function_name":"get","is_native":true,"locals_types":[],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"type_name"},"parameters":[],"return_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"type_name","name":"TypeName","type_args":[]}}}],"type_instantiation":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"natives","name":"X","type_args":[]}}]},"gas_left":1000000000}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"CloseFrame":{"frame_id":2,"gas_left":999998834,"return_":[{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}]}},{"Instruction":{"gas_left":999998674,"instruction":"ST_LOC","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Instruction":{"gas_left":999998672,"instruction":"IMM_BORROW_LOC","pc":2,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999998672,"instruction":"CALL","pc":3,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":3,"frame_id":12,"function_name":"borrow_string","is_native":false,"locals_types":[{"ref_type":"Imm","type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"type_name","name":"TypeName","type_args":[]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"type_name"},"parameters":[{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}],"return_types":[{"ref_type":"Imm","type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"ascii","name":"String","type_args":[]}}}],"type_instantiation":[]},"gas_left":999998672}},{"Instruction":{"gas_left":999996390,"instruction":"MOVE_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[12,0]},"moved":true,"root_value_read":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999996388,"instruction":"IMM_BORROW_FIELD","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999995749,"instruction":"RET","pc":2,"type_parameters":[]}},{"CloseFrame":{"frame_id":12,"gas_left":999995749,"return_":[{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}]}},{"Instruction":{"gas_left":999995747,"instruction":"POP","pc":4,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999995587,"instruction":"MOVE_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":true,"root_value_read":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999995587,"instruction":"CALL","pc":6,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":6,"frame_id":27,"function_name":"into_string","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"type_name","name":"TypeName","type_args":[]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"type_name"},"parameters":[{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}],"return_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"ascii","name":"String","type_args":[]}}}],"type_instantiation":[]},"gas_left":999995587}},{"Instruction":{"gas_left":999993319,"instruction":"IMM_BORROW_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[27,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[27,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999993317,"instruction":"IMM_BORROW_FIELD","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[27,0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[27,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Instruction":{"gas_left":999993161,"instruction":"READ_REF","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[27,0]},0]},"snapshot":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Local":[27,0]},0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"name":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}},"type":"0x1::type_name::TypeName"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}}}}},{"Instruction":{"gas_left":999992522,"instruction":"RET","pc":3,"type_parameters":[]}},{"CloseFrame":{"frame_id":27,"gas_left":999992522,"return_":[{"RuntimeValue":{"value":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}}}]}},{"Instruction":{"gas_left":999992520,"instruction":"POP","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"bytes":[48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,48,49,58,58,110,97,116,105,118,101,115,58,58,88]},"type":"0x1::ascii::String"}}}}},{"Instruction":{"gas_left":999991881,"instruction":"RET","pc":8,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999991881,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__packs__test_gen_pack_order.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__packs__test_gen_pack_order.json new file mode 100644 index 0000000000000..ace8cfe264562 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__packs__test_gen_pack_order.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":2,"frame_id":0,"function_name":"test_gen_pack_order","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"packs","name":"GenX","type_args":["u64","bool","u8"]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"packs"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_TRUE","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999994,"instruction":"LD_U8","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999841,"instruction":"PACK_GENERIC","pc":3,"type_parameters":["u64","bool","u8"]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}},{"Instruction":{"gas_left":999999741,"instruction":"ST_LOC","pc":4,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}}},{"Instruction":{"gas_left":999999739,"instruction":"IMM_BORROW_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}},{"Instruction":{"gas_left":999999737,"instruction":"IMM_BORROW_FIELD_GENERIC","pc":6,"type_parameters":["u64","bool","u8"]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},2]},"snapshot":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}},{"Instruction":{"gas_left":999999705,"instruction":"READ_REF","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},2]},"snapshot":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Local":[0,0]},2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999703,"instruction":"LD_U8","pc":8,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999639,"instruction":"EQ","pc":9,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999637,"instruction":"BR_FALSE","pc":10,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999635,"instruction":"BRANCH","pc":11,"type_parameters":[]}},{"Instruction":{"gas_left":999998996,"instruction":"RET","pc":16,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999998996,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__packs__test_gen_unpack_order.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__packs__test_gen_unpack_order.json new file mode 100644 index 0000000000000..0d2facae5bfd9 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__packs__test_gen_unpack_order.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":3,"frame_id":0,"function_name":"test_gen_unpack_order","is_native":false,"locals_types":[{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"packs"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_TRUE","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999994,"instruction":"LD_U8","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999841,"instruction":"PACK_GENERIC","pc":3,"type_parameters":["u64","bool","u8"]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}},{"Instruction":{"gas_left":999999688,"instruction":"UNPACK_GENERIC","pc":4,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::GenX"}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999656,"instruction":"ST_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Write":{"location":{"Local":[0,3]},"root_value_after_write":{"RuntimeValue":{"value":0}}}}},{"Instruction":{"gas_left":999999624,"instruction":"ST_LOC","pc":6,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Write":{"location":{"Local":[0,2]},"root_value_after_write":{"RuntimeValue":{"value":true}}}}},{"Instruction":{"gas_left":999999592,"instruction":"ST_LOC","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Write":{"location":{"Local":[0,1]},"root_value_after_write":{"RuntimeValue":{"value":1}}}}},{"Instruction":{"gas_left":999999560,"instruction":"MOVE_LOC","pc":8,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,3]},"moved":true,"root_value_read":{"RuntimeValue":{"value":0}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999558,"instruction":"LD_U8","pc":9,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999494,"instruction":"EQ","pc":10,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999492,"instruction":"BR_FALSE","pc":11,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999460,"instruction":"MOVE_LOC","pc":12,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,2]},"moved":true,"root_value_read":{"RuntimeValue":{"value":true}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999428,"instruction":"ST_LOC","pc":13,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":true}}}}},{"Instruction":{"gas_left":999999426,"instruction":"BRANCH","pc":14,"type_parameters":[]}},{"Instruction":{"gas_left":999999394,"instruction":"MOVE_LOC","pc":17,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":true,"root_value_read":{"RuntimeValue":{"value":true}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999392,"instruction":"BR_FALSE","pc":18,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999390,"instruction":"BRANCH","pc":19,"type_parameters":[]}},{"Instruction":{"gas_left":999998751,"instruction":"RET","pc":22,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999998751,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__packs__test_pack_order.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__packs__test_pack_order.json new file mode 100644 index 0000000000000..5b176fc2625d8 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__packs__test_pack_order.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":0,"frame_id":0,"function_name":"test_pack_order","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"packs","name":"X","type_args":[]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"packs"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_TRUE","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999994,"instruction":"LD_U8","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999841,"instruction":"PACK","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}},{"Instruction":{"gas_left":999999741,"instruction":"ST_LOC","pc":4,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}}},{"Instruction":{"gas_left":999999739,"instruction":"IMM_BORROW_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}},{"Instruction":{"gas_left":999999737,"instruction":"IMM_BORROW_FIELD","pc":6,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},2]},"snapshot":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}},{"Instruction":{"gas_left":999999705,"instruction":"READ_REF","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},2]},"snapshot":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Local":[0,0]},2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999703,"instruction":"LD_U8","pc":8,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999639,"instruction":"EQ","pc":9,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999637,"instruction":"BR_FALSE","pc":10,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999635,"instruction":"BRANCH","pc":11,"type_parameters":[]}},{"Instruction":{"gas_left":999998996,"instruction":"RET","pc":16,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999998996,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__packs__test_unpack_order.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__packs__test_unpack_order.json new file mode 100644 index 0000000000000..e1f6e8893c539 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__packs__test_unpack_order.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":1,"frame_id":0,"function_name":"test_unpack_order","is_native":false,"locals_types":[{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"bool"},{"ref_type":null,"type_":"u8"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"packs"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_TRUE","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999994,"instruction":"LD_U8","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999841,"instruction":"PACK","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}},{"Instruction":{"gas_left":999999688,"instruction":"UNPACK","pc":4,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":true,"pos2":0},"type":"0x1::packs::X"}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999656,"instruction":"ST_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Write":{"location":{"Local":[0,3]},"root_value_after_write":{"RuntimeValue":{"value":0}}}}},{"Instruction":{"gas_left":999999624,"instruction":"ST_LOC","pc":6,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Write":{"location":{"Local":[0,2]},"root_value_after_write":{"RuntimeValue":{"value":true}}}}},{"Instruction":{"gas_left":999999592,"instruction":"ST_LOC","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Write":{"location":{"Local":[0,1]},"root_value_after_write":{"RuntimeValue":{"value":1}}}}},{"Instruction":{"gas_left":999999560,"instruction":"MOVE_LOC","pc":8,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,3]},"moved":true,"root_value_read":{"RuntimeValue":{"value":0}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999558,"instruction":"LD_U8","pc":9,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999494,"instruction":"EQ","pc":10,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999492,"instruction":"BR_FALSE","pc":11,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999460,"instruction":"MOVE_LOC","pc":12,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,2]},"moved":true,"root_value_read":{"RuntimeValue":{"value":true}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999428,"instruction":"ST_LOC","pc":13,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":true}}}}},{"Instruction":{"gas_left":999999426,"instruction":"BRANCH","pc":14,"type_parameters":[]}},{"Instruction":{"gas_left":999999394,"instruction":"MOVE_LOC","pc":17,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":true,"root_value_read":{"RuntimeValue":{"value":true}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999392,"instruction":"BR_FALSE","pc":18,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999390,"instruction":"BRANCH","pc":19,"type_parameters":[]}},{"Instruction":{"gas_left":999998751,"instruction":"RET","pc":22,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999998751,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__nested_struct_reference_mutation.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__nested_struct_reference_mutation.json new file mode 100644 index 0000000000000..ae31124c175af --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__nested_struct_reference_mutation.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":5,"frame_id":0,"function_name":"nested_struct_reference_mutation","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}}]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_U64","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999999994,"instruction":"LD_U64","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999999992,"instruction":"LD_U64","pc":3,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999999890,"instruction":"PACK_GENERIC","pc":4,"type_parameters":["u64"]}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}}}}},{"Instruction":{"gas_left":999999734,"instruction":"PACK_GENERIC","pc":5,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}}}}},{"Instruction":{"gas_left":999999524,"instruction":"PACK_GENERIC","pc":6,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}}]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999999384,"instruction":"ST_LOC","pc":7,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Instruction":{"gas_left":999999382,"instruction":"IMM_BORROW_LOC","pc":8,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999999380,"instruction":"IMM_BORROW_FIELD_GENERIC","pc":9,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}}]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999999378,"instruction":"IMM_BORROW_FIELD_GENERIC","pc":10,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999999376,"instruction":"IMM_BORROW_FIELD_GENERIC","pc":11,"type_parameters":["u64"]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999999344,"instruction":"READ_REF","pc":12,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999999342,"instruction":"LD_U64","pc":13,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999999278,"instruction":"EQ","pc":14,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999276,"instruction":"BR_FALSE","pc":15,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999274,"instruction":"BRANCH","pc":16,"type_parameters":[]}},{"Instruction":{"gas_left":999999271,"instruction":"MUT_BORROW_LOC","pc":23,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999999269,"instruction":"MUT_BORROW_FIELD_GENERIC","pc":24,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}}]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999999269,"instruction":"CALL","pc":25,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":6,"frame_id":56,"function_name":"l0","is_native":false,"locals_types":[{"ref_type":"Mut","type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[{"MutRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}],"return_types":[],"type_instantiation":[]},"gas_left":999999269}},{"Instruction":{"gas_left":999996987,"instruction":"MOVE_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[56,0]},"moved":true,"root_value_read":{"MutRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999996985,"instruction":"MUT_BORROW_FIELD_GENERIC","pc":1,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}},{"Effect":{"Pop":{"MutRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999996985,"instruction":"CALL","pc":2,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":7,"frame_id":64,"function_name":"l1","is_native":false,"locals_types":[{"ref_type":"Mut","type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}],"return_types":[],"type_instantiation":[]},"gas_left":999996985}},{"Instruction":{"gas_left":999994703,"instruction":"MOVE_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[64,0]},"moved":true,"root_value_read":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999994701,"instruction":"MUT_BORROW_FIELD_GENERIC","pc":1,"type_parameters":["u64"]}},{"Effect":{"Pop":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999994701,"instruction":"CALL","pc":2,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":8,"frame_id":72,"function_name":"incr","is_native":false,"locals_types":[{"ref_type":"Mut","type_":"u64"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}],"return_types":[],"type_instantiation":[]},"gas_left":999994701}},{"Instruction":{"gas_left":999992419,"instruction":"COPY_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[72,0]},"moved":false,"root_value_read":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999992387,"instruction":"READ_REF","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999992385,"instruction":"LD_U64","pc":2,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999992383,"instruction":"ADD","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999992367,"instruction":"MOVE_LOC","pc":4,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[72,0]},"moved":true,"root_value_read":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999992335,"instruction":"WRITE_REF","pc":5,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":3,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Write":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"root_value_after_write":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Instruction":{"gas_left":999991696,"instruction":"RET","pc":6,"type_parameters":[]}},{"CloseFrame":{"frame_id":72,"gas_left":999991696,"return_":[]}},{"Instruction":{"gas_left":999991057,"instruction":"RET","pc":3,"type_parameters":[]}},{"CloseFrame":{"frame_id":64,"gas_left":999991057,"return_":[]}},{"Instruction":{"gas_left":999990418,"instruction":"RET","pc":3,"type_parameters":[]}},{"CloseFrame":{"frame_id":56,"gas_left":999990418,"return_":[]}},{"Instruction":{"gas_left":999990416,"instruction":"IMM_BORROW_LOC","pc":26,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999990414,"instruction":"IMM_BORROW_FIELD_GENERIC","pc":27,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}}]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999990412,"instruction":"IMM_BORROW_FIELD_GENERIC","pc":28,"type_parameters":[{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"Y","type_args":["u64"]}}]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999990410,"instruction":"IMM_BORROW_FIELD_GENERIC","pc":29,"type_parameters":["u64"]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Instruction":{"gas_left":999990378,"instruction":"READ_REF","pc":30,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"snapshot":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Indexed":[{"Indexed":[{"Local":[0,0]},1]},1]},0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"x":1,"y":{"fields":{"x":2,"y":{"fields":{"x":4,"y":4},"type":"0x1::references::Y"}},"type":"0x1::references::Y<0x1::references::Y>"}},"type":"0x1::references::Y<0x1::references::Y<0x1::references::Y>>"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999990376,"instruction":"LD_U64","pc":31,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999990312,"instruction":"EQ","pc":32,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999990310,"instruction":"BR_FALSE","pc":33,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999990308,"instruction":"BRANCH","pc":34,"type_parameters":[]}},{"Instruction":{"gas_left":999989669,"instruction":"RET","pc":41,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999989669,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__pass_mut_assign_in_other_fn.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__pass_mut_assign_in_other_fn.json new file mode 100644 index 0000000000000..765d28809cbb8 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__pass_mut_assign_in_other_fn.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":0,"frame_id":0,"function_name":"pass_mut_assign_in_other_fn","is_native":false,"locals_types":[{"ref_type":null,"type_":"u64"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999999966,"instruction":"ST_LOC","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":0}}}}},{"Instruction":{"gas_left":999999963,"instruction":"MUT_BORROW_LOC","pc":2,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":0}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":0}}}},{"Instruction":{"gas_left":999999961,"instruction":"LD_U64","pc":3,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999959,"instruction":"LD_U64","pc":4,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999999959,"instruction":"CALL","pc":5,"type_parameters":[]}},{"OpenFrame":{"frame":{"binary_member_index":1,"frame_id":14,"function_name":"assign_add","is_native":false,"locals_types":[{"ref_type":"Mut","type_":"u64"},{"ref_type":null,"type_":"u64"},{"ref_type":null,"type_":"u64"}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[{"MutRef":{"location":{"Local":[0,0]},"snapshot":0}},{"RuntimeValue":{"value":1}},{"RuntimeValue":{"value":2}}],"return_types":[],"type_instantiation":[]},"gas_left":999999959}},{"Instruction":{"gas_left":999995395,"instruction":"MOVE_LOC","pc":0,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[14,1]},"moved":true,"root_value_read":{"RuntimeValue":{"value":1}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999995363,"instruction":"MOVE_LOC","pc":1,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[14,2]},"moved":true,"root_value_read":{"RuntimeValue":{"value":2}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999995361,"instruction":"ADD","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999995345,"instruction":"MOVE_LOC","pc":3,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[14,0]},"moved":true,"root_value_read":{"MutRef":{"location":{"Local":[0,0]},"snapshot":0}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":0}}}},{"Instruction":{"gas_left":999995313,"instruction":"WRITE_REF","pc":4,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":0}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":3}}}}},{"Instruction":{"gas_left":999994674,"instruction":"RET","pc":5,"type_parameters":[]}},{"CloseFrame":{"frame_id":14,"gas_left":999994674,"return_":[]}},{"Instruction":{"gas_left":999994642,"instruction":"MOVE_LOC","pc":6,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":true,"root_value_read":{"RuntimeValue":{"value":3}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999994640,"instruction":"LD_U64","pc":7,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999994576,"instruction":"EQ","pc":8,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999994574,"instruction":"BR_FALSE","pc":9,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999994572,"instruction":"BRANCH","pc":10,"type_parameters":[]}},{"Instruction":{"gas_left":999993933,"instruction":"RET","pc":13,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999993933,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__test_struct_borrow.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__test_struct_borrow.json new file mode 100644 index 0000000000000..6076e89f6399a --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__test_struct_borrow.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":2,"frame_id":0,"function_name":"test_struct_borrow","is_native":false,"locals_types":[{"ref_type":null,"type_":{"struct":{"address":"0000000000000000000000000000000000000000000000000000000000000001","module":"references","name":"X","type_args":[]}}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999996,"instruction":"LD_FALSE","pc":1,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":false}}}},{"Instruction":{"gas_left":999999894,"instruction":"PACK","pc":2,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":false}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}},{"Instruction":{"gas_left":999999826,"instruction":"ST_LOC","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}}},{"Instruction":{"gas_left":999999824,"instruction":"IMM_BORROW_LOC","pc":4,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}},{"Instruction":{"gas_left":999999822,"instruction":"IMM_BORROW_FIELD","pc":5,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,0]},"snapshot":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},0]},"snapshot":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}},{"Instruction":{"gas_left":999999790,"instruction":"READ_REF","pc":6,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,0]},0]},"snapshot":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Local":[0,0]},0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":{"fields":{"pos0":1,"pos1":false},"type":"0x1::references::X"}}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999788,"instruction":"LD_U64","pc":7,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999724,"instruction":"EQ","pc":8,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999722,"instruction":"BR_FALSE","pc":9,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999999720,"instruction":"BRANCH","pc":10,"type_parameters":[]}},{"Instruction":{"gas_left":999999081,"instruction":"RET","pc":13,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999999081,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__test_vector_mut_borrow.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__test_vector_mut_borrow.json new file mode 100644 index 0000000000000..10cba16aeb1d3 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__test_vector_mut_borrow.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":3,"frame_id":0,"function_name":"test_vector_mut_borrow","is_native":false,"locals_types":[{"ref_type":null,"type_":"u64"},{"ref_type":"Mut","type_":"u64"},{"ref_type":null,"type_":{"vector":"u64"}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999966,"instruction":"ST_LOC","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":1}}}}},{"Instruction":{"gas_left":999999963,"instruction":"MUT_BORROW_LOC","pc":2,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":1}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}},{"Instruction":{"gas_left":999999947,"instruction":"ST_LOC","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}},{"Effect":{"Write":{"location":{"Local":[0,1]},"root_value_after_write":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}}},{"Instruction":{"gas_left":999999945,"instruction":"LD_U64","pc":4,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999999929,"instruction":"COPY_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,1]},"moved":false,"root_value_read":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}},{"Instruction":{"gas_left":999999897,"instruction":"WRITE_REF","pc":6,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":2}}}}},{"Instruction":{"gas_left":999999895,"instruction":"LD_U64","pc":7,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999999879,"instruction":"COPY_LOC","pc":8,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,1]},"moved":false,"root_value_read":{"MutRef":{"location":{"Local":[0,0]},"snapshot":2}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":2}}}},{"Instruction":{"gas_left":999999847,"instruction":"WRITE_REF","pc":9,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":2}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":3}}}}},{"Instruction":{"gas_left":999999831,"instruction":"COPY_LOC","pc":10,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,1]},"moved":false,"root_value_read":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}},{"Instruction":{"gas_left":999999799,"instruction":"READ_REF","pc":11,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":3}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999999714,"instruction":"VEC_PACK","pc":12,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Push":{"RuntimeValue":{"value":[3]}}}},{"Instruction":{"gas_left":999999698,"instruction":"ST_LOC","pc":13,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":[3]}}}},{"Effect":{"Write":{"location":{"Local":[0,2]},"root_value_after_write":{"RuntimeValue":{"value":[3]}}}}},{"Instruction":{"gas_left":999999696,"instruction":"LD_U64","pc":14,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999999693,"instruction":"MUT_BORROW_LOC","pc":15,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":[3]}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,2]},"snapshot":[3]}}}},{"Instruction":{"gas_left":999999691,"instruction":"LD_U64","pc":16,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999997788,"instruction":"VEC_MUT_BORROW","pc":17,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,2]},"snapshot":[3]}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Local":[0,2]},0]},"snapshot":[3]}}}},{"Instruction":{"gas_left":999997756,"instruction":"WRITE_REF","pc":18,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Indexed":[{"Local":[0,2]},0]},"snapshot":[3]}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Write":{"location":{"Indexed":[{"Local":[0,2]},0]},"root_value_after_write":{"RuntimeValue":{"value":[4]}}}}},{"Instruction":{"gas_left":999997754,"instruction":"IMM_BORROW_LOC","pc":19,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":[4]}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,2]},"snapshot":[4]}}}},{"Instruction":{"gas_left":999997752,"instruction":"LD_U64","pc":20,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999996417,"instruction":"VEC_IMM_BORROW","pc":21,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,2]},"snapshot":[4]}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,2]},0]},"snapshot":[4]}}}},{"Instruction":{"gas_left":999996385,"instruction":"READ_REF","pc":22,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,2]},0]},"snapshot":[4]}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Local":[0,2]},0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":[4]}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999996383,"instruction":"LD_U64","pc":23,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999996319,"instruction":"EQ","pc":24,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996317,"instruction":"BR_FALSE","pc":25,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996315,"instruction":"BRANCH","pc":26,"type_parameters":[]}},{"Instruction":{"gas_left":999996299,"instruction":"MOVE_LOC","pc":31,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,1]},"moved":true,"root_value_read":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}},{"Instruction":{"gas_left":999996267,"instruction":"READ_REF","pc":32,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":3}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999996265,"instruction":"LD_U64","pc":33,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999996201,"instruction":"EQ","pc":34,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996199,"instruction":"BR_FALSE","pc":35,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996197,"instruction":"BRANCH","pc":36,"type_parameters":[]}},{"Instruction":{"gas_left":999995558,"instruction":"RET","pc":39,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999995558,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__test_vector_mut_borrow_pop.json b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__test_vector_mut_borrow_pop.json new file mode 100644 index 0000000000000..40468e125fc3d --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/saved_traces/0x1__references__test_vector_mut_borrow_pop.json @@ -0,0 +1 @@ +{"events":[{"OpenFrame":{"frame":{"binary_member_index":4,"frame_id":0,"function_name":"test_vector_mut_borrow_pop","is_native":false,"locals_types":[{"ref_type":null,"type_":"u64"},{"ref_type":"Mut","type_":"u64"},{"ref_type":null,"type_":{"vector":"u64"}}],"module":{"address":"0000000000000000000000000000000000000000000000000000000000000001","name":"references"},"parameters":[],"return_types":[],"type_instantiation":[]},"gas_left":1000000000}},{"Instruction":{"gas_left":999999998,"instruction":"LD_U64","pc":0,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":1}}}},{"Instruction":{"gas_left":999999966,"instruction":"ST_LOC","pc":1,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":1}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":1}}}}},{"Instruction":{"gas_left":999999963,"instruction":"MUT_BORROW_LOC","pc":2,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":1}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}},{"Instruction":{"gas_left":999999947,"instruction":"ST_LOC","pc":3,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}},{"Effect":{"Write":{"location":{"Local":[0,1]},"root_value_after_write":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}}},{"Instruction":{"gas_left":999999945,"instruction":"LD_U64","pc":4,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":2}}}},{"Instruction":{"gas_left":999999929,"instruction":"COPY_LOC","pc":5,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,1]},"moved":false,"root_value_read":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}},{"Instruction":{"gas_left":999999897,"instruction":"WRITE_REF","pc":6,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":1}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":2}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":2}}}}},{"Instruction":{"gas_left":999999895,"instruction":"LD_U64","pc":7,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999999879,"instruction":"COPY_LOC","pc":8,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,1]},"moved":false,"root_value_read":{"MutRef":{"location":{"Local":[0,0]},"snapshot":2}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":2}}}},{"Instruction":{"gas_left":999999847,"instruction":"WRITE_REF","pc":9,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":2}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Write":{"location":{"Local":[0,0]},"root_value_after_write":{"RuntimeValue":{"value":3}}}}},{"Instruction":{"gas_left":999999831,"instruction":"MOVE_LOC","pc":10,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,1]},"moved":true,"root_value_read":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}},{"Instruction":{"gas_left":999999799,"instruction":"READ_REF","pc":11,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,0]},"snapshot":3}}}},{"Effect":{"Read":{"location":{"Local":[0,0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":3}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":3}}}},{"Instruction":{"gas_left":999999714,"instruction":"VEC_PACK","pc":12,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":3}}}},{"Effect":{"Push":{"RuntimeValue":{"value":[3]}}}},{"Instruction":{"gas_left":999999698,"instruction":"ST_LOC","pc":13,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":[3]}}}},{"Effect":{"Write":{"location":{"Local":[0,2]},"root_value_after_write":{"RuntimeValue":{"value":[3]}}}}},{"Instruction":{"gas_left":999999696,"instruction":"LD_U64","pc":14,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999999693,"instruction":"MUT_BORROW_LOC","pc":15,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":[3]}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,2]},"snapshot":[3]}}}},{"Instruction":{"gas_left":999999691,"instruction":"LD_U64","pc":16,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999997788,"instruction":"VEC_MUT_BORROW","pc":17,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,2]},"snapshot":[3]}}}},{"Effect":{"Push":{"MutRef":{"location":{"Indexed":[{"Local":[0,2]},0]},"snapshot":[3]}}}},{"Instruction":{"gas_left":999997756,"instruction":"WRITE_REF","pc":18,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Indexed":[{"Local":[0,2]},0]},"snapshot":[3]}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Write":{"location":{"Indexed":[{"Local":[0,2]},0]},"root_value_after_write":{"RuntimeValue":{"value":[4]}}}}},{"Instruction":{"gas_left":999997754,"instruction":"IMM_BORROW_LOC","pc":19,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":[4]}}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Local":[0,2]},"snapshot":[4]}}}},{"Instruction":{"gas_left":999997752,"instruction":"LD_U64","pc":20,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":0}}}},{"Instruction":{"gas_left":999996417,"instruction":"VEC_IMM_BORROW","pc":21,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":0}}}},{"Effect":{"Pop":{"ImmRef":{"location":{"Local":[0,2]},"snapshot":[4]}}}},{"Effect":{"Push":{"ImmRef":{"location":{"Indexed":[{"Local":[0,2]},0]},"snapshot":[4]}}}},{"Instruction":{"gas_left":999996385,"instruction":"READ_REF","pc":22,"type_parameters":[]}},{"Effect":{"Pop":{"ImmRef":{"location":{"Indexed":[{"Local":[0,2]},0]},"snapshot":[4]}}}},{"Effect":{"Read":{"location":{"Indexed":[{"Local":[0,2]},0]},"moved":false,"root_value_read":{"RuntimeValue":{"value":[4]}}}}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999996383,"instruction":"LD_U64","pc":23,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999996319,"instruction":"EQ","pc":24,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996317,"instruction":"BR_FALSE","pc":25,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996315,"instruction":"BRANCH","pc":26,"type_parameters":[]}},{"Instruction":{"gas_left":999996312,"instruction":"MUT_BORROW_LOC","pc":29,"type_parameters":[]}},{"Effect":{"Read":{"location":{"Local":[0,2]},"moved":false,"root_value_read":{"RuntimeValue":{"value":[4]}}}}},{"Effect":{"Push":{"MutRef":{"location":{"Local":[0,2]},"snapshot":[4]}}}},{"Instruction":{"gas_left":999996084,"instruction":"VEC_POP_BACK","pc":30,"type_parameters":[]}},{"Effect":{"Pop":{"MutRef":{"location":{"Local":[0,2]},"snapshot":[4]}}}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999996082,"instruction":"LD_U64","pc":31,"type_parameters":[]}},{"Effect":{"Push":{"RuntimeValue":{"value":4}}}},{"Instruction":{"gas_left":999996018,"instruction":"EQ","pc":32,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Pop":{"RuntimeValue":{"value":4}}}},{"Effect":{"Push":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996016,"instruction":"BR_FALSE","pc":33,"type_parameters":[]}},{"Effect":{"Pop":{"RuntimeValue":{"value":true}}}},{"Instruction":{"gas_left":999996014,"instruction":"BRANCH","pc":34,"type_parameters":[]}},{"Instruction":{"gas_left":999995375,"instruction":"RET","pc":37,"type_parameters":[]}},{"CloseFrame":{"frame_id":0,"gas_left":999995375,"return_":[]}}],"version":1} \ No newline at end of file diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/calls.move b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/calls.move new file mode 100644 index 0000000000000..ead1f74736bd8 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/calls.move @@ -0,0 +1,75 @@ +module 0x1::calls { + const A: u64 = 1; + const B: vector = vector[1,2,3]; + + public struct X has drop { + x: u64, + y: bool, + } + + public struct Y has drop { + x: A, + y: B, + } + + #[test] + fun test_call_order() { + let a = 1u64; + let b = true; + let c = 1u8; + f_test_call_order(a, b, c); + } + + fun f_test_call_order(_x: u64, _b: bool, _c: u8) { } + + #[test] + fun test_return_order() { + let (a, b, c) = f_test_return_order(); + assert!(c == 0u8, a); + assert!(b, a); + } + + fun f_test_return_order(): (u64, bool, u8) { + (1, true, 0u8) + } + + #[test] + fun test_call_return_order() { + let (a, b, c) = f_test_return_order(); + f_test_call_order(a, b, c); + } + + #[test] + fun test_complex_nested_calls() { + f() + } + + fun f() { + let x = k() + 1; + if (x > 0) g(x) + } + + fun k(): u64 { + 1 + } + + fun g(x: u64) { + let y = x as u8; + let _ = B; + let x = X { x: A, y: true }; + let j = Y { x, y: true }; + h(i(j)); + h(y) + } + + fun h(_y: u8) { } + + fun i(y: Y): u8 { + let Y { x: _, y: _ } = y; + let x = &1; + let _h = *x; + let j = &mut 1; + *j = 2; + *j + } +} diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/errors.move b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/errors.move new file mode 100644 index 0000000000000..442cdfd188f0f --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/errors.move @@ -0,0 +1,44 @@ +module 0x1::errors { + #[test] + #[expected_failure] + fun aborter() { + let x = 1 + 1; + abort x + } + + #[test] + #[expected_failure] + fun div_0() { + 1/0; + } + + #[test] + #[expected_failure] + fun underflow() { + 1 - 10; + } + + #[test] + #[expected_failure] + fun bad_cast() { + 256u64 as u8; + } + + #[test] + #[expected_failure] + fun overshift_l() { + 1u8 << 20; + } + + #[test] + #[expected_failure] + fun overshift_r() { + 1u8 << 20; + } + + #[test] + #[expected_failure] + fun fail_during_abort() { + abort 1/0 + } +} diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/natives.move b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/natives.move new file mode 100644 index 0000000000000..d48eb04a24e77 --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/natives.move @@ -0,0 +1,17 @@ +module 0x1::natives { + public struct X() has drop; + + #[test] + fun get_type_name_test() { + let x = std::type_name::get(); + let _t = x.borrow_string(); + let _t = x.into_string(); + } + + #[test] + fun get_orig_type_name_test() { + let x = std::type_name::get_with_original_ids(); + let _t = x.borrow_string(); + let _t = x.into_string(); + } +} diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/references.move b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/references.move new file mode 100644 index 0000000000000..c1451a4ad97fe --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/references.move @@ -0,0 +1,74 @@ +module 0x1::references { + + #[test] + fun pass_mut_assign_in_other_fn() { + let x = 1; + let y = 2; + let mut res = 0; + assign_add(&mut res, x, y); + assert!(res == 3); + } + + fun assign_add(x: &mut u64, a: u64, b: u64) { + *x = a + b; + } + + public struct X(u64, bool) has drop; + + #[test] + fun test_struct_borrow() { + let x = &X(1, false); + assert!(x.0 == 1); + } + + #[test] + fun test_vector_mut_borrow() { + let x = &mut 1; + *x = 2; + *x = 3; + let mut y = vector[*x]; + *y.borrow_mut(0) = 4; + assert!(*y.borrow(0) == 4, 42); + assert!(*x == 3, 42) + } + + #[test] + fun test_vector_mut_borrow_pop() { + let x = &mut 1; + *x = 2; + *x = 3; + let mut y = vector[*x]; + *y.borrow_mut(0) = 4; + assert!(*y.borrow(0) == 4); + assert!(y.pop_back() == 4); + } + + public struct Y { + x: u64, + y: T, + } has drop; + + #[test] + fun nested_struct_reference_mutation() { + let mut y = Y { x: 1, y: Y { x: 2, y: Y { x: 3, y: 4 } } }; + + assert!(y.y.y.x == 3, y.y.y.x); + + let l0 = &mut y.y; + l0(l0); + assert!(y.y.y.x == 4, y.y.y.x); + } + + fun l0(x: &mut Y>){ + l1(&mut x.y); + } + + fun l1(x: &mut Y){ + incr(&mut x.x); + } + + fun incr(a: &mut u64) { + *a = *a + 1; + } +} + diff --git a/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/structs.move b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/structs.move new file mode 100644 index 0000000000000..93bbf2851810f --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_tests/tracing-unit-tests/sources/structs.move @@ -0,0 +1,44 @@ +module 0x1::packs { + + public struct X(u64, bool, u8) has drop; + + public struct GenX(A, B, C) has drop; + + #[test] + fun test_pack_order() { + let a = 1; + let b = true; + let c = 0u8; + let x = X(a, b, c); + assert!(x.2 == 0u8, x.0); + } + + #[test] + fun test_unpack_order() { + let a = 1; + let b = true; + let c = 0u8; + let x = X(a, b, c); + let X(a, b, c) = x; + assert!(c == 0u8 && b, a); + } + + #[test] + fun test_gen_pack_order() { + let a = 1u64; + let b = true; + let c = 0u8; + let x = GenX(a, b, c); + assert!(x.2 == 0u8, x.0); + } + + #[test] + fun test_gen_unpack_order() { + let a = 1u64; + let b = true; + let c = 0u8; + let x = GenX(a, b, c); + let GenX(a, b, c) = x; + assert!(c == 0u8 && b, a); + } +} diff --git a/external-crates/move/crates/move-cli/tests/tracing_testsuite.rs b/external-crates/move/crates/move-cli/tests/tracing_testsuite.rs new file mode 100644 index 0000000000000..cfe95ec02ac3a --- /dev/null +++ b/external-crates/move/crates/move-cli/tests/tracing_testsuite.rs @@ -0,0 +1,26 @@ +// Copyright (c) The Diem Core Contributors +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use std::path::Path; + +#[allow(unused_variables)] +fn run_all(args_path: &Path) -> datatest_stable::Result<()> { + #[cfg(feature = "gas-profiler")] + { + use move_cli::sandbox::commands::test; + use std::path::PathBuf; + let cli_exe = env!("CARGO_BIN_EXE_move"); + let use_temp_dir = !args_path.parent().unwrap().join("NO_TEMPDIR").exists(); + test::run_one( + args_path, + &PathBuf::from(cli_exe), + /* use_temp_dir */ use_temp_dir, + /* track_cov */ false, + )?; + } + Ok(()) +} + +// runs all the tests +datatest_stable::harness!(run_all, "tests/tracing_tests", r"args\.txt$"); diff --git a/external-crates/move/crates/move-compiler/src/cfgir/ast.rs b/external-crates/move/crates/move-compiler/src/cfgir/ast.rs index 06570cd22092f..72507507b8142 100644 --- a/external-crates/move/crates/move-compiler/src/cfgir/ast.rs +++ b/external-crates/move/crates/move-compiler/src/cfgir/ast.rs @@ -194,7 +194,7 @@ impl AstDebug for Program { let Program { modules, info: _ } = self; for (m, mdef) in modules.key_cloned_iter() { - w.write(&format!("module {}", m)); + w.write(format!("module {}", m)); w.block(|w| mdef.ast_debug(w)); w.new_line(); } @@ -217,7 +217,7 @@ impl AstDebug for ModuleDefinition { } = self; warning_filter.ast_debug(w); if let Some(n) = package_name { - w.writeln(&format!("{}", n)) + w.writeln(format!("{}", n)) } attributes.ast_debug(w); w.writeln(match target_kind { @@ -229,9 +229,9 @@ impl AstDebug for ModuleDefinition { } => "dependency module", TargetKind::External => "external module", }); - w.writeln(&format!("dependency order #{}", dependency_order)); + w.writeln(format!("dependency order #{}", dependency_order)); for (mident, _loc) in friends.key_cloned_iter() { - w.write(&format!("friend {};", mident)); + w.write(format!("friend {};", mident)); w.new_line(); } for sdef in structs.key_cloned_iter() { @@ -268,7 +268,7 @@ impl AstDebug for (ConstantName, &Constant) { ) = self; warning_filter.ast_debug(w); attributes.ast_debug(w); - w.write(&format!("const#{index} {name}:")); + w.write(format!("const#{index} {name}:")); signature.ast_debug(w); w.write(" = "); match value { @@ -283,14 +283,14 @@ impl AstDebug for MoveValue { fn ast_debug(&self, w: &mut AstWriter) { use MoveValue as V; match self { - V::U8(u) => w.write(&format!("{}", u)), - V::U16(u) => w.write(&format!("{}", u)), - V::U32(u) => w.write(&format!("{}", u)), - V::U64(u) => w.write(&format!("{}", u)), - V::U128(u) => w.write(&format!("{}", u)), - V::U256(u) => w.write(&format!("{}", u)), - V::Bool(b) => w.write(&format!("{}", b)), - V::Address(a) => w.write(&format!("{}", a)), + V::U8(u) => w.write(format!("{}", u)), + V::U16(u) => w.write(format!("{}", u)), + V::U32(u) => w.write(format!("{}", u)), + V::U64(u) => w.write(format!("{}", u)), + V::U128(u) => w.write(format!("{}", u)), + V::U256(u) => w.write(format!("{}", u)), + V::Bool(b) => w.write(format!("{}", b)), + V::Address(a) => w.write(format!("{}", a)), V::Vector(vs) => { w.write("vector["); w.comma(vs, |w, v| v.ast_debug(w)); @@ -326,12 +326,12 @@ impl AstDebug for (FunctionName, &Function) { compiled_visibility.ast_debug(w); w.write(") "); if entry.is_some() { - w.write(&format!("{} ", ENTRY_MODIFIER)); + w.write(format!("{} ", ENTRY_MODIFIER)); } if let FunctionBody_::Native = &body.value { w.write("native "); } - w.write(&format!("fun#{index} {name}")); + w.write(format!("fun#{index} {name}")); signature.ast_debug(w); match &body.value { FunctionBody_::Defined { @@ -344,7 +344,7 @@ impl AstDebug for (FunctionName, &Function) { w.indent(4, |w| { w.list(locals, ",", |w, (_, v, (mut_, st))| { mut_.ast_debug(w); - w.write(&format!("{}: ", v)); + w.write(format!("{}: ", v)); st.ast_debug(w); true }) @@ -353,11 +353,11 @@ impl AstDebug for (FunctionName, &Function) { w.writeln("block info:"); w.indent(4, |w| { for (lbl, info) in block_info { - w.writeln(&format!("{lbl}: ")); + w.writeln(format!("{lbl}: ")); info.ast_debug(w); } }); - w.writeln(&format!("start={}", start.0)); + w.writeln(format!("start={}", start.0)); w.new_line(); blocks.ast_debug(w); }), @@ -378,7 +378,7 @@ impl AstDebug for BasicBlocks { impl AstDebug for (&Label, &BasicBlock) { fn ast_debug(&self, w: &mut AstWriter) { - w.write(&format!("label {}:", (self.0).0)); + w.write(format!("label {}:", (self.0).0)); w.indent(4, |w| w.semicolon(self.1, |w, cmd| cmd.ast_debug(w))) } } @@ -398,7 +398,7 @@ impl AstDebug for LoopInfo { is_loop_stmt, loop_end, } = self; - w.write(&format!( + w.write(format!( "{{ is_loop_stmt: {}, end: ", if *is_loop_stmt { "true" } else { "false" } )); @@ -411,7 +411,7 @@ impl AstDebug for LoopEnd { fn ast_debug(&self, w: &mut AstWriter) { match self { LoopEnd::Unused => w.write("unused end"), - LoopEnd::Target(lbl) => w.write(&format!("{lbl}")), + LoopEnd::Target(lbl) => w.write(format!("{lbl}")), } } } diff --git a/external-crates/move/crates/move-compiler/src/cfgir/cfg.rs b/external-crates/move/crates/move-compiler/src/cfgir/cfg.rs index 90bd08b8d92c7..82c791dbd083e 100644 --- a/external-crates/move/crates/move-compiler/src/cfgir/cfg.rs +++ b/external-crates/move/crates/move-compiler/src/cfgir/cfg.rs @@ -697,7 +697,7 @@ impl<'a, T: Deref> AstDebug for ReverseCFG<'a, T> { loop_heads, } = self; w.writeln("--ReverseBlockCFG--"); - w.writeln(&format!("terminal: {}", terminal)); + w.writeln(format!("terminal: {}", terminal)); ast_debug_cfg( w, traversal_order[0], @@ -722,8 +722,8 @@ fn ast_debug_cfg<'a>( w.write("successor_map:"); w.indent(4, |w| { for (lbl, nexts) in successor_map { - w.write(&format!("{} => [", lbl)); - w.comma(nexts, |w, next| w.write(&format!("{}", next))); + w.write(format!("{} => [", lbl)); + w.comma(nexts, |w, next| w.write(format!("{}", next))); w.writeln("]") } }); @@ -731,8 +731,8 @@ fn ast_debug_cfg<'a>( w.write("predecessor_map:"); w.indent(4, |w| { for (lbl, nexts) in predecessor_map { - w.write(&format!("{} <= [", lbl)); - w.comma(nexts, |w, next| w.write(&format!("{}", next))); + w.write(format!("{} <= [", lbl)); + w.comma(nexts, |w, next| w.write(format!("{}", next))); w.writeln("]") } }); @@ -740,7 +740,7 @@ fn ast_debug_cfg<'a>( w.write("traversal:"); w.indent(4, |w| { for (cur, next) in traversal { - w.writeln(&format!("{} => {}", cur, next)) + w.writeln(format!("{} => {}", cur, next)) } }); @@ -748,7 +748,7 @@ fn ast_debug_cfg<'a>( w.indent(4, |w| { for (loop_head, back_edge_predecessors) in loop_heads { for pred in back_edge_predecessors { - w.writeln(&format!( + w.writeln(format!( "loop head: {}. back edge predecessor: {}", loop_head, pred )) @@ -756,7 +756,7 @@ fn ast_debug_cfg<'a>( } }); - w.writeln(&format!("start: {}", start)); + w.writeln(format!("start: {}", start)); w.writeln("blocks:"); w.indent(4, |w| blocks.ast_debug(w)); } diff --git a/external-crates/move/crates/move-compiler/src/cfgir/translate.rs b/external-crates/move/crates/move-compiler/src/cfgir/translate.rs index 16a40e326dbd4..93ec88edf4396 100644 --- a/external-crates/move/crates/move-compiler/src/cfgir/translate.rs +++ b/external-crates/move/crates/move-compiler/src/cfgir/translate.rs @@ -7,7 +7,7 @@ use crate::{ self, ast::{self as G, BasicBlock, BasicBlocks, BlockInfo}, cfg::{ImmForwardCFG, MutForwardCFG}, - visitor::{CFGIRVisitorConstructor, CFGIRVisitorContext}, + visitor::{CFGIRVisitor, CFGIRVisitorConstructor, CFGIRVisitorContext}, }, diag, diagnostics::Diagnostics, @@ -970,8 +970,7 @@ fn visit_program(context: &mut Context, prog: &mut G::Program) { AbsintVisitor.visit(context.env, prog); - for visitor in &context.env.visitors().cfgir { - let mut v = visitor.borrow_mut(); + for v in &context.env.visitors().cfgir { v.visit(context.env, prog) } } @@ -1048,8 +1047,7 @@ impl<'a> CFGIRVisitorContext for AbsintVisitorContext<'a> { infinite_loop_starts: &infinite_loop_starts, }; let mut ds = Diagnostics::new(); - for visitor in &self.env.visitors().abs_int { - let mut v = visitor.borrow_mut(); + for v in &self.env.visitors().abs_int { ds.extend(v.verify(self.env, &function_context, &cfg)); } self.env.add_diags(ds); diff --git a/external-crates/move/crates/move-compiler/src/cfgir/visitor.rs b/external-crates/move/crates/move-compiler/src/cfgir/visitor.rs index 5d877bdfefa95..1ec55b3cd3f82 100644 --- a/external-crates/move/crates/move-compiler/src/cfgir/visitor.rs +++ b/external-crates/move/crates/move-compiler/src/cfgir/visitor.rs @@ -23,8 +23,8 @@ use move_proc_macros::growing_stack; pub type AbsIntVisitorObj = Box; pub type CFGIRVisitorObj = Box; -pub trait CFGIRVisitor { - fn visit(&mut self, env: &mut CompilationEnv, program: &G::Program); +pub trait CFGIRVisitor: Send + Sync { + fn visit(&self, env: &mut CompilationEnv, program: &G::Program); fn visitor(self) -> Visitor where @@ -34,9 +34,9 @@ pub trait CFGIRVisitor { } } -pub trait AbstractInterpreterVisitor { +pub trait AbstractInterpreterVisitor: Send + Sync { fn verify( - &mut self, + &self, env: &CompilationEnv, context: &CFGContext, cfg: &ImmForwardCFG, @@ -54,12 +54,12 @@ pub trait AbstractInterpreterVisitor { // CFGIR visitor //************************************************************************************************** -pub trait CFGIRVisitorConstructor { +pub trait CFGIRVisitorConstructor: Send { type Context<'a>: Sized + CFGIRVisitorContext; fn context<'a>(env: &'a mut CompilationEnv, program: &G::Program) -> Self::Context<'a>; - fn visit(&mut self, env: &mut CompilationEnv, program: &G::Program) { + fn visit(env: &mut CompilationEnv, program: &G::Program) { let mut context = Self::context(env, program); context.visit(program); } @@ -321,9 +321,9 @@ impl From for CFGIRVisitorObj { } } -impl CFGIRVisitor for V { - fn visit(&mut self, env: &mut CompilationEnv, program: &G::Program) { - self.visit(env, program) +impl CFGIRVisitor for V { + fn visit(&self, env: &mut CompilationEnv, program: &G::Program) { + Self::visit(env, program) } } @@ -454,12 +454,7 @@ pub trait SimpleAbsIntConstructor: Sized { init_state: &mut as SimpleAbsInt>::State, ) -> Option>; - fn verify( - &mut self, - env: &CompilationEnv, - context: &CFGContext, - cfg: &ImmForwardCFG, - ) -> Diagnostics { + fn verify(env: &CompilationEnv, context: &CFGContext, cfg: &ImmForwardCFG) -> Diagnostics { let mut locals = context .locals .key_cloned_iter() @@ -758,14 +753,20 @@ impl TransferFunctions for V { } impl AbstractInterpreter for V {} -impl AbstractInterpreterVisitor for V { +impl From for AbsIntVisitorObj { + fn from(value: V) -> Self { + Box::new(value) + } +} + +impl AbstractInterpreterVisitor for V { fn verify( - &mut self, + &self, env: &CompilationEnv, context: &CFGContext, cfg: &ImmForwardCFG, ) -> Diagnostics { - SimpleAbsIntConstructor::verify(self, env, context, cfg) + ::verify(env, context, cfg) } } diff --git a/external-crates/move/crates/move-compiler/src/command_line/compiler.rs b/external-crates/move/crates/move-compiler/src/command_line/compiler.rs index a3189505cc2ca..5ab1794628a81 100644 --- a/external-crates/move/crates/move-compiler/src/command_line/compiler.rs +++ b/external-crates/move/crates/move-compiler/src/command_line/compiler.rs @@ -99,6 +99,7 @@ enum PassResult { #[derive(Clone)] pub struct FullyCompiledProgram { pub files: MappedFiles, + pub comments: CommentMap, pub parser: parser::ast::Program, pub expansion: expansion::ast::Program, pub naming: naming::ast::Program, @@ -650,7 +651,7 @@ pub fn construct_pre_compiled_lib, NamedAddress: Into()?; - let (_comments, stepped) = match pprog_and_comments_res { + let (comments, stepped) = match pprog_and_comments_res { Err((_pass, errors)) => return Ok(Err((files, errors))), Ok(res) => res, }; @@ -662,6 +663,7 @@ pub fn construct_pre_compiled_lib, NamedAddress: Into Ok(Err((files, errors))), Ok(PassResult::Compilation(compiled, _)) => Ok(Ok(FullyCompiledProgram { files, + comments, parser: hook.take_parser_ast(), expansion: hook.take_expansion_ast(), naming: hook.take_naming_ast(), diff --git a/external-crates/move/crates/move-compiler/src/diagnostics/mod.rs b/external-crates/move/crates/move-compiler/src/diagnostics/mod.rs index aba6a32541993..3c7cbe46de462 100644 --- a/external-crates/move/crates/move-compiler/src/diagnostics/mod.rs +++ b/external-crates/move/crates/move-compiler/src/diagnostics/mod.rs @@ -1212,13 +1212,13 @@ impl AstDebug for WarningFilters { for (prefix, filters) in &self.filters { let prefix_str = prefix.unwrap_or(known_attributes::DiagnosticAttribute::ALLOW); match filters { - UnprefixedWarningFilters::All => w.write(&format!( + UnprefixedWarningFilters::All => w.write(format!( "#[{}({})]", prefix_str, WarningFilter::All(*prefix).to_str().unwrap(), )), UnprefixedWarningFilters::Specified { categories, codes } => { - w.write(&format!("#[{}(", prefix_str)); + w.write(format!("#[{}(", prefix_str)); let items = categories .iter() .map(|(cat, n)| WarningFilter::Category { diff --git a/external-crates/move/crates/move-compiler/src/expansion/alias_map_builder.rs b/external-crates/move/crates/move-compiler/src/expansion/alias_map_builder.rs index 30ed68580776e..9b8a7b44c103e 100644 --- a/external-crates/move/crates/move-compiler/src/expansion/alias_map_builder.rs +++ b/external-crates/move/crates/move-compiler/src/expansion/alias_map_builder.rs @@ -2,8 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ - expansion::ast::{self as E, ModuleIdent}, - expansion::translate::ModuleMemberKind, + expansion::{ + ast::{self as E, ModuleIdent}, + name_validation::ModuleMemberKind, + }, parser::ast::{self as P}, shared::{unique_map::UniqueMap, *}, }; diff --git a/external-crates/move/crates/move-compiler/src/expansion/ast.rs b/external-crates/move/crates/move-compiler/src/expansion/ast.rs index 2b804dab8a395..1fb7b04aad199 100644 --- a/external-crates/move/crates/move-compiler/src/expansion/ast.rs +++ b/external-crates/move/crates/move-compiler/src/expansion/ast.rs @@ -973,7 +973,7 @@ impl AstDebug for Program { fn ast_debug(&self, w: &mut AstWriter) { let Program { modules } = self; for (m, mdef) in modules.key_cloned_iter() { - w.write(&format!("module {}", m)); + w.write(format!("module {}", m)); w.block(|w| mdef.ast_debug(w)); w.new_line(); } @@ -999,7 +999,7 @@ impl AstDebug for ExplicitUseFun { function.ast_debug(w); w.write(" as "); ty.ast_debug(w); - w.writeln(&format!(".{method};")); + w.writeln(format!(".{method};")); } } @@ -1022,7 +1022,7 @@ impl AstDebug for ImplicitUseFunCandidate { ImplicitUseFunKind::UseAlias { used: false } => "#unused", ImplicitUseFunKind::FunctionDeclaration => "#fundecl", }; - w.writeln(&format!("implcit{kind_str}#use fun {m}::{n};")); + w.writeln(format!("implcit{kind_str}#use fun {m}::{n};")); } } @@ -1045,9 +1045,9 @@ impl AstDebug for AttributeValue_ { fn ast_debug(&self, w: &mut AstWriter) { match self { AttributeValue_::Value(v) => v.ast_debug(w), - AttributeValue_::Module(m) => w.write(&format!("{m}")), + AttributeValue_::Module(m) => w.write(format!("{m}")), AttributeValue_::ModuleAccess(n) => n.ast_debug(w), - AttributeValue_::Address(a) => w.write(&format!("{a}")), + AttributeValue_::Address(a) => w.write(format!("{a}")), } } } @@ -1055,14 +1055,14 @@ impl AstDebug for AttributeValue_ { impl AstDebug for Attribute_ { fn ast_debug(&self, w: &mut AstWriter) { match self { - Attribute_::Name(n) => w.write(&format!("{}", n)), + Attribute_::Name(n) => w.write(format!("{}", n)), Attribute_::Assigned(n, v) => { - w.write(&format!("{}", n)); + w.write(format!("{}", n)); w.write(" = "); v.ast_debug(w); } Attribute_::Parameterized(n, inners) => { - w.write(&format!("{}", n)); + w.write(format!("{}", n)); w.write("("); w.list(inners, ", ", |w, (_, _, inner)| { inner.ast_debug(w); @@ -1113,7 +1113,7 @@ impl AstDebug for ModuleDefinition { } = self; warning_filter.ast_debug(w); if let Some(n) = package_name { - w.writeln(&format!("{}", n)) + w.writeln(format!("{}", n)) } attributes.ast_debug(w); w.writeln(match target_kind { @@ -1127,7 +1127,7 @@ impl AstDebug for ModuleDefinition { }); use_funs.ast_debug(w); for (mident, _loc) in friends.key_cloned_iter() { - w.write(&format!("friend {};", mident)); + w.write(format!("friend {};", mident)); w.new_line(); } for sdef in structs.key_cloned_iter() { @@ -1179,21 +1179,21 @@ impl AstDebug for (DatatypeName, &StructDefinition) { w.write("native "); } - w.write(&format!("struct#{index} {name}")); + w.write(format!("struct#{index} {name}")); type_parameters.ast_debug(w); ability_modifiers_ast_debug(w, abilities); match fields { StructFields::Named(fields) => w.block(|w| { w.list(fields, ",", |w, (_, f, idx_st)| { let (idx, st) = idx_st; - w.write(&format!("{}#{}: ", idx, f)); + w.write(format!("{}#{}: ", idx, f)); st.ast_debug(w); true }); }), StructFields::Positional(fields) => w.block(|w| { w.list(fields.iter().enumerate(), ",", |w, (idx, ty)| { - w.write(&format!("{idx}#pos{idx}: ")); + w.write(format!("{idx}#pos{idx}: ")); ty.ast_debug(w); true }); @@ -1220,7 +1220,7 @@ impl AstDebug for (DatatypeName, &EnumDefinition) { warning_filter.ast_debug(w); attributes.ast_debug(w); - w.write(&format!("enum#{index} {name}")); + w.write(format!("enum#{index} {name}")); type_parameters.ast_debug(w); ability_modifiers_ast_debug(w, abilities); w.block(|w| { @@ -1242,19 +1242,19 @@ impl AstDebug for (VariantName, &VariantDefinition) { }, ) = self; - w.write(&format!("variant#{index} {name}")); + w.write(format!("variant#{index} {name}")); match fields { VariantFields::Named(fields) => w.block(|w| { w.list(fields, ",", |w, (_, f, idx_st)| { let (idx, st) = idx_st; - w.write(&format!("{}#{}: ", idx, f)); + w.write(format!("{}#{}: ", idx, f)); st.ast_debug(w); true }); }), VariantFields::Positional(fields) => w.block(|w| { w.list(fields.iter().enumerate(), ",", |w, (idx, ty)| { - w.write(&format!("{idx}#pos{idx}: ")); + w.write(format!("{idx}#pos{idx}: ")); ty.ast_debug(w); true }); @@ -1284,15 +1284,15 @@ impl AstDebug for (FunctionName, &Function) { attributes.ast_debug(w); visibility.ast_debug(w); if entry.is_some() { - w.write(&format!("{} ", ENTRY_MODIFIER)); + w.write(format!("{} ", ENTRY_MODIFIER)); } if macro_.is_some() { - w.write(&format!("{} ", MACRO_MODIFIER)); + w.write(format!("{} ", MACRO_MODIFIER)); } if let FunctionBody_::Native = &body.value { - w.write(&format!("{} ", NATIVE_MODIFIER)); + w.write(format!("{} ", NATIVE_MODIFIER)); } - w.write(&format!("fun#{index} {name}")); + w.write(format!("fun#{index} {name}")); signature.ast_debug(w); match &body.value { FunctionBody_::Defined(body) => body.ast_debug(w), @@ -1303,7 +1303,7 @@ impl AstDebug for (FunctionName, &Function) { impl AstDebug for Visibility { fn ast_debug(&self, w: &mut AstWriter) { - w.write(&format!("{} ", self)) + w.write(format!("{} ", self)) } } @@ -1318,7 +1318,7 @@ impl AstDebug for FunctionSignature { w.write("("); w.comma(parameters, |w, (mutability, v, st)| { mutability.ast_debug(w); - w.write(&format!("{}: ", v)); + w.write(format!("{}: ", v)); st.ast_debug(w); }); w.write("): "); @@ -1341,7 +1341,7 @@ impl AstDebug for (ConstantName, &Constant) { ) = self; warning_filter.ast_debug(w); attributes.ast_debug(w); - w.write(&format!("const#{index} {}:", name)); + w.write(format!("const#{index} {}:", name)); signature.ast_debug(w); w.write(" = "); value.ast_debug(w); @@ -1485,16 +1485,16 @@ impl AstDebug for Value_ { fn ast_debug(&self, w: &mut AstWriter) { use Value_ as V; match self { - V::Address(addr) => w.write(&format!("@{}", addr)), - V::InferredNum(u) => w.write(&format!("{}", u)), - V::U8(u) => w.write(&format!("{}u8", u)), - V::U16(u) => w.write(&format!("{}u16", u)), - V::U32(u) => w.write(&format!("{}u32", u)), - V::U64(u) => w.write(&format!("{}u64", u)), - V::U128(u) => w.write(&format!("{}u128", u)), - V::U256(u) => w.write(&format!("{}u256", u)), - V::Bool(b) => w.write(&format!("{}", b)), - V::Bytearray(v) => w.write(&format!("{:?}", v)), + V::Address(addr) => w.write(format!("@{}", addr)), + V::InferredNum(u) => w.write(format!("{}", u)), + V::U8(u) => w.write(format!("{}u8", u)), + V::U16(u) => w.write(format!("{}u16", u)), + V::U32(u) => w.write(format!("{}u32", u)), + V::U64(u) => w.write(format!("{}u64", u)), + V::U128(u) => w.write(format!("{}u128", u)), + V::U256(u) => w.write(format!("{}u256", u)), + V::Bool(b) => w.write(format!("{}", b)), + V::Bytearray(v) => w.write(format!("{:?}", v)), } } } @@ -1532,7 +1532,7 @@ impl AstDebug for Exp_ { } E::MethodCall(e, f, is_macro, tys_opt, sp!(_, rhs)) => { e.ast_debug(w); - w.write(&format!(".{}", f)); + w.write(format!(".{}", f)); if is_macro.is_some() { w.write("!"); } @@ -1555,7 +1555,7 @@ impl AstDebug for Exp_ { w.write("{"); w.comma(fields, |w, (_, f, idx_e)| { let (idx, e) = idx_e; - w.write(&format!("{}#{}: ", idx, f)); + w.write(format!("{}#{}: ", idx, f)); e.ast_debug(w); }); w.write("}"); @@ -1727,7 +1727,7 @@ impl AstDebug for ExpDotted_ { D::Exp(e) => e.ast_debug(w), D::Dot(e, n) => { e.ast_debug(w); - w.write(&format!(".{}", n)) + w.write(format!(".{}", n)) } D::Index(e, rhs) => { e.ast_debug(w); @@ -1853,7 +1853,7 @@ impl AstDebug for LValue_ { if let Some(mutability) = mutability { mutability.ast_debug(w); } - w.write(&format!("{}", v)); + w.write(format!("{}", v)); if let Some(ss) = tys_opt { w.write("<"); ss.ast_debug(w); @@ -1925,7 +1925,7 @@ impl AstDebug for FieldBindings { w.write("{"); w.comma(fields, |w, (_, f, idx_b)| { let (idx, b) = idx_b; - w.write(&format!("{}#{}: ", idx, f)); + w.write(format!("{}#{}: ", idx, f)); b.ast_debug(w); }); if ellipsis.is_some() { @@ -1936,7 +1936,7 @@ impl AstDebug for FieldBindings { FieldBindings::Positional(vals) => { w.write("("); w.comma(vals.iter().enumerate(), |w, (idx, lval)| { - w.write(&format!("{idx}: ")); + w.write(format!("{idx}: ")); lval.ast_debug(w); }); w.write(")"); diff --git a/external-crates/move/crates/move-compiler/src/expansion/mod.rs b/external-crates/move/crates/move-compiler/src/expansion/mod.rs index ddff273766cdb..54bb6be1e514d 100644 --- a/external-crates/move/crates/move-compiler/src/expansion/mod.rs +++ b/external-crates/move/crates/move-compiler/src/expansion/mod.rs @@ -8,6 +8,7 @@ pub mod ast; mod byte_string; mod hex_string; mod legacy_aliases; +pub mod name_validation; mod path_expander; mod primitive_definers; pub(crate) mod translate; diff --git a/external-crates/move/crates/move-compiler/src/expansion/name_validation.rs b/external-crates/move/crates/move-compiler/src/expansion/name_validation.rs new file mode 100644 index 0000000000000..1991ab59a7f98 --- /dev/null +++ b/external-crates/move/crates/move-compiler/src/expansion/name_validation.rs @@ -0,0 +1,424 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + diag, + diagnostics::Diagnostic, + parser::ast::{self as P, ModuleName, Var, MACRO_MODIFIER}, + shared::*, +}; +use move_ir_types::location::*; +use move_symbol_pool::Symbol; +use std::collections::BTreeSet; + +// Implicit aliases for the Move Stdlib: +// use std::vector; +// use std::option::{Self, Option}; +pub const IMPLICIT_STD_MODULES: &[Symbol] = &[symbol!("option"), symbol!("vector")]; +pub const IMPLICIT_STD_MEMBERS: &[(Symbol, Symbol, ModuleMemberKind)] = &[( + symbol!("option"), + symbol!("Option"), + ModuleMemberKind::Struct, +)]; + +// Implicit aliases for Sui mode: +// use sui::object::{Self, ID, UID}; +// use sui::transfer; +// use sui::tx_context::{Self, TxContext}; +pub const IMPLICIT_SUI_MODULES: &[Symbol] = &[ + symbol!("object"), + symbol!("transfer"), + symbol!("tx_context"), +]; +pub const IMPLICIT_SUI_MEMBERS: &[(Symbol, Symbol, ModuleMemberKind)] = &[ + (symbol!("object"), symbol!("ID"), ModuleMemberKind::Struct), + (symbol!("object"), symbol!("UID"), ModuleMemberKind::Struct), + ( + symbol!("tx_context"), + symbol!("TxContext"), + ModuleMemberKind::Struct, + ), +]; + +#[derive(Copy, Clone, Debug)] +pub enum ModuleMemberKind { + Constant, + Function, + Struct, + Enum, +} + +#[derive(Copy, Clone, Debug)] +pub enum NameCase { + Constant, + Function, + Struct, + Enum, + Module, + ModuleMemberAlias(ModuleMemberKind), + ModuleAlias, + Variable, + Address, + TypeParameter, +} + +//************************************************************************************************** +// impls +//************************************************************************************************** + +impl ModuleMemberKind { + pub fn case(self) -> NameCase { + match self { + ModuleMemberKind::Constant => NameCase::Constant, + ModuleMemberKind::Function => NameCase::Function, + ModuleMemberKind::Struct => NameCase::Struct, + ModuleMemberKind::Enum => NameCase::Enum, + } + } +} + +impl NameCase { + pub const fn name(&self) -> &'static str { + match self { + NameCase::Constant => "constant", + NameCase::Function => "function", + NameCase::Struct => "struct", + NameCase::Enum => "enum", + NameCase::Module => "module", + NameCase::ModuleMemberAlias(ModuleMemberKind::Function) => "function alias", + NameCase::ModuleMemberAlias(ModuleMemberKind::Constant) => "constant alias", + NameCase::ModuleMemberAlias(ModuleMemberKind::Struct) => "struct alias", + NameCase::ModuleMemberAlias(ModuleMemberKind::Enum) => "enum alias", + NameCase::ModuleAlias => "module alias", + NameCase::Variable => "variable", + NameCase::Address => "address", + NameCase::TypeParameter => "type parameter", + } + } +} + +//************************************************************************************************** +// Valid names +//************************************************************************************************** + +#[allow(clippy::result_unit_err)] +pub fn check_valid_address_name( + env: &mut CompilationEnv, + sp!(_, ln_): &P::LeadingNameAccess, +) -> Result<(), ()> { + use P::LeadingNameAccess_ as LN; + match ln_ { + LN::AnonymousAddress(_) => Ok(()), + LN::GlobalAddress(n) | LN::Name(n) => { + check_restricted_name_all_cases(env, NameCase::Address, n) + } + } +} + +pub fn valid_local_variable_name(s: Symbol) -> bool { + s.starts_with('_') || s.starts_with(|c: char| c.is_ascii_lowercase()) +} + +#[allow(clippy::result_unit_err)] +pub fn check_valid_function_parameter_name( + env: &mut CompilationEnv, + is_macro: Option, + v: &Var, +) { + const SYNTAX_IDENTIFIER_NOTE: &str = + "'macro' parameters start with '$' to indicate that their arguments are not evaluated \ + before the macro is expanded, meaning the entire expression is substituted. \ + This is different from regular function parameters that are evaluated before the \ + function is called."; + let is_syntax_identifier = v.is_syntax_identifier(); + if let Some(macro_loc) = is_macro { + if !is_syntax_identifier && !v.is_underscore() { + let msg = format!( + "Invalid parameter name '{}'. '{}' parameter names must start with '$' (or must be '_')", + v, MACRO_MODIFIER, + ); + let macro_msg = format!("Declared '{}' here", MACRO_MODIFIER); + let mut diag = diag!( + Declarations::InvalidName, + (v.loc(), msg), + (macro_loc, macro_msg), + ); + diag.add_note(SYNTAX_IDENTIFIER_NOTE); + env.add_diag(diag); + } + } else if is_syntax_identifier { + let msg = format!( + "Invalid parameter name '{}'. Non-'{}' parameter names cannot start with '$'", + v, MACRO_MODIFIER, + ); + let mut diag = diag!(Declarations::InvalidName, (v.loc(), msg)); + diag.add_note(SYNTAX_IDENTIFIER_NOTE); + env.add_diag(diag); + } else if !is_valid_local_variable_name(v.value()) { + let msg = format!( + "Invalid parameter name '{}'. Local variable names must start with 'a'..'z', '_', \ + or be a valid name quoted with backticks (`name`)", + v, + ); + env.add_diag(diag!(Declarations::InvalidName, (v.loc(), msg))); + } + let _ = check_restricted_name_all_cases(env, NameCase::Variable, &v.0); +} + +pub fn check_valid_local_name(env: &mut CompilationEnv, v: &Var) { + if !is_valid_local_variable_name(v.value()) { + let msg = format!( + "Invalid local name '{}'. Local variable names must start with 'a'..'z', '_', \ + or be a valid name quoted with backticks (`name`)", + v, + ); + env.add_diag(diag!(Declarations::InvalidName, (v.loc(), msg))); + } + let _ = check_restricted_name_all_cases(env, NameCase::Variable, &v.0); +} + +fn is_valid_local_variable_name(s: Symbol) -> bool { + Var::is_valid_name(s) && !Var::is_syntax_identifier_name(s) +} + +pub fn check_valid_module_member_name( + env: &mut CompilationEnv, + member: ModuleMemberKind, + name: Name, +) -> Option { + match check_valid_module_member_name_impl(env, member, &name, member.case()) { + Err(()) => None, + Ok(()) => Some(name), + } +} + +pub fn check_valid_module_member_alias( + env: &mut CompilationEnv, + member: ModuleMemberKind, + alias: Name, +) -> Option { + match check_valid_module_member_name_impl( + env, + member, + &alias, + NameCase::ModuleMemberAlias(member), + ) { + Err(()) => None, + Ok(()) => Some(alias), + } +} + +fn check_valid_module_member_name_impl( + env: &mut CompilationEnv, + member: ModuleMemberKind, + n: &Name, + case: NameCase, +) -> Result<(), ()> { + use ModuleMemberKind as M; + fn upper_first_letter(s: &str) -> String { + let mut chars = s.chars(); + match chars.next() { + None => String::new(), + Some(c) => c.to_uppercase().collect::() + chars.as_str(), + } + } + match member { + M::Function => { + if n.value.starts_with('_') { + let msg = format!( + "Invalid {} name '{}'. {} names cannot start with '_'", + case.name(), + n, + upper_first_letter(case.name()), + ); + env.add_diag(diag!(Declarations::InvalidName, (n.loc, msg))); + return Err(()); + } + } + M::Constant | M::Struct | M::Enum => { + if !is_valid_datatype_or_constant_name(&n.value) { + let msg = format!( + "Invalid {} name '{}'. {} names must start with 'A'..'Z'", + case.name(), + n, + upper_first_letter(case.name()), + ); + env.add_diag(diag!(Declarations::InvalidName, (n.loc, msg))); + return Err(()); + } + } + } + + // TODO move these names to a more central place? + check_restricted_names( + env, + case, + n, + crate::naming::ast::BuiltinFunction_::all_names(), + )?; + check_restricted_names( + env, + case, + n, + crate::naming::ast::BuiltinTypeName_::all_names(), + )?; + + // Restricting Self for now in the case where we ever have impls + // Otherwise, we could allow it + check_restricted_name_all_cases(env, case, n)?; + + Ok(()) +} + +#[allow(clippy::result_unit_err)] +pub fn check_valid_type_parameter_name( + env: &mut CompilationEnv, + is_macro: Option, + n: &Name, +) -> Result<(), ()> { + // TODO move these names to a more central place? + if n.value == symbol!("_") { + let diag = restricted_name_error(NameCase::TypeParameter, n.loc, "_"); + env.add_diag(diag); + return Err(()); + } + + const SYNTAX_IDENTIFIER_NOTE: &str = "Type parameter names starting with '$' indicate that \ + their arguments do not have to satisfy certain constraints before the macro is expanded, \ + meaning types like '&mut u64' or '(bool, u8)' may be used as arguments."; + + let is_syntax_ident = Var::is_syntax_identifier_name(n.value); + if let Some(macro_loc) = is_macro { + if !is_syntax_ident { + let msg = format!( + "Invalid type parameter name. \ + '{} fun' type parameter names must start with '$'", + MACRO_MODIFIER + ); + let macro_msg = format!("Declared '{}' here", MACRO_MODIFIER); + let mut diag = diag!( + Declarations::InvalidName, + (n.loc, msg), + (macro_loc, macro_msg), + ); + diag.add_note(SYNTAX_IDENTIFIER_NOTE); + env.add_diag(diag); + } else { + let next_char = n.value.chars().nth(1).unwrap(); + if !next_char.is_ascii_alphabetic() { + let msg = format!( + "Invalid type parameter name '{}'. \ + Following the '$', the '{} fun' type parameter must be have a valid type \ + parameter name starting with a letter 'a'..'z' or 'A'..'Z'", + n, MACRO_MODIFIER + ); + let mut diag = diag!(Declarations::InvalidName, (n.loc, msg)); + diag.add_note(SYNTAX_IDENTIFIER_NOTE); + env.add_diag(diag); + } + } + } else if is_syntax_ident { + let msg = format!( + "Invalid type parameter name. \ + Only '{} fun' type parameter names cat start with '$'", + MACRO_MODIFIER + ); + let mut diag = diag!(Declarations::InvalidName, (n.loc, msg)); + diag.add_note(SYNTAX_IDENTIFIER_NOTE); + env.add_diag(diag); + } + + // TODO move these names to a more central place? + check_restricted_names( + env, + NameCase::TypeParameter, + n, + crate::naming::ast::BuiltinFunction_::all_names(), + )?; + check_restricted_names( + env, + NameCase::TypeParameter, + n, + crate::naming::ast::BuiltinTypeName_::all_names(), + )?; + + check_restricted_name_all_cases(env, NameCase::TypeParameter, n) +} + +pub fn is_valid_datatype_or_constant_name(s: &str) -> bool { + s.starts_with(|c: char| c.is_ascii_uppercase()) +} + +#[allow(clippy::result_unit_err)] +// Checks for a restricted name in any decl case +// Self and vector are not allowed +pub fn check_restricted_name_all_cases( + env: &mut CompilationEnv, + case: NameCase, + n: &Name, +) -> Result<(), ()> { + match case { + NameCase::Constant + | NameCase::Function + | NameCase::Struct + | NameCase::Enum + | NameCase::Module + | NameCase::ModuleMemberAlias(_) + | NameCase::ModuleAlias + | NameCase::Address => { + if Var::is_syntax_identifier_name(n.value) { + let msg = format!( + "Invalid {} name '{}'. Identifiers starting with '$' can be used only for \ + parameters and type paramters", + case.name(), + n, + ); + env.add_diag(diag!(Declarations::InvalidName, (n.loc, msg))); + return Err(()); + } + } + NameCase::Variable | NameCase::TypeParameter => (), + } + + let n_str = n.value.as_str(); + let can_be_vector = matches!(case, NameCase::Module | NameCase::ModuleAlias); + if n_str == ModuleName::SELF_NAME + || (!can_be_vector && n_str == crate::naming::ast::BuiltinTypeName_::VECTOR) + { + env.add_diag(restricted_name_error(case, n.loc, n_str)); + Err(()) + } else { + Ok(()) + } +} + +fn check_restricted_names( + env: &mut CompilationEnv, + case: NameCase, + sp!(loc, n_): &Name, + all_names: &BTreeSet, +) -> Result<(), ()> { + if all_names.contains(n_) { + env.add_diag(restricted_name_error(case, *loc, n_)); + Err(()) + } else { + Ok(()) + } +} + +fn restricted_name_error(case: NameCase, loc: Loc, restricted: &str) -> Diagnostic { + let a_or_an = match case.name().chars().next().unwrap() { + // TODO this is not exhaustive to the indefinite article rules in English + // but 'case' is never user generated, so it should be okay for a while/forever... + 'a' | 'e' | 'i' | 'o' | 'u' => "an", + _ => "a", + }; + let msg = format!( + "Invalid {case} name '{restricted}'. '{restricted}' is restricted and cannot be used to \ + name {a_or_an} {case}", + a_or_an = a_or_an, + case = case.name(), + restricted = restricted, + ); + diag!(NameResolution::ReservedName, (loc, msg)) +} diff --git a/external-crates/move/crates/move-compiler/src/expansion/path_expander.rs b/external-crates/move/crates/move-compiler/src/expansion/path_expander.rs index 8891e077adcdc..70e7cde321745 100644 --- a/external-crates/move/crates/move-compiler/src/expansion/path_expander.rs +++ b/external-crates/move/crates/move-compiler/src/expansion/path_expander.rs @@ -12,9 +12,10 @@ use crate::{ aliases::{AliasMap, AliasSet}, ast::{self as E, Address, ModuleIdent, ModuleIdent_}, legacy_aliases, + name_validation::is_valid_datatype_or_constant_name, translate::{ - is_valid_datatype_or_constant_name, make_address, module_ident, top_level_address, - top_level_address_opt, value, DefnContext, + make_address, module_ident, top_level_address, top_level_address_opt, value, + DefnContext, }, }, ice, ice_assert, diff --git a/external-crates/move/crates/move-compiler/src/expansion/translate.rs b/external-crates/move/crates/move-compiler/src/expansion/translate.rs index 093a28e8f9af0..40eb01fe7a98c 100644 --- a/external-crates/move/crates/move-compiler/src/expansion/translate.rs +++ b/external-crates/move/crates/move-compiler/src/expansion/translate.rs @@ -13,6 +13,13 @@ use crate::{ aliases::AliasSet, ast::{self as E, Address, Fields, ModuleIdent, ModuleIdent_, TargetKind}, byte_string, hex_string, + name_validation::{ + check_restricted_name_all_cases, check_valid_address_name, + check_valid_function_parameter_name, check_valid_local_name, + check_valid_module_member_alias, check_valid_module_member_name, + check_valid_type_parameter_name, valid_local_variable_name, ModuleMemberKind, NameCase, + IMPLICIT_STD_MEMBERS, IMPLICIT_STD_MODULES, IMPLICIT_SUI_MEMBERS, IMPLICIT_SUI_MODULES, + }, path_expander::{ access_result, Access, LegacyPathExpander, ModuleAccessResult, Move2024PathExpander, PathExpander, @@ -327,35 +334,6 @@ fn compute_address_conflicts( .collect() } -// Implicit aliases for the Move Stdlib: -// use std::vector; -// use std::option::{Self, Option}; -const IMPLICIT_STD_MODULES: &[Symbol] = &[symbol!("option"), symbol!("vector")]; -const IMPLICIT_STD_MEMBERS: &[(Symbol, Symbol, ModuleMemberKind)] = &[( - symbol!("option"), - symbol!("Option"), - ModuleMemberKind::Struct, -)]; - -// Implicit aliases for Sui mode: -// use sui::object::{Self, ID, UID}; -// use sui::transfer; -// use sui::tx_context::{Self, TxContext}; -const IMPLICIT_SUI_MODULES: &[Symbol] = &[ - symbol!("object"), - symbol!("transfer"), - symbol!("tx_context"), -]; -const IMPLICIT_SUI_MEMBERS: &[(Symbol, Symbol, ModuleMemberKind)] = &[ - (symbol!("object"), symbol!("ID"), ModuleMemberKind::Struct), - (symbol!("object"), symbol!("UID"), ModuleMemberKind::Struct), - ( - symbol!("tx_context"), - symbol!("TxContext"), - ModuleMemberKind::Struct, - ), -]; - fn default_aliases(context: &mut Context) -> AliasMapBuilder { let current_package = context.current_package(); let mut builder = context.new_alias_map_builder(); @@ -623,7 +601,7 @@ fn top_level_address_( suggest_declaration: bool, ln: P::LeadingNameAccess, ) -> Address { - let name_res = check_valid_address_name(context, &ln); + let name_res = check_valid_address_name(context.env, &ln); let sp!(loc, ln_) = ln; match ln_ { P::LeadingNameAccess_::AnonymousAddress(bytes) => { @@ -661,7 +639,7 @@ pub(super) fn top_level_address_opt( context: &mut DefnContext, ln: P::LeadingNameAccess, ) -> Option
{ - let name_res = check_valid_address_name(context, &ln); + let name_res = check_valid_address_name(context.env, &ln); let named_address_mapping = context.named_address_mapping.as_ref().unwrap(); let sp!(loc, ln_) = ln; match ln_ { @@ -847,8 +825,8 @@ fn module_( assert!(context.address.is_none()); assert!(address.is_none()); set_module_address(context, &name, module_address); - let _ = check_restricted_name_all_cases(&mut context.defn_context, NameCase::Module, &name.0); - if name.value().starts_with(|c| c == '_') { + let _ = check_restricted_name_all_cases(context.defn_context.env, NameCase::Module, &name.0); + if name.value().starts_with('_') { let msg = format!( "Invalid module name '{}'. Module names cannot start with '_'", name, @@ -1461,7 +1439,7 @@ fn aliases_from_member( ) -> Option { macro_rules! check_name_and_add_implicit_alias { ($kind:expr, $name:expr) => {{ - if let Some(n) = check_valid_module_member_name(context, $kind, $name) { + if let Some(n) = check_valid_module_member_name(&mut context.env(), $kind, $name) { if let Err(loc) = acc.add_implicit_member_alias( n.clone(), current_module.clone(), @@ -1598,7 +1576,7 @@ fn module_use( macro_rules! add_module_alias { ($ident:expr, $alias:expr) => {{ if let Err(()) = check_restricted_name_all_cases( - &mut context.defn_context, + &mut context.defn_context.env, NameCase::ModuleAlias, &$alias, ) { @@ -1678,7 +1656,8 @@ fn module_use( let alias = alias_opt.unwrap_or(member); - let alias = match check_valid_module_member_alias(context, member_kind, alias) { + let alias = match check_valid_module_member_alias(context.env(), member_kind, alias) + { None => continue, Some(alias) => alias, }; @@ -2259,7 +2238,7 @@ fn function_signature( .map(|(pmut, v, t)| (mutability(context, v.loc(), pmut), v, type_(context, t))) .collect::>(); for (_, v, _) in ¶meters { - check_valid_function_parameter_name(context, is_macro, v) + check_valid_function_parameter_name(context.env(), is_macro, v) } let return_type = type_(context, pret_ty); E::FunctionSignature { @@ -2307,7 +2286,7 @@ fn function_type_parameters( .into_iter() .map(|(name, constraints_vec)| { let constraints = ability_set(context, "constraint", constraints_vec); - let _ = check_valid_type_parameter_name(context, is_macro, &name); + let _ = check_valid_type_parameter_name(context.env(), is_macro, &name); (name, constraints) }) .collect() @@ -2320,7 +2299,7 @@ fn datatype_type_parameters( pty_params .into_iter() .map(|param| { - let _ = check_valid_type_parameter_name(context, None, ¶m.name); + let _ = check_valid_type_parameter_name(context.env(), None, ¶m.name); E::DatatypeTypeParameter { is_phantom: param.is_phantom, name: param.name, @@ -3331,7 +3310,7 @@ fn bind(context: &mut Context, sp!(loc, pb_): P::Bind) -> Option { let b_ = match pb_ { PB::Var(pmut, v) => { let emut = mutability(context, v.loc(), pmut); - check_valid_local_name(context, &v); + check_valid_local_name(context.env(), &v); EL::Var(Some(emut), sp(loc, E::ModuleAccess_::Name(v.0)), None) } PB::Unpack(ptn, pfields) => { @@ -3574,388 +3553,3 @@ fn mutability(context: &mut Context, _loc: Loc, pmut: P::Mutability) -> E::Mutab None => E::Mutability::Either, } } - -//************************************************************************************************** -// Valid names -//************************************************************************************************** - -fn check_valid_address_name( - context: &mut DefnContext, - sp!(_, ln_): &P::LeadingNameAccess, -) -> Result<(), ()> { - use P::LeadingNameAccess_ as LN; - match ln_ { - LN::AnonymousAddress(_) => Ok(()), - LN::GlobalAddress(n) | LN::Name(n) => { - check_restricted_name_all_cases(context, NameCase::Address, n) - } - } -} - -fn valid_local_variable_name(s: Symbol) -> bool { - s.starts_with('_') || s.starts_with(|c: char| c.is_ascii_lowercase()) -} - -fn check_valid_function_parameter_name(context: &mut Context, is_macro: Option, v: &Var) { - const SYNTAX_IDENTIFIER_NOTE: &str = - "'macro' parameters start with '$' to indicate that their arguments are not evaluated \ - before the macro is expanded, meaning the entire expression is substituted. \ - This is different from regular function parameters that are evaluated before the \ - function is called."; - let is_syntax_identifier = v.is_syntax_identifier(); - if let Some(macro_loc) = is_macro { - if !is_syntax_identifier && !v.is_underscore() { - let msg = format!( - "Invalid parameter name '{}'. '{}' parameter names must start with '$' (or must be '_')", - v, MACRO_MODIFIER, - ); - let macro_msg = format!("Declared '{}' here", MACRO_MODIFIER); - let mut diag = diag!( - Declarations::InvalidName, - (v.loc(), msg), - (macro_loc, macro_msg), - ); - diag.add_note(SYNTAX_IDENTIFIER_NOTE); - context.env().add_diag(diag); - } - } else if is_syntax_identifier { - let msg = format!( - "Invalid parameter name '{}'. Non-'{}' parameter names cannot start with '$'", - v, MACRO_MODIFIER, - ); - let mut diag = diag!(Declarations::InvalidName, (v.loc(), msg)); - diag.add_note(SYNTAX_IDENTIFIER_NOTE); - context.env().add_diag(diag); - } else if !is_valid_local_variable_name(v.value()) { - let msg = format!( - "Invalid parameter name '{}'. Local variable names must start with 'a'..'z', '_', \ - or be a valid name quoted with backticks (`name`)", - v, - ); - context - .env() - .add_diag(diag!(Declarations::InvalidName, (v.loc(), msg))); - } - let _ = check_restricted_name_all_cases(&mut context.defn_context, NameCase::Variable, &v.0); -} - -fn check_valid_local_name(context: &mut Context, v: &Var) { - if !is_valid_local_variable_name(v.value()) { - let msg = format!( - "Invalid local name '{}'. Local variable names must start with 'a'..'z', '_', \ - or be a valid name quoted with backticks (`name`)", - v, - ); - context - .env() - .add_diag(diag!(Declarations::InvalidName, (v.loc(), msg))); - } - let _ = check_restricted_name_all_cases(&mut context.defn_context, NameCase::Variable, &v.0); -} - -fn is_valid_local_variable_name(s: Symbol) -> bool { - Var::is_valid_name(s) && !Var::is_syntax_identifier_name(s) -} - -#[derive(Copy, Clone, Debug)] -pub enum ModuleMemberKind { - Constant, - Function, - Struct, - Enum, -} - -impl ModuleMemberKind { - pub fn case(self) -> NameCase { - match self { - ModuleMemberKind::Constant => NameCase::Constant, - ModuleMemberKind::Function => NameCase::Function, - ModuleMemberKind::Struct => NameCase::Struct, - ModuleMemberKind::Enum => NameCase::Enum, - } - } -} - -#[derive(Copy, Clone, Debug)] -pub enum NameCase { - Constant, - Function, - Struct, - Enum, - Module, - ModuleMemberAlias(ModuleMemberKind), - ModuleAlias, - Variable, - Address, - TypeParameter, -} - -impl NameCase { - pub const fn name(&self) -> &'static str { - match self { - NameCase::Constant => "constant", - NameCase::Function => "function", - NameCase::Struct => "struct", - NameCase::Enum => "enum", - NameCase::Module => "module", - NameCase::ModuleMemberAlias(ModuleMemberKind::Function) => "function alias", - NameCase::ModuleMemberAlias(ModuleMemberKind::Constant) => "constant alias", - NameCase::ModuleMemberAlias(ModuleMemberKind::Struct) => "struct alias", - NameCase::ModuleMemberAlias(ModuleMemberKind::Enum) => "enum alias", - NameCase::ModuleAlias => "module alias", - NameCase::Variable => "variable", - NameCase::Address => "address", - NameCase::TypeParameter => "type parameter", - } - } -} - -fn check_valid_module_member_name( - context: &mut Context, - member: ModuleMemberKind, - name: Name, -) -> Option { - match check_valid_module_member_name_impl(context, member, &name, member.case()) { - Err(()) => None, - Ok(()) => Some(name), - } -} - -fn check_valid_module_member_alias( - context: &mut Context, - member: ModuleMemberKind, - alias: Name, -) -> Option { - match check_valid_module_member_name_impl( - context, - member, - &alias, - NameCase::ModuleMemberAlias(member), - ) { - Err(()) => None, - Ok(()) => Some(alias), - } -} - -fn check_valid_module_member_name_impl( - context: &mut Context, - member: ModuleMemberKind, - n: &Name, - case: NameCase, -) -> Result<(), ()> { - use ModuleMemberKind as M; - fn upper_first_letter(s: &str) -> String { - let mut chars = s.chars(); - match chars.next() { - None => String::new(), - Some(c) => c.to_uppercase().collect::() + chars.as_str(), - } - } - match member { - M::Function => { - if n.value.starts_with(|c| c == '_') { - let msg = format!( - "Invalid {} name '{}'. {} names cannot start with '_'", - case.name(), - n, - upper_first_letter(case.name()), - ); - context - .env() - .add_diag(diag!(Declarations::InvalidName, (n.loc, msg))); - return Err(()); - } - } - M::Constant | M::Struct | M::Enum => { - if !is_valid_datatype_or_constant_name(&n.value) { - let msg = format!( - "Invalid {} name '{}'. {} names must start with 'A'..'Z'", - case.name(), - n, - upper_first_letter(case.name()), - ); - context - .env() - .add_diag(diag!(Declarations::InvalidName, (n.loc, msg))); - return Err(()); - } - } - } - - // TODO move these names to a more central place? - check_restricted_names( - context, - case, - n, - crate::naming::ast::BuiltinFunction_::all_names(), - )?; - check_restricted_names( - context, - case, - n, - crate::naming::ast::BuiltinTypeName_::all_names(), - )?; - - // Restricting Self for now in the case where we ever have impls - // Otherwise, we could allow it - check_restricted_name_all_cases(&mut context.defn_context, case, n)?; - - Ok(()) -} - -fn check_valid_type_parameter_name( - context: &mut Context, - is_macro: Option, - n: &Name, -) -> Result<(), ()> { - // TODO move these names to a more central place? - if n.value == symbol!("_") { - let diag = restricted_name_error(NameCase::TypeParameter, n.loc, "_"); - context.env().add_diag(diag); - return Err(()); - } - - const SYNTAX_IDENTIFIER_NOTE: &str = "Type parameter names starting with '$' indicate that \ - their arguments do not have to satisfy certain constraints before the macro is expanded, \ - meaning types like '&mut u64' or '(bool, u8)' may be used as arguments."; - - let is_syntax_ident = Var::is_syntax_identifier_name(n.value); - if let Some(macro_loc) = is_macro { - if !is_syntax_ident { - let msg = format!( - "Invalid type parameter name. \ - '{} fun' type parameter names must start with '$'", - MACRO_MODIFIER - ); - let macro_msg = format!("Declared '{}' here", MACRO_MODIFIER); - let mut diag = diag!( - Declarations::InvalidName, - (n.loc, msg), - (macro_loc, macro_msg), - ); - diag.add_note(SYNTAX_IDENTIFIER_NOTE); - context.env().add_diag(diag); - } else { - let next_char = n.value.chars().nth(1).unwrap(); - if !next_char.is_ascii_alphabetic() { - let msg = format!( - "Invalid type parameter name '{}'. \ - Following the '$', the '{} fun' type parameter must be have a valid type \ - parameter name starting with a letter 'a'..'z' or 'A'..'Z'", - n, MACRO_MODIFIER - ); - let mut diag = diag!(Declarations::InvalidName, (n.loc, msg)); - diag.add_note(SYNTAX_IDENTIFIER_NOTE); - context.env().add_diag(diag); - } - } - } else if is_syntax_ident { - let msg = format!( - "Invalid type parameter name. \ - Only '{} fun' type parameter names cat start with '$'", - MACRO_MODIFIER - ); - let mut diag = diag!(Declarations::InvalidName, (n.loc, msg)); - diag.add_note(SYNTAX_IDENTIFIER_NOTE); - context.env().add_diag(diag); - } - - // TODO move these names to a more central place? - check_restricted_names( - context, - NameCase::TypeParameter, - n, - crate::naming::ast::BuiltinFunction_::all_names(), - )?; - check_restricted_names( - context, - NameCase::TypeParameter, - n, - crate::naming::ast::BuiltinTypeName_::all_names(), - )?; - - check_restricted_name_all_cases(&mut context.defn_context, NameCase::TypeParameter, n) -} - -pub fn is_valid_datatype_or_constant_name(s: &str) -> bool { - s.starts_with(|c: char| c.is_ascii_uppercase()) -} - -// Checks for a restricted name in any decl case -// Self and vector are not allowed -fn check_restricted_name_all_cases( - context: &mut DefnContext, - case: NameCase, - n: &Name, -) -> Result<(), ()> { - match case { - NameCase::Constant - | NameCase::Function - | NameCase::Struct - | NameCase::Enum - | NameCase::Module - | NameCase::ModuleMemberAlias(_) - | NameCase::ModuleAlias - | NameCase::Address => { - if Var::is_syntax_identifier_name(n.value) { - let msg = format!( - "Invalid {} name '{}'. Identifiers starting with '$' can be used only for \ - parameters and type paramters", - case.name(), - n, - ); - context - .env - .add_diag(diag!(Declarations::InvalidName, (n.loc, msg))); - return Err(()); - } - } - NameCase::Variable | NameCase::TypeParameter => (), - } - - let n_str = n.value.as_str(); - let can_be_vector = matches!(case, NameCase::Module | NameCase::ModuleAlias); - if n_str == ModuleName::SELF_NAME - || (!can_be_vector && n_str == crate::naming::ast::BuiltinTypeName_::VECTOR) - { - context - .env - .add_diag(restricted_name_error(case, n.loc, n_str)); - Err(()) - } else { - Ok(()) - } -} - -fn check_restricted_names( - context: &mut Context, - case: NameCase, - sp!(loc, n_): &Name, - all_names: &BTreeSet, -) -> Result<(), ()> { - if all_names.contains(n_) { - context - .env() - .add_diag(restricted_name_error(case, *loc, n_)); - Err(()) - } else { - Ok(()) - } -} - -fn restricted_name_error(case: NameCase, loc: Loc, restricted: &str) -> Diagnostic { - let a_or_an = match case.name().chars().next().unwrap() { - // TODO this is not exhaustive to the indefinite article rules in English - // but 'case' is never user generated, so it should be okay for a while/forever... - 'a' | 'e' | 'i' | 'o' | 'u' => "an", - _ => "a", - }; - let msg = format!( - "Invalid {case} name '{restricted}'. '{restricted}' is restricted and cannot be used to \ - name {a_or_an} {case}", - a_or_an = a_or_an, - case = case.name(), - restricted = restricted, - ); - diag!(NameResolution::ReservedName, (loc, msg)) -} diff --git a/external-crates/move/crates/move-compiler/src/hlir/ast.rs b/external-crates/move/crates/move-compiler/src/hlir/ast.rs index bb91335dba46f..36db981f504e8 100644 --- a/external-crates/move/crates/move-compiler/src/hlir/ast.rs +++ b/external-crates/move/crates/move-compiler/src/hlir/ast.rs @@ -915,7 +915,7 @@ impl AstDebug for Program { let Program { modules, info: _ } = self; for (m, mdef) in modules.key_cloned_iter() { - w.write(&format!("module {}", m)); + w.write(format!("module {}", m)); w.block(|w| mdef.ast_debug(w)); w.new_line(); } @@ -938,7 +938,7 @@ impl AstDebug for ModuleDefinition { } = self; warning_filter.ast_debug(w); if let Some(n) = package_name { - w.writeln(&format!("{}", n)) + w.writeln(format!("{}", n)) } attributes.ast_debug(w); w.writeln(match target_kind { @@ -950,9 +950,9 @@ impl AstDebug for ModuleDefinition { } => "dependency module", TargetKind::External => "external module", }); - w.writeln(&format!("dependency order #{}", dependency_order)); + w.writeln(format!("dependency order #{}", dependency_order)); for (mident, _loc) in friends.key_cloned_iter() { - w.write(&format!("friend {};", mident)); + w.write(format!("friend {};", mident)); w.new_line(); } for sdef in structs.key_cloned_iter() { @@ -993,13 +993,13 @@ impl AstDebug for (DatatypeName, &StructDefinition) { w.write("native "); } - w.write(&format!("struct#{index} {name}")); + w.write(format!("struct#{index} {name}")); type_parameters.ast_debug(w); ability_modifiers_ast_debug(w, abilities); if let StructFields::Defined(fields) = fields { w.block(|w| { w.list(fields, ";", |w, (f, bt)| { - w.write(&format!("{}: ", f)); + w.write(format!("{}: ", f)); bt.ast_debug(w); true }) @@ -1024,12 +1024,12 @@ impl AstDebug for (DatatypeName, &EnumDefinition) { warning_filter.ast_debug(w); attributes.ast_debug(w); - w.write(&format!("struct#{index} {name}")); + w.write(format!("struct#{index} {name}")); type_parameters.ast_debug(w); ability_modifiers_ast_debug(w, abilities); w.block(|w| { w.list(variants, ";", |w, (_, v, vdef)| { - w.write(&format!("{} {{ ", v)); + w.write(format!("{} {{ ", v)); vdef.ast_debug(w); w.write(" }"); true @@ -1047,7 +1047,7 @@ impl AstDebug for VariantDefinition { } = self; w.write(format!("id:{}|", index)); w.comma(fields, |w, (f, bt)| { - w.write(&format!("{}: ", f)); + w.write(format!("{}: ", f)); bt.ast_debug(w); }) } @@ -1076,12 +1076,12 @@ impl AstDebug for (FunctionName, &Function) { compiled_visibility.ast_debug(w); w.write(") "); if entry.is_some() { - w.write(&format!("{} ", ENTRY_MODIFIER)); + w.write(format!("{} ", ENTRY_MODIFIER)); } if let FunctionBody_::Native = &body.value { w.write("native "); } - w.write(&format!("fun#{index} {name}")); + w.write(format!("fun#{index} {name}")); signature.ast_debug(w); match &body.value { FunctionBody_::Defined { locals, body } => w.block(|w| (locals, body).ast_debug(w)), @@ -1104,7 +1104,7 @@ impl AstDebug for (&UniqueMap, &Block) { w.indent(4, |w| { w.list(*locals, ",", |w, (_, v, (mut_, st))| { mut_.ast_debug(w); - w.write(&format!("{}: ", v)); + w.write(format!("{}: ", v)); st.ast_debug(w); true }) @@ -1136,19 +1136,19 @@ impl AstDebug for FunctionSignature { impl AstDebug for Visibility { fn ast_debug(&self, w: &mut AstWriter) { - w.write(&format!("{} ", self)) + w.write(format!("{} ", self)) } } impl AstDebug for Var { fn ast_debug(&self, w: &mut AstWriter) { - w.write(&format!("{}", self.0)) + w.write(format!("{}", self.0)) } } impl AstDebug for BlockLabel { fn ast_debug(&self, w: &mut AstWriter) { - w.write(&format!("'{}", self.0)) + w.write(format!("'{}", self.0)) } } @@ -1167,7 +1167,7 @@ impl AstDebug for (ConstantName, &Constant) { ) = self; warning_filter.ast_debug(w); attributes.ast_debug(w); - w.write(&format!("const#{index} {name}:")); + w.write(format!("const#{index} {name}:")); signature.ast_debug(w); w.write(" = "); w.block(|w| value.ast_debug(w)); @@ -1179,7 +1179,7 @@ impl AstDebug for TypeName_ { fn ast_debug(&self, w: &mut AstWriter) { match self { TypeName_::Builtin(bt) => bt.ast_debug(w), - TypeName_::ModuleType(m, s) => w.write(&format!("{}::{}", m, s)), + TypeName_::ModuleType(m, s) => w.write(format!("{}::{}", m, s)), } } } @@ -1385,8 +1385,8 @@ impl AstDebug for Command_ { w.write(" = "); exp.ast_debug(w); } - C::Jump { target, from_user } if *from_user => w.write(&format!("jump@{}", target.0)), - C::Jump { target, .. } => w.write(&format!("jump {}", target.0)), + C::Jump { target, from_user } if *from_user => w.write(format!("jump@{}", target.0)), + C::Jump { target, .. } => w.write(format!("jump {}", target.0)), C::JumpIf { cond, if_true, @@ -1394,7 +1394,7 @@ impl AstDebug for Command_ { } => { w.write("jump_if("); cond.ast_debug(w); - w.write(&format!(") {} else {}", if_true.0, if_false.0)); + w.write(format!(") {} else {}", if_true.0, if_false.0)); } C::VariantSwitch { subject, @@ -1418,14 +1418,14 @@ impl AstDebug for Value_ { fn ast_debug(&self, w: &mut AstWriter) { use Value_ as V; match self { - V::Address(addr) => w.write(&format!("@{}", addr)), - V::U8(u) => w.write(&format!("{}u8", u)), - V::U16(u) => w.write(&format!("{}u16", u)), - V::U32(u) => w.write(&format!("{}u32", u)), - V::U64(u) => w.write(&format!("{}u64", u)), - V::U128(u) => w.write(&format!("{}u128", u)), - V::U256(u) => w.write(&format!("{}u256", u)), - V::Bool(b) => w.write(&format!("{}", b)), + V::Address(addr) => w.write(format!("@{}", addr)), + V::U8(u) => w.write(format!("{}u8", u)), + V::U16(u) => w.write(format!("{}u16", u)), + V::U32(u) => w.write(format!("{}u32", u)), + V::U64(u) => w.write(format!("{}u64", u)), + V::U128(u) => w.write(format!("{}u128", u)), + V::U256(u) => w.write(format!("{}u256", u)), + V::Bool(b) => w.write(format!("{}", b)), V::Vector(ty, elems) => { w.write("vector#value"); w.write("<"); @@ -1472,7 +1472,7 @@ impl AstDebug for UnannotatedExp_ { MoveOpAnnotation::InferredLastUsage => "#last ", MoveOpAnnotation::InferredNoCopy => "#no-copy ", }; - w.write(&format!("move{}", case)); + w.write(format!("move{}", case)); v.ast_debug(w) } E::Copy { @@ -1489,12 +1489,12 @@ impl AstDebug for UnannotatedExp_ { w.write("copy@"); v.ast_debug(w) } - E::Constant(c) => w.write(&format!("{}", c)), + E::Constant(c) => w.write(format!("{}", c)), E::ModuleCall(mcall) => { mcall.ast_debug(w); } E::Vector(_loc, n, ty, elems) => { - w.write(&format!("vector#{}", n)); + w.write(format!("vector#{}", n)); w.write("<"); ty.ast_debug(w); w.write(">"); @@ -1508,26 +1508,26 @@ impl AstDebug for UnannotatedExp_ { w.write(")"); } E::Pack(s, tys, fields) => { - w.write(&format!("{}", s)); + w.write(format!("{}", s)); w.write("<"); tys.ast_debug(w); w.write(">"); w.write("{"); w.comma(fields, |w, (f, bt, e)| { - w.annotate(|w| w.write(&format!("{}", f)), bt); + w.annotate(|w| w.write(format!("{}", f)), bt); w.write(": "); e.ast_debug(w); }); w.write("}"); } E::PackVariant(e, v, tys, fields) => { - w.write(&format!("{}::{}", e, v)); + w.write(format!("{}::{}", e, v)); w.write("<"); tys.ast_debug(w); w.write(">"); w.write("{"); w.comma(fields, |w, (f, bt, e)| { - w.annotate(|w| w.write(&format!("{}", f)), bt); + w.annotate(|w| w.write(format!("{}", f)), bt); w.write(": "); e.ast_debug(w); }); @@ -1565,7 +1565,7 @@ impl AstDebug for UnannotatedExp_ { w.write("mut "); } e.ast_debug(w); - w.write(&format!(".{}", f)); + w.write(format!(".{}", f)); } E::BorrowLocal(mut_, v) => { w.write("&"); @@ -1589,7 +1589,7 @@ impl AstDebug for UnannotatedExp_ { } => { w.write("ErrorConstant"); if let Some(c) = error_constant { - w.write(&format!("({})", c)) + w.write(format!("({})", c)) } } } @@ -1604,7 +1604,7 @@ impl AstDebug for ModuleCall { type_arguments, arguments, } = self; - w.write(&format!("{}::{}", module, name)); + w.write(format!("{}::{}", module, name)); w.write("<"); type_arguments.ast_debug(w); w.write(">"); @@ -1648,19 +1648,19 @@ impl AstDebug for LValue_ { ); } L::Unpack(s, tys, fields) => { - w.write(&format!("{}", s)); + w.write(format!("{}", s)); w.write("<"); tys.ast_debug(w); w.write(">"); w.write("{"); w.comma(fields, |w, (f, l)| { - w.write(&format!("{}: ", f)); + w.write(format!("{}: ", f)); l.ast_debug(w) }); w.write("}"); } L::UnpackVariant(e, v, unpack_type, _rhs_loc, tys, fields) => { - w.write(&format!("{}::{}", e, v)); + w.write(format!("{}::{}", e, v)); match unpack_type { UnpackType::ByMutRef => w.write(" &mut "), UnpackType::ByImmRef => w.write(" &"), @@ -1671,7 +1671,7 @@ impl AstDebug for LValue_ { w.write(">"); w.write("{"); w.comma(fields, |w, (f, l)| { - w.write(&format!("{}: ", f)); + w.write(format!("{}: ", f)); l.ast_debug(w) }); w.write("}"); diff --git a/external-crates/move/crates/move-compiler/src/hlir/translate.rs b/external-crates/move/crates/move-compiler/src/hlir/translate.rs index 3aabd518ac23f..26c07e9d20152 100644 --- a/external-crates/move/crates/move-compiler/src/hlir/translate.rs +++ b/external-crates/move/crates/move-compiler/src/hlir/translate.rs @@ -382,6 +382,7 @@ fn function(context: &mut Context, _name: FunctionName, f: T::Function) -> H::Fu warning_filter, index, attributes, + loc: _, compiled_visibility: tcompiled_visibility, visibility: tvisibility, entry, diff --git a/external-crates/move/crates/move-compiler/src/linters/mod.rs b/external-crates/move/crates/move-compiler/src/linters/mod.rs index 67e97f7c0d5bb..1aaf95a668c27 100644 --- a/external-crates/move/crates/move-compiler/src/linters/mod.rs +++ b/external-crates/move/crates/move-compiler/src/linters/mod.rs @@ -15,6 +15,8 @@ pub mod abort_constant; pub mod constant_naming; pub mod loop_without_exit; pub mod meaningless_math_operation; +pub mod redundant_ref_deref; +pub mod self_assignment; pub mod unnecessary_conditional; pub mod unnecessary_while_loop; pub mod unneeded_return; @@ -138,6 +140,18 @@ lints!( LinterDiagnosticCategory::Complexity, "unnecessary_conditional", "'if' expression can be removed" + ), + ( + SelfAssignment, + LinterDiagnosticCategory::Suspicious, + "self_assignment", + "assignment preserves the same value" + ), + ( + RedundantRefDeref, + LinterDiagnosticCategory::Complexity, + "redundant_ref_deref", + "redundant reference/dereference" ) ); @@ -173,6 +187,8 @@ pub fn linter_visitors(level: LintLevel) -> Vec { abort_constant::AssertAbortNamedConstants.visitor(), loop_without_exit::LoopWithoutExit.visitor(), unnecessary_conditional::UnnecessaryConditional.visitor(), + self_assignment::SelfAssignmentVisitor.visitor(), + redundant_ref_deref::RedundantRefDerefVisitor.visitor(), ] } } diff --git a/external-crates/move/crates/move-compiler/src/linters/redundant_ref_deref.rs b/external-crates/move/crates/move-compiler/src/linters/redundant_ref_deref.rs new file mode 100644 index 0000000000000..cf1ed98301767 --- /dev/null +++ b/external-crates/move/crates/move-compiler/src/linters/redundant_ref_deref.rs @@ -0,0 +1,155 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +// Implements lint rule for Move code to detect redundant ref/deref patterns. +// It identifies and reports unnecessary temporary borrow followed by a deref, suggesting either +// removal or conversion to `copy`. + +use crate::linters::StyleCodes; +use crate::{ + diag, + diagnostics::WarningFilters, + shared::CompilationEnv, + typing::{ + ast::{self as T, Exp, UnannotatedExp_ as TE}, + visitor::{TypingVisitorConstructor, TypingVisitorContext}, + }, +}; + +pub struct RedundantRefDerefVisitor; + +pub struct Context<'a> { + env: &'a mut CompilationEnv, +} + +impl TypingVisitorConstructor for RedundantRefDerefVisitor { + type Context<'a> = Context<'a>; + + fn context<'a>(env: &'a mut CompilationEnv, _program: &T::Program) -> Self::Context<'a> { + Context { env } + } +} + +impl TypingVisitorContext for Context<'_> { + fn add_warning_filter_scope(&mut self, filter: WarningFilters) { + self.env.add_warning_filter_scope(filter) + } + fn pop_warning_filter_scope(&mut self) { + self.env.pop_warning_filter_scope() + } + + fn visit_exp_custom(&mut self, exp: &Exp) -> bool { + self.check_redundant_ref_deref(exp); + false + } +} + +impl Context<'_> { + // Check for &* pattern + fn check_redundant_ref_deref(&mut self, exp: &Exp) { + let TE::Dereference(deref_exp) = &exp.exp.value else { + return; + }; + // This is a carve-out to handle cases like `&(s.value), which generate a field borrow and + // dereference to perform a `copy`. In those cases, the location information is reused for + // both, meaning it was generated by typing, and thus not subject to warnings. + // TODO(cswords): In the future, we should mark which of these are generated during typing + // to handle paths. + if exp.exp.loc == deref_exp.exp.loc { + return; + } + match &deref_exp.exp.value { + TE::TempBorrow(_, inner) if is_simple_deref_ref_exp(inner) => self.env.add_diag(diag!( + StyleCodes::RedundantRefDeref.diag_info(), + ( + exp.exp.loc, + "Redundant borrow-dereference detected. \ + Remove this borrow-deref and use the expression directly." + ) + )), + TE::TempBorrow(_, inner) if all_deref_borrow(inner) => self.env.add_diag(diag!( + StyleCodes::RedundantRefDeref.diag_info(), + ( + exp.exp.loc, + "Redundant borrow-dereference detected. \ + Use the inner expression directly." + ) + )), + TE::Borrow(false, _, _) if exp.exp.loc != deref_exp.exp.loc => { + self.env.add_diag(diag!( + StyleCodes::RedundantRefDeref.diag_info(), + ( + exp.exp.loc, + "Redundant borrow-dereference detected. \ + Use the field access directly." + ) + )) + } + TE::Borrow(_, _, _) | TE::BorrowLocal(_, _) => self.env.add_diag(diag!( + StyleCodes::RedundantRefDeref.diag_info(), + ( + exp.exp.loc, + "Redundant borrow-dereference detected. \ + Replace this borrow-deref with 'copy'." + ) + )), + _ => (), + } + } +} + +/// Indicates if the expression is of the form `[&*]+....e` +fn all_deref_borrow(exp: &Exp) -> bool { + let TE::Dereference(deref_exp) = &exp.exp.value else { + return false; + }; + match &deref_exp.exp.value { + TE::TempBorrow(_, inner) => all_deref_borrow(inner), + TE::Borrow(_, _, _) | TE::BorrowLocal(_, _) => true, + _ => false, + } +} + +/// Indicates if the expression at hand is of a form where `&*&` can always be reduces to `&`, such +/// as function calls and constants. +fn is_simple_deref_ref_exp(exp: &Exp) -> bool { + match &exp.exp.value { + TE::Value(_) => true, + TE::Constant(_, _) => true, + TE::ErrorConstant { .. } => true, + TE::ModuleCall(_) => true, + TE::Vector(_, _, _, _) => true, + TE::Copy { .. } => true, + + TE::Cast(inner, _) | TE::Annotate(inner, _) => is_simple_deref_ref_exp(inner), + + TE::Move { .. } => false, // Copy case + TE::Use(_) => todo!(), + TE::IfElse(_, _, _) + | TE::Match(_, _) + | TE::VariantMatch(_, _, _) + | TE::Loop { .. } + | TE::NamedBlock(_, _) + | TE::Block(_) + | TE::Dereference(_) + | TE::UnaryExp(_, _) + | TE::BinopExp(_, _, _, _) + | TE::Pack(_, _, _, _) + | TE::PackVariant(_, _, _, _, _) + | TE::ExpList(_) + | TE::Borrow(_, _, _) + | TE::TempBorrow(_, _) + | TE::BorrowLocal(_, _) => false, + // These are already errors + TE::Unit { .. } + | TE::Builtin(_, _) + | TE::While(_, _, _) + | TE::Assign(_, _, _) + | TE::Return(_) + | TE::Abort(_) + | TE::Mutate(_, _) + | TE::Give(_, _) + | TE::Continue(_) + | TE::UnresolvedError => false, + } +} diff --git a/external-crates/move/crates/move-compiler/src/linters/self_assignment.rs b/external-crates/move/crates/move-compiler/src/linters/self_assignment.rs new file mode 100644 index 0000000000000..808b55ee9ff44 --- /dev/null +++ b/external-crates/move/crates/move-compiler/src/linters/self_assignment.rs @@ -0,0 +1,211 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +//! Detects and reports explicit self-assignments in code, such as `x = x;`, which are generally unnecessary +//! and could indicate potential errors or misunderstandings in the code logic. +use super::StyleCodes; +use crate::{ + diag, + diagnostics::WarningFilters, + naming::ast::Var, + shared::CompilationEnv, + typing::{ + ast::{self as T}, + visitor::{TypingVisitorConstructor, TypingVisitorContext}, + }, +}; +use move_ir_types::location::Loc; +use move_proc_macros::growing_stack; + +pub struct SelfAssignmentVisitor; + +pub struct Context<'a> { + env: &'a mut CompilationEnv, +} + +impl TypingVisitorConstructor for SelfAssignmentVisitor { + type Context<'a> = Context<'a>; + + fn context<'a>(env: &'a mut CompilationEnv, _program: &T::Program) -> Self::Context<'a> { + Context { env } + } +} + +impl TypingVisitorContext for Context<'_> { + fn add_warning_filter_scope(&mut self, filter: WarningFilters) { + self.env.add_warning_filter_scope(filter) + } + + fn pop_warning_filter_scope(&mut self) { + self.env.pop_warning_filter_scope() + } + + fn visit_exp_custom(&mut self, e: &T::Exp) -> bool { + use T::UnannotatedExp_ as E; + match &e.exp.value { + E::Mutate(lhs, rhs) => check_mutate(self, e.exp.loc, lhs, rhs), + E::Assign(lvalues, _, rhs) => check_assign(self, lvalues, rhs), + _ => (), + } + false + } +} + +fn check_mutate(context: &mut Context, loc: Loc, lhs: &T::Exp, rhs: &T::Exp) { + #[growing_stack] + fn same_memory_location(lhs: &T::Exp, rhs: &T::Exp) -> Option<(Loc, Loc)> { + use T::UnannotatedExp_ as E; + let lhs = inner_exp(lhs); + let rhs = inner_exp(rhs); + match &lhs.exp.value { + E::Unit { .. } + | E::Value(_) + | E::Constant(_, _) + | E::ModuleCall(_) + | E::Vector(_, _, _, _) + | E::IfElse(_, _, _) + | E::Match(_, _) + | E::VariantMatch(_, _, _) + | E::While(_, _, _) + | E::Loop { .. } + | E::Assign(_, _, _) + | E::Mutate(_, _) + | E::Return(_) + | E::Abort(_) + | E::Continue(_) + | E::Give(_, _) + | E::Dereference(_) + | E::UnaryExp(_, _) + | E::BinopExp(_, _, _, _) + | E::Pack(_, _, _, _) + | E::PackVariant(_, _, _, _, _) + | E::ExpList(_) + | E::TempBorrow(_, _) + | E::Cast(_, _) + | E::ErrorConstant { .. } + | E::UnresolvedError => None, + E::Block(s) | E::NamedBlock(_, s) => { + debug_assert!(s.1.len() > 1); + None + } + + E::Move { var: l, .. } | E::Copy { var: l, .. } | E::Use(l) | E::BorrowLocal(_, l) => { + same_local(l, rhs) + } + E::Builtin(b1, l) => { + if !gives_memory_location(b1) { + return None; + } + match &rhs.exp.value { + E::Builtin(b2, r) if b1 == b2 => same_memory_location(l, r), + _ => None, + } + } + E::Borrow(_, l, lfield) => match &rhs.exp.value { + E::Borrow(_, r, rfield) if lfield == rfield => { + same_memory_location(l, r)?; + Some((lhs.exp.loc, rhs.exp.loc)) + } + _ => None, + }, + + E::Annotate(_, _) => unreachable!(), + } + } + + let rhs = inner_exp(rhs); + let rhs = match &rhs.exp.value { + T::UnannotatedExp_::Dereference(inner) => inner, + _ => rhs, + }; + let Some((lhs_loc, rhs_loc)) = same_memory_location(lhs, rhs) else { + return; + }; + report_self_assignment(context, "mutation", loc, lhs_loc, rhs_loc); +} + +fn check_assign(context: &mut Context, sp!(_, lvalues_): &T::LValueList, rhs: &T::Exp) { + let vars = lvalues_.iter().map(lvalue_var).collect::>(); + let rhs_items = exp_list_items(rhs); + for (lhs_opt, rhs) in vars.into_iter().zip(rhs_items) { + let Some((loc, lhs)) = lhs_opt else { + continue; + }; + if let Some((lhs_loc, rhs_loc)) = same_local(lhs, rhs) { + report_self_assignment(context, "assignment", loc, lhs_loc, rhs_loc); + } + } +} + +fn same_local(lhs: &Var, rhs: &T::Exp) -> Option<(Loc, Loc)> { + use T::UnannotatedExp_ as E; + match &rhs.exp.value { + E::Copy { var: r, .. } | E::Move { var: r, .. } | E::BorrowLocal(_, r) => { + if lhs == r { + Some((lhs.loc, r.loc)) + } else { + None + } + } + _ => None, + } +} + +fn gives_memory_location(sp!(_, b_): &T::BuiltinFunction) -> bool { + match b_ { + T::BuiltinFunction_::Freeze(_) => true, + T::BuiltinFunction_::Assert(_) => false, + } +} + +fn inner_exp(mut e: &T::Exp) -> &T::Exp { + use T::UnannotatedExp_ as E; + loop { + match &e.exp.value { + E::Annotate(inner, _) => e = inner, + E::Block((_, seq)) | E::NamedBlock(_, (_, seq)) if seq.len() == 1 => { + match &seq[0].value { + T::SequenceItem_::Seq(inner) => e = inner, + T::SequenceItem_::Declare(_) | T::SequenceItem_::Bind(_, _, _) => break e, + } + } + _ => break e, + } + } +} + +fn lvalue_var(sp!(loc, lvalue_): &T::LValue) -> Option<(Loc, &Var)> { + use T::LValue_ as L; + match &lvalue_ { + L::Var { var, .. } => Some((*loc, var)), + L::Ignore + | L::Unpack(_, _, _, _) + | L::BorrowUnpack(_, _, _, _, _) + | L::UnpackVariant(_, _, _, _, _) + | L::BorrowUnpackVariant(_, _, _, _, _, _) => None, + } +} + +fn exp_list_items(e: &T::Exp) -> Vec<&T::Exp> { + match &inner_exp(e).exp.value { + T::UnannotatedExp_::ExpList(items) => items + .iter() + .flat_map(|item| match item { + T::ExpListItem::Single(e, _) => vec![e], + T::ExpListItem::Splat(_, e, _) => exp_list_items(e), + }) + .collect::>(), + _ => vec![e], + } +} + +fn report_self_assignment(context: &mut Context, case: &str, eloc: Loc, lloc: Loc, rloc: Loc) { + let msg = + format!("Unnecessary self-{case}. The {case} is redundant and will not change the value"); + context.env.add_diag(diag!( + StyleCodes::SelfAssignment.diag_info(), + (eloc, msg), + (lloc, "This location"), + (rloc, "Is the same as this location"), + )); +} diff --git a/external-crates/move/crates/move-compiler/src/naming/ast.rs b/external-crates/move/crates/move-compiler/src/naming/ast.rs index a1f99050aa16d..52ef5c5aae880 100644 --- a/external-crates/move/crates/move-compiler/src/naming/ast.rs +++ b/external-crates/move/crates/move-compiler/src/naming/ast.rs @@ -229,6 +229,7 @@ pub struct Function { // index in the original order as defined in the source file pub index: usize, pub attributes: Attributes, + pub loc: Loc, pub visibility: Visibility, pub entry: Option, pub macro_: Option, @@ -1068,7 +1069,7 @@ impl AstDebug for Program_ { fn ast_debug(&self, w: &mut AstWriter) { let Self { modules } = self; for (m, mdef) in modules.key_cloned_iter() { - w.write(&format!("module {}", m)); + w.write(format!("module {}", m)); w.block(|w| mdef.ast_debug(w)); w.new_line(); } @@ -1106,7 +1107,7 @@ impl AstDebug for UseFun { UseFunKind::FunctionDeclaration => "#fundecl", }; let usage = if *used { "#used" } else { "#unused" }; - w.write(&format!("use{kind_str}{usage} {target_m}::{target_f}")); + w.write(format!("use{kind_str}{usage} {target_m}::{target_f}")); } } @@ -1117,7 +1118,7 @@ impl AstDebug for (&TypeName, &UniqueMap) { use_fun.ast_debug(w); w.write(" as "); tn.ast_debug(w); - w.writeln(&format!(".{method_f};")); + w.writeln(format!(".{method_f};")); } } } @@ -1137,7 +1138,7 @@ impl AstDebug for UseFuns { resolved, implicit_candidates, } = self; - w.write(&format!("use_funs#{} ", color)); + w.write(format!("use_funs#{} ", color)); resolved.ast_debug(w); if !implicit_candidates.is_empty() { w.write("unresolved "); @@ -1161,7 +1162,7 @@ impl AstDebug for SyntaxMethod { kind, } = self; let kind_str = format!("{:?}", kind.value); - w.write(&format!( + w.write(format!( "syntax({kind_str}) for {tname} -> {target_m}::{target_f}\n" )); } @@ -1209,7 +1210,7 @@ impl AstDebug for ModuleDefinition { } = self; warning_filter.ast_debug(w); if let Some(n) = package_name { - w.writeln(&format!("{}", n)) + w.writeln(format!("{}", n)) } attributes.ast_debug(w); w.writeln(match target_kind { @@ -1224,7 +1225,7 @@ impl AstDebug for ModuleDefinition { use_funs.ast_debug(w); syntax_methods.ast_debug(w); for (mident, _loc) in friends.key_cloned_iter() { - w.write(&format!("friend {};", mident)); + w.write(format!("friend {};", mident)); w.new_line(); } for sdef in structs.key_cloned_iter() { @@ -1265,7 +1266,7 @@ impl AstDebug for (DatatypeName, &StructDefinition) { if let StructFields::Native(_) = fields { w.write("native "); } - w.write(&format!("struct#{index} {name}")); + w.write(format!("struct#{index} {name}")); type_parameters.ast_debug(w); ability_modifiers_ast_debug(w, abilities); if let StructFields::Defined(is_positional, fields) = fields { @@ -1275,7 +1276,7 @@ impl AstDebug for (DatatypeName, &StructDefinition) { w.block(|w| { w.list(fields, ",", |w, (_, f, idx_st)| { let (idx, st) = idx_st; - w.write(&format!("{}#{}: ", idx, f)); + w.write(format!("{}#{}: ", idx, f)); st.ast_debug(w); true }) @@ -1301,7 +1302,7 @@ impl AstDebug for (DatatypeName, &EnumDefinition) { warning_filter.ast_debug(w); attributes.ast_debug(w); - w.write(&format!("enum#{index} {name}")); + w.write(format!("enum#{index} {name}")); type_parameters.ast_debug(w); ability_modifiers_ast_debug(w, abilities); w.block(|w| { @@ -1323,7 +1324,7 @@ impl AstDebug for (VariantName, &VariantDefinition) { }, ) = self; - w.write(&format!("variant#{index} {name}")); + w.write(format!("variant#{index} {name}")); match fields { VariantFields::Defined(is_positional, fields) => { if *is_positional { @@ -1332,7 +1333,7 @@ impl AstDebug for (VariantName, &VariantDefinition) { w.block(|w| { w.list(fields, ",", |w, (_, f, idx_st)| { let (idx, st) = idx_st; - w.write(&format!("{}#{}: ", idx, f)); + w.write(format!("{}#{}: ", idx, f)); st.ast_debug(w); true }); @@ -1351,6 +1352,7 @@ impl AstDebug for (FunctionName, &Function) { warning_filter, index, attributes, + loc: _, visibility, macro_, entry, @@ -1362,15 +1364,15 @@ impl AstDebug for (FunctionName, &Function) { attributes.ast_debug(w); visibility.ast_debug(w); if entry.is_some() { - w.write(&format!("{} ", ENTRY_MODIFIER)); + w.write(format!("{} ", ENTRY_MODIFIER)); } if macro_.is_some() { - w.write(&format!("{} ", MACRO_MODIFIER)); + w.write(format!("{} ", MACRO_MODIFIER)); } if let FunctionBody_::Native = &body.value { - w.write(&format!("{} ", NATIVE_MODIFIER)); + w.write(format!("{} ", NATIVE_MODIFIER)); } - w.write(&format!("fun#{index} {name}")); + w.write(format!("fun#{index} {name}")); signature.ast_debug(w); match &body.value { FunctionBody_::Defined(body) => body.ast_debug(w), @@ -1404,12 +1406,12 @@ impl AstDebug for Var_ { let Self { name, id, color } = self; let id = *id; let color = *color; - w.write(&format!("{name}")); + w.write(format!("{name}")); if id != 0 { - w.write(&format!("#{id}")); + w.write(format!("#{id}")); } if color != 0 { - w.write(&format!("#{color}")); + w.write(format!("#{color}")); } } } @@ -1420,12 +1422,12 @@ impl AstDebug for BlockLabel { is_implicit: _, label: sp!(_, Var_ { name, id, color }), } = self; - w.write(&format!("'{name}")); + w.write(format!("'{name}")); if *id != 0 { - w.write(&format!("#{id}")); + w.write(format!("#{id}")); } if *color != 0 { - w.write(&format!("#{color}")); + w.write(format!("#{color}")); } } } @@ -1465,7 +1467,7 @@ impl AstDebug for (ConstantName, &Constant) { ) = self; warning_filter.ast_debug(w); attributes.ast_debug(w); - w.write(&format!("const#{index} {name}:")); + w.write(format!("const#{index} {name}:")); signature.ast_debug(w); w.write(" = "); value.ast_debug(w); @@ -1475,16 +1477,16 @@ impl AstDebug for (ConstantName, &Constant) { impl AstDebug for BuiltinTypeName_ { fn ast_debug(&self, w: &mut AstWriter) { - w.write(&format!("{}", self)); + w.write(format!("{}", self)); } } impl AstDebug for TypeName_ { fn ast_debug(&self, w: &mut AstWriter) { match self { - TypeName_::Multiple(len) => w.write(&format!("Multiple({})", len)), + TypeName_::Multiple(len) => w.write(format!("Multiple({})", len)), TypeName_::Builtin(bt) => bt.ast_debug(w), - TypeName_::ModuleType(m, s) => w.write(&format!("{}::{}", m, s)), + TypeName_::ModuleType(m, s) => w.write(format!("{}::{}", m, s)), } } } @@ -1496,7 +1498,7 @@ impl AstDebug for TParam { user_specified_name, abilities, } = self; - w.write(&format!("{}#{}", user_specified_name, id.0)); + w.write(format!("{}#{}", user_specified_name, id.0)); ability_constraints_ast_debug(w, abilities); } } @@ -1564,7 +1566,7 @@ impl AstDebug for Type_ { w.write("|"); result.ast_debug(w); } - Type_::Var(tv) => w.write(&format!("#{}", tv.0)), + Type_::Var(tv) => w.write(format!("#{}", tv.0)), Type_::Anything => w.write("_"), Type_::UnresolvedError => w.write("_|_"), } @@ -1619,9 +1621,9 @@ impl AstDebug for Exp_ { } => w.write("/*()*/"), E::Value(v) => v.ast_debug(w), E::Var(v) => v.ast_debug(w), - E::Constant(m, c) => w.write(&format!("{}::{}", m, c)), + E::Constant(m, c) => w.write(format!("{}::{}", m, c)), E::ModuleCall(m, f, is_macro, tys_opt, sp!(_, rhs)) => { - w.write(&format!("{}::{}", m, f)); + w.write(format!("{}::{}", m, f)); if is_macro.is_some() { w.write("!"); } @@ -1636,7 +1638,7 @@ impl AstDebug for Exp_ { } E::MethodCall(e, f, is_macro, tys_opt, sp!(_, rhs)) => { e.ast_debug(w); - w.write(&format!(".{}", f)); + w.write(format!(".{}", f)); if is_macro.is_some() { w.write("!"); } @@ -1673,7 +1675,7 @@ impl AstDebug for Exp_ { w.write("]"); } E::Pack(m, s, tys_opt, fields) => { - w.write(&format!("{}::{}", m, s)); + w.write(format!("{}::{}", m, s)); if let Some(ss) = tys_opt { w.write("<"); ss.ast_debug(w); @@ -1682,13 +1684,13 @@ impl AstDebug for Exp_ { w.write("{"); w.comma(fields, |w, (_, f, idx_e)| { let (idx, e) = idx_e; - w.write(&format!("{}#{}: ", idx, f)); + w.write(format!("{}#{}: ", idx, f)); e.ast_debug(w); }); w.write("}"); } E::PackVariant(m, e, v, tys_opt, fields) => { - w.write(&format!("{}::{}::{}", m, e, v)); + w.write(format!("{}::{}::{}", m, e, v)); if let Some(ss) = tys_opt { w.write("<"); ss.ast_debug(w); @@ -1697,7 +1699,7 @@ impl AstDebug for Exp_ { w.write("{"); w.comma(fields, |w, (_, f, idx_e)| { let (idx, e) = idx_e; - w.write(&format!("{}#{}: ", idx, f)); + w.write(format!("{}#{}: ", idx, f)); e.ast_debug(w); }); w.write("}"); @@ -1769,7 +1771,7 @@ impl AstDebug for Exp_ { e.ast_debug(w); } E::Give(usage, name, e) => { - w.write(&format!("give#{usage} '")); + w.write(format!("give#{usage} '")); name.ast_debug(w); w.write(" "); e.ast_debug(w); @@ -1841,7 +1843,7 @@ impl AstDebug for Lambda { w.write(" -> "); ty.ast_debug(w); } - w.write(&format!("use_funs#{}", use_fun_color)); + w.write(format!("use_funs#{}", use_fun_color)); e.ast_debug(w); } } @@ -1887,7 +1889,7 @@ impl AstDebug for ExpDotted_ { D::Exp(e) => e.ast_debug(w), D::Dot(e, n) => { e.ast_debug(w); - w.write(&format!(".{}", n)) + w.write(format!(".{}", n)) } D::Index(e, sp!(_, args)) => { e.ast_debug(w); @@ -2016,7 +2018,7 @@ impl AstDebug for LValue_ { } } L::Unpack(m, s, tys_opt, fields) => { - w.write(&format!("{}::{}", m, s)); + w.write(format!("{}::{}", m, s)); if let Some(ss) = tys_opt { w.write("<"); ss.ast_debug(w); @@ -2025,7 +2027,7 @@ impl AstDebug for LValue_ { w.write("{"); w.comma(fields, |w, (_, f, idx_b)| { let (idx, b) = idx_b; - w.write(&format!("{}#{}: ", idx, f)); + w.write(format!("{}#{}: ", idx, f)); b.ast_debug(w); }); w.write("}"); diff --git a/external-crates/move/crates/move-compiler/src/naming/translate.rs b/external-crates/move/crates/move-compiler/src/naming/translate.rs index f238eb60f6ff1..c69876a26eb4a 100644 --- a/external-crates/move/crates/move-compiler/src/naming/translate.rs +++ b/external-crates/move/crates/move-compiler/src/naming/translate.rs @@ -12,7 +12,7 @@ use crate::{ editions::FeatureGate, expansion::{ ast::{self as E, AbilitySet, Ellipsis, ModuleIdent, Mutability, Visibility}, - translate::is_valid_datatype_or_constant_name as is_constant_name, + name_validation::is_valid_datatype_or_constant_name as is_constant_name, }, ice, naming::{ @@ -1985,7 +1985,7 @@ fn function( warning_filter, index, attributes, - loc: _, + loc, visibility, macro_, entry, @@ -2026,6 +2026,7 @@ fn function( warning_filter, index, attributes, + loc, visibility, macro_, entry, diff --git a/external-crates/move/crates/move-compiler/src/parser/ast.rs b/external-crates/move/crates/move-compiler/src/parser/ast.rs index edfddcb304f2b..ee3cc68d11b61 100644 --- a/external-crates/move/crates/move-compiler/src/parser/ast.rs +++ b/external-crates/move/crates/move-compiler/src/parser/ast.rs @@ -1348,7 +1348,7 @@ fn ast_debug_package_definition( def, } = pkg; match package { - Some(n) => w.writeln(&format!("package: {}", n)), + Some(n) => w.writeln(format!("package: {}", n)), None => w.writeln("no package"), } named_address_maps.get(*named_address_map).ast_debug(w); @@ -1358,7 +1358,7 @@ fn ast_debug_package_definition( impl AstDebug for NamedAddressMap { fn ast_debug(&self, w: &mut AstWriter) { for (sym, addr) in self { - w.write(&format!("{} => {}", sym, addr)); + w.write(format!("{} => {}", sym, addr)); w.new_line() } } @@ -1382,7 +1382,7 @@ impl AstDebug for AddressDefinition { modules, } = self; attributes.ast_debug(w); - w.write(&format!("address {}", addr)); + w.write(format!("address {}", addr)); w.writeln(" {{"); for m in modules { m.ast_debug(w) @@ -1403,14 +1403,14 @@ impl AstDebug for AttributeValue_ { impl AstDebug for Attribute_ { fn ast_debug(&self, w: &mut AstWriter) { match self { - Attribute_::Name(n) => w.write(&format!("{}", n)), + Attribute_::Name(n) => w.write(format!("{}", n)), Attribute_::Assigned(n, v) => { - w.write(&format!("{}", n)); + w.write(format!("{}", n)); w.write(" = "); v.ast_debug(w); } Attribute_::Parameterized(n, inners) => { - w.write(&format!("{}", n)); + w.write(format!("{}", n)); w.write("("); w.list(&inners.value, ", ", |w, inner| { inner.ast_debug(w); @@ -1455,12 +1455,12 @@ impl AstDebug for ModuleDefinition { } = self; attributes.ast_debug(w); match address { - None => w.write(&format!( + None => w.write(format!( "module {}{}", if *is_spec_module { "spec " } else { "" }, name )), - Some(addr) => w.write(&format!("module {}::{}", addr, name)), + Some(addr) => w.write(format!("module {}::{}", addr, name)), }; w.block(|w| { for mem in members { @@ -1500,12 +1500,12 @@ impl AstDebug for ModuleUse { fn ast_debug(&self, w: &mut AstWriter) { match self { ModuleUse::Module(alias) => { - alias.map(|alias| w.write(&format!("as {}", alias))); + alias.map(|alias| w.write(format!("as {}", alias))); } ModuleUse::Members(members) => w.block(|w| { w.comma(members, |w, (name, alias)| { - w.write(&format!("{}", name)); - alias.map(|alias| w.write(&format!("as {}", alias.value))); + w.write(format!("{}", name)); + alias.map(|alias| w.write(format!("as {}", alias.value))); }) }), ModuleUse::Partial { @@ -1524,14 +1524,14 @@ impl AstDebug for Use { w.write("use "); match self { Use::ModuleUse(mident, use_) => { - w.write(&format!("{}", mident)); + w.write(format!("{}", mident)); use_.ast_debug(w); } Use::NestedModuleUses(addr, entries) => { - w.write(&format!("{}::", addr)); + w.write(format!("{}::", addr)); w.block(|w| { w.comma(entries, |w, (name, use_)| { - w.write(&format!("{}::", name)); + w.write(format!("{}::", name)); use_.ast_debug(w); }) }) @@ -1571,7 +1571,7 @@ impl AstDebug for FriendDecl { friend, } = self; attributes.ast_debug(w); - w.write(&format!("friend {}", friend)); + w.write(format!("friend {}", friend)); } } @@ -1596,7 +1596,7 @@ impl AstDebug for EnumDefinition { w.write("]"); } - w.write(&format!(" enum {}", name)); + w.write(format!(" enum {}", name)); type_parameters.ast_debug(w); w.block(|w| { w.list(variants, ",", |w, variant| { @@ -1614,17 +1614,17 @@ impl AstDebug for VariantDefinition { name, fields, } = self; - w.write(&format!("{}", name)); + w.write(format!("{}", name)); match fields { VariantFields::Named(fields) => w.block(|w| { w.semicolon(fields, |w, (f, st)| { - w.write(&format!("{}: ", f)); + w.write(format!("{}: ", f)); st.ast_debug(w); }); }), VariantFields::Positional(types) => w.block(|w| { w.semicolon(types.iter().enumerate(), |w, (i, st)| { - w.write(&format!("pos{}: ", i)); + w.write(format!("pos{}: ", i)); st.ast_debug(w); }); }), @@ -1654,18 +1654,18 @@ impl AstDebug for StructDefinition { w.write("native "); } - w.write(&format!("struct {}", name)); + w.write(format!("struct {}", name)); type_parameters.ast_debug(w); match fields { StructFields::Named(fields) => w.block(|w| { w.semicolon(fields, |w, (f, st)| { - w.write(&format!("{}: ", f)); + w.write(format!("{}: ", f)); st.ast_debug(w); }); }), StructFields::Positional(types) => w.block(|w| { w.semicolon(types.iter().enumerate(), |w, (i, st)| { - w.write(&format!("pos{}: ", i)); + w.write(format!("pos{}: ", i)); st.ast_debug(w); }); }), @@ -1689,15 +1689,15 @@ impl AstDebug for Function { attributes.ast_debug(w); visibility.ast_debug(w); if entry.is_some() { - w.write(&format!("{} ", ENTRY_MODIFIER)); + w.write(format!("{} ", ENTRY_MODIFIER)); } if macro_.is_some() { - w.write(&format!("{} ", MACRO_MODIFIER)); + w.write(format!("{} ", MACRO_MODIFIER)); } if let FunctionBody_::Native = &body.value { - w.write(&format!("{} ", NATIVE_MODIFIER)); + w.write(format!("{} ", NATIVE_MODIFIER)); } - w.write(&format!("fun {}", name)); + w.write(format!("fun {}", name)); signature.ast_debug(w); match &body.value { FunctionBody_::Defined(body) => w.block(|w| body.ast_debug(w)), @@ -1708,7 +1708,7 @@ impl AstDebug for Function { impl AstDebug for Visibility { fn ast_debug(&self, w: &mut AstWriter) { - w.write(&format!("{} ", self)) + w.write(format!("{} ", self)) } } @@ -1725,7 +1725,7 @@ impl AstDebug for FunctionSignature { if mut_.is_some() { w.write("mut "); } - w.write(&format!("{}: ", v)); + w.write(format!("{}: ", v)); st.ast_debug(w); }); w.write(")"); @@ -1744,7 +1744,7 @@ impl AstDebug for Constant { value, } = self; attributes.ast_debug(w); - w.write(&format!("const {}:", name)); + w.write(format!("const {}:", name)); signature.ast_debug(w); w.write(" = "); value.ast_debug(w); @@ -1807,7 +1807,7 @@ fn ability_constraints_ast_debug(w: &mut AstWriter, abilities: &[Ability]) { impl AstDebug for Ability_ { fn ast_debug(&self, w: &mut AstWriter) { - w.write(&format!("{}", self)) + w.write(format!("{}", self)) } } @@ -1996,7 +1996,7 @@ impl AstDebug for Exp_ { ma.ast_debug(w); w.write("{"); w.comma(fields, |w, (f, e)| { - w.write(&format!("{}: ", f)); + w.write(format!("{}: ", f)); e.ast_debug(w); }); w.write("}"); @@ -2127,11 +2127,11 @@ impl AstDebug for Exp_ { } E::Dot(e, n) => { e.ast_debug(w); - w.write(&format!(".{}", n)); + w.write(format!(".{}", n)); } E::DotCall(e, n, is_macro, tyargs, sp!(_, rhs)) => { e.ast_debug(w); - w.write(&format!(".{}", n)); + w.write(format!(".{}", n)); if is_macro.is_some() { w.write("!"); } @@ -2211,7 +2211,7 @@ impl AstDebug for Ellipsis<(Field, MatchPattern)> { w.write(".."); } Ellipsis::Binder((n, p)) => { - w.write(&format!("{}: ", n)); + w.write(format!("{}: ", n)); p.ast_debug(w); } } @@ -2258,13 +2258,13 @@ impl AstDebug for MatchPattern_ { impl AstDebug for BinOp_ { fn ast_debug(&self, w: &mut AstWriter) { - w.write(&format!("{}", self)); + w.write(format!("{}", self)); } } impl AstDebug for UnaryOp_ { fn ast_debug(&self, w: &mut AstWriter) { - w.write(&format!("{}", self)); + w.write(format!("{}", self)); } } @@ -2344,7 +2344,7 @@ impl AstDebug for Bind_ { if mut_.is_some() { w.write("mut "); } - w.write(&format!("{}", v)) + w.write(format!("{}", v)) } B::Unpack(ma, fields) => { ma.ast_debug(w); @@ -2375,7 +2375,7 @@ impl AstDebug for Ellipsis<(Field, Bind)> { w.write(".."); } Ellipsis::Binder((n, b)) => { - w.write(&format!("{}: ", n)); + w.write(format!("{}: ", n)); b.ast_debug(w); } } diff --git a/external-crates/move/crates/move-compiler/src/parser/lexer.rs b/external-crates/move/crates/move-compiler/src/parser/lexer.rs index 01113f71c6603..610d978e733fb 100644 --- a/external-crates/move/crates/move-compiler/src/parser/lexer.rs +++ b/external-crates/move/crates/move-compiler/src/parser/lexer.rs @@ -355,7 +355,7 @@ impl<'input> Lexer<'input> { if is_doc { let end = get_offset(text); let mut comment = &self.text[(start + 3)..end]; - comment = comment.trim_end_matches(|c: char| c == '\r'); + comment = comment.trim_end_matches('\r'); self.doc_comments .insert((start as u32, end as u32), comment.to_string()); diff --git a/external-crates/move/crates/move-compiler/src/parser/syntax.rs b/external-crates/move/crates/move-compiler/src/parser/syntax.rs index ed89548d5e368..7fdf9221d598a 100644 --- a/external-crates/move/crates/move-compiler/src/parser/syntax.rs +++ b/external-crates/move/crates/move-compiler/src/parser/syntax.rs @@ -3188,18 +3188,16 @@ fn parse_function_decl( ); let return_type = parse_ret_type(context, name) - .map_err(|diag| { + .inspect_err(|diag| { context.advance_until_stop_set(Some(*diag.clone())); - diag }) .ok(); context.stop_set.remove(Tok::LBrace); let body = parse_body(context, native) - .map_err(|diag| { + .inspect_err(|diag| { context.advance_until_stop_set(Some(*diag.clone())); - diag }) .ok(); @@ -3560,11 +3558,10 @@ fn parse_struct_decl( let mut abilities = if infix_ability_declaration_loc.is_some() { parse_infix_ability_declarations(context) - .map_err(|diag| { + .inspect_err(|diag| { // if parsing failed, assume no abilities present even if `has` keyword was present infix_ability_declaration_loc = None; context.advance_until_stop_set(Some(*diag.clone())); - diag }) .unwrap_or_default() } else { @@ -3610,9 +3607,8 @@ fn parse_struct_decl( infix_ability_declaration_loc, &mut abilities, ) - .map_err(|diag| { + .inspect_err(|diag| { context.advance_until_stop_set(Some(*diag.clone())); - diag }) .ok(); } diff --git a/external-crates/move/crates/move-compiler/src/shared/mod.rs b/external-crates/move/crates/move-compiler/src/shared/mod.rs index 90a7ef53ac2fa..a242bcb7565ef 100644 --- a/external-crates/move/crates/move-compiler/src/shared/mod.rs +++ b/external-crates/move/crates/move-compiler/src/shared/mod.rs @@ -943,9 +943,9 @@ impl Default for PackageConfig { //************************************************************************************************** pub struct Visitors { - pub typing: Vec>, - pub abs_int: Vec>, - pub cfgir: Vec>, + pub typing: Vec, + pub abs_int: Vec, + pub cfgir: Vec, } impl Visitors { @@ -958,15 +958,24 @@ impl Visitors { }; for pass in passes { match pass { - Visitor::AbsIntVisitor(f) => vs.abs_int.push(RefCell::new(f)), - Visitor::TypingVisitor(f) => vs.typing.push(RefCell::new(f)), - Visitor::CFGIRVisitor(f) => vs.cfgir.push(RefCell::new(f)), + Visitor::AbsIntVisitor(f) => vs.abs_int.push(f), + Visitor::TypingVisitor(f) => vs.typing.push(f), + Visitor::CFGIRVisitor(f) => vs.cfgir.push(f), } } vs } } +// TODO remove it once visitor invocation is parallel +#[allow(unused)] +fn check() {} +#[allow(unused)] +fn check_all() { + check::(); + check::<&Visitors>(); +} + //************************************************************************************************** // Save Hooks //************************************************************************************************** diff --git a/external-crates/move/crates/move-compiler/src/sui_mode/linters/coin_field.rs b/external-crates/move/crates/move-compiler/src/sui_mode/linters/coin_field.rs index 9c5a02016da50..2af05f7f6e247 100644 --- a/external-crates/move/crates/move-compiler/src/sui_mode/linters/coin_field.rs +++ b/external-crates/move/crates/move-compiler/src/sui_mode/linters/coin_field.rs @@ -30,7 +30,7 @@ const COIN_FIELD_DIAG: DiagnosticInfo = custom( pub struct CoinFieldVisitor; impl TypingVisitor for CoinFieldVisitor { - fn visit(&mut self, env: &mut CompilationEnv, program: &T::Program) { + fn visit(&self, env: &mut CompilationEnv, program: &T::Program) { for (_, _, mdef) in program.modules.iter() { if mdef.attributes.is_test_or_test_only() { continue; diff --git a/external-crates/move/crates/move-compiler/src/sui_mode/typing.rs b/external-crates/move/crates/move-compiler/src/sui_mode/typing.rs index f88fd5eee2e26..b895758f2602f 100644 --- a/external-crates/move/crates/move-compiler/src/sui_mode/typing.rs +++ b/external-crates/move/crates/move-compiler/src/sui_mode/typing.rs @@ -279,6 +279,7 @@ fn function(context: &mut Context, name: FunctionName, fdef: &T::Function) { index: _, macro_: _, attributes, + loc: _, entry, } = fdef; let prev_in_test = context.in_test; diff --git a/external-crates/move/crates/move-compiler/src/to_bytecode/translate.rs b/external-crates/move/crates/move-compiler/src/to_bytecode/translate.rs index b16e8c6d8fba5..bb1ba8ab8727b 100644 --- a/external-crates/move/crates/move-compiler/src/to_bytecode/translate.rs +++ b/external-crates/move/crates/move-compiler/src/to_bytecode/translate.rs @@ -811,11 +811,11 @@ fn base_types(context: &mut Context, bs: Vec) -> Vec { bs.into_iter().map(|b| base_type(context, b)).collect() } -fn base_type(context: &mut Context, sp!(_, bt_): H::BaseType) -> IR::Type { +fn base_type(context: &mut Context, sp!(bt_loc, bt_): H::BaseType) -> IR::Type { use BuiltinTypeName_ as BT; use H::{BaseType_ as B, TypeName_ as TN}; - use IR::Type as IRT; - match bt_ { + use IR::Type_ as IRT; + let type_ = match bt_ { B::Unreachable | B::UnresolvedError => { panic!("ICE should not have reached compilation if there are errors") } @@ -845,15 +845,19 @@ fn base_type(context: &mut Context, sp!(_, bt_): H::BaseType) -> IR::Type { user_specified_name, .. }) => IRT::TypeParameter(type_var(user_specified_name).value), - } + }; + sp(bt_loc, type_) } -fn single_type(context: &mut Context, sp!(_, st_): H::SingleType) -> IR::Type { +fn single_type(context: &mut Context, sp!(st_loc, st_): H::SingleType) -> IR::Type { use H::SingleType_ as S; - use IR::Type as IRT; + use IR::Type_ as IRT; match st_ { S::Base(bt) => base_type(context, bt), - S::Ref(mut_, bt) => IRT::Reference(mut_, Box::new(base_type(context, bt))), + S::Ref(mut_, bt) => sp( + st_loc, + IRT::Reference(mut_, Box::new(base_type(context, bt))), + ), } } diff --git a/external-crates/move/crates/move-compiler/src/typing/ast.rs b/external-crates/move/crates/move-compiler/src/typing/ast.rs index 770721d53ccb3..d847ba757f9ca 100644 --- a/external-crates/move/crates/move-compiler/src/typing/ast.rs +++ b/external-crates/move/crates/move-compiler/src/typing/ast.rs @@ -83,6 +83,7 @@ pub struct Function { // index in the original order as defined in the source file pub index: usize, pub attributes: Attributes, + pub loc: Loc, /// The original, declared visibility as defined in the source file pub visibility: Visibility, /// We sometimes change the visibility of functions, e.g. `entry` is marked as `public` in @@ -413,7 +414,7 @@ impl AstDebug for Program { let Program { modules, info: _ } = self; for (m, mdef) in modules.key_cloned_iter() { - w.write(&format!("module {}", m)); + w.write(format!("module {}", m)); w.block(|w| mdef.ast_debug(w)); w.new_line(); } @@ -441,7 +442,7 @@ impl AstDebug for ModuleDefinition { } = self; warning_filter.ast_debug(w); if let Some(n) = package_name { - w.writeln(&format!("{}", n)) + w.writeln(format!("{}", n)) } attributes.ast_debug(w); w.writeln(match target_kind { @@ -453,20 +454,20 @@ impl AstDebug for ModuleDefinition { } => "dependency module", TargetKind::External => "external module", }); - w.writeln(&format!("dependency order #{}", dependency_order)); + w.writeln(format!("dependency order #{}", dependency_order)); for (mident, neighbor) in immediate_neighbors.key_cloned_iter() { - w.write(&format!("{mident} is")); + w.write(format!("{mident} is")); neighbor.ast_debug(w); w.writeln(";"); } for addr in used_addresses { - w.write(&format!("uses address {};", addr)); + w.write(format!("uses address {};", addr)); w.new_line() } use_funs.ast_debug(w); syntax_methods.ast_debug(w); for (mident, _loc) in friends.key_cloned_iter() { - w.write(&format!("friend {};", mident)); + w.write(format!("friend {};", mident)); w.new_line(); } for sdef in structs.key_cloned_iter() { @@ -496,6 +497,7 @@ impl AstDebug for (FunctionName, &Function) { warning_filter, index, attributes, + loc: _, visibility, compiled_visibility, entry, @@ -512,15 +514,15 @@ impl AstDebug for (FunctionName, &Function) { compiled_visibility.ast_debug(w); w.write(") "); if entry.is_some() { - w.write(&format!("{} ", ENTRY_MODIFIER)); + w.write(format!("{} ", ENTRY_MODIFIER)); } if macro_.is_some() { - w.write(&format!("{} ", MACRO_MODIFIER)); + w.write(format!("{} ", MACRO_MODIFIER)); } if let FunctionBody_::Native = &body.value { - w.write(&format!("{} ", NATIVE_MODIFIER)); + w.write(format!("{} ", NATIVE_MODIFIER)); } - w.write(&format!("fun#{index} {name}")); + w.write(format!("fun#{index} {name}")); signature.ast_debug(w); body.ast_debug(w); } @@ -551,7 +553,7 @@ impl AstDebug for (ConstantName, &Constant) { ) = self; warning_filter.ast_debug(w); attributes.ast_debug(w); - w.write(&format!("const#{index} {name}:")); + w.write(format!("const#{index} {name}:")); signature.ast_debug(w); w.write(" = "); value.ast_debug(w); @@ -632,7 +634,7 @@ impl AstDebug for UnannotatedExp_ { w.write("use@"); v.ast_debug(w) } - E::Constant(m, c) => w.write(&format!("{}::{}", m, c)), + E::Constant(m, c) => w.write(format!("{}::{}", m, c)), E::ModuleCall(mcall) => { mcall.ast_debug(w); } @@ -652,14 +654,14 @@ impl AstDebug for UnannotatedExp_ { w.write("]"); } E::Pack(m, s, tys, fields) => { - w.write(&format!("{}::{}", m, s)); + w.write(format!("{}::{}", m, s)); w.write("<"); tys.ast_debug(w); w.write(">"); w.write("{"); w.comma(fields, |w, (_, f, idx_bt_e)| { let (idx, (bt, e)) = idx_bt_e; - w.write(&format!("({}#{}:", idx, f)); + w.write(format!("({}#{}:", idx, f)); bt.ast_debug(w); w.write("): "); e.ast_debug(w); @@ -667,14 +669,14 @@ impl AstDebug for UnannotatedExp_ { w.write("}"); } E::PackVariant(m, e, v, tys, fields) => { - w.write(&format!("{}::{}::{}", m, e, v)); + w.write(format!("{}::{}::{}", m, e, v)); w.write("<"); tys.ast_debug(w); w.write(">"); w.write("{"); w.comma(fields, |w, (_, f, idx_bt_e)| { let (idx, (bt, e)) = idx_bt_e; - w.write(&format!("({}#{}:", idx, f)); + w.write(format!("({}#{}:", idx, f)); bt.ast_debug(w); w.write("): "); e.ast_debug(w); @@ -806,7 +808,7 @@ impl AstDebug for UnannotatedExp_ { w.write("mut "); } e.ast_debug(w); - w.write(&format!(".{}", f)); + w.write(format!(".{}", f)); } E::TempBorrow(mut_, e) => { w.write("&"); @@ -843,7 +845,7 @@ impl AstDebug for UnannotatedExp_ { } => { w.write("ErrorConstant"); if let Some(c) = error_constant { - w.write(&format!("({})", c)) + w.write(format!("({})", c)) } } } @@ -867,7 +869,7 @@ impl AstDebug for ModuleCall { arguments, method_name: _, } = self; - w.write(&format!("{}::{}", module, name)); + w.write(format!("{}::{}", module, name)); if !parameter_types.is_empty() { w.write("["); if !parameter_types.is_empty() { @@ -941,28 +943,28 @@ impl AstDebug for UnannotatedPat_ { if *mut_ { w.write("mut "); } - w.write(&format!("{}::{}::{}", m, e, v)); + w.write(format!("{}::{}::{}", m, e, v)); w.write("<"); tys.ast_debug(w); w.write(">"); w.write("{"); w.comma(fields, |w, (_, f, idx_bt_a)| { let (idx, (bt, a)) = idx_bt_a; - w.annotate(|w| w.write(&format!("{}#{}", idx, f)), bt); + w.annotate(|w| w.write(format!("{}#{}", idx, f)), bt); w.write(": "); a.ast_debug(w); }); w.write("}"); } UnannotatedPat_::Variant(m, e, v, tys, fields) => { - w.write(&format!("{}::{}::{}", m, e, v)); + w.write(format!("{}::{}::{}", m, e, v)); w.write("<"); tys.ast_debug(w); w.write(">"); w.write("{"); w.comma(fields, |w, (_, f, idx_bt_a)| { let (idx, (bt, a)) = idx_bt_a; - w.annotate(|w| w.write(&format!("{}#{}", idx, f)), bt); + w.annotate(|w| w.write(format!("{}#{}", idx, f)), bt); w.write(": "); a.ast_debug(w); }); @@ -973,35 +975,35 @@ impl AstDebug for UnannotatedPat_ { if *mut_ { w.write("mut "); } - w.write(&format!("{}::{}", m, s)); + w.write(format!("{}::{}", m, s)); w.write("<"); tys.ast_debug(w); w.write(">"); w.write("{"); w.comma(fields, |w, (_, f, idx_bt_a)| { let (idx, (bt, a)) = idx_bt_a; - w.annotate(|w| w.write(&format!("{}#{}", idx, f)), bt); + w.annotate(|w| w.write(format!("{}#{}", idx, f)), bt); w.write(": "); a.ast_debug(w); }); w.write("}"); } UnannotatedPat_::Struct(m, e, tys, fields) => { - w.write(&format!("{}::{}", m, e)); + w.write(format!("{}::{}", m, e)); w.write("<"); tys.ast_debug(w); w.write(">"); w.write("{"); w.comma(fields, |w, (_, f, idx_bt_a)| { let (idx, (bt, a)) = idx_bt_a; - w.annotate(|w| w.write(&format!("{}#{}", idx, f)), bt); + w.annotate(|w| w.write(format!("{}#{}", idx, f)), bt); w.write(": "); a.ast_debug(w); }); w.write("}"); } UnannotatedPat_::Constant(m, c) => { - w.write(&format!("{}::{}", m, c)); + w.write(format!("{}::{}", m, c)); } UnannotatedPat_::Or(lhs, rhs) => { w.write("("); @@ -1071,14 +1073,14 @@ impl AstDebug for LValue_ { st, ), L::Unpack(m, s, tys, fields) => { - w.write(&format!("{}::{}", m, s)); + w.write(format!("{}::{}", m, s)); w.write("<"); tys.ast_debug(w); w.write(">"); w.write("{"); w.comma(fields, |w, (_, f, idx_bt_a)| { let (idx, (bt, a)) = idx_bt_a; - w.annotate(|w| w.write(&format!("{}#{}", idx, f)), bt); + w.annotate(|w| w.write(format!("{}#{}", idx, f)), bt); w.write(": "); a.ast_debug(w); }); @@ -1089,28 +1091,28 @@ impl AstDebug for LValue_ { if *mut_ { w.write("mut "); } - w.write(&format!("{}::{}", m, s)); + w.write(format!("{}::{}", m, s)); w.write("<"); tys.ast_debug(w); w.write(">"); w.write("{"); w.comma(fields, |w, (_, f, idx_bt_a)| { let (idx, (bt, a)) = idx_bt_a; - w.annotate(|w| w.write(&format!("{}#{}", idx, f)), bt); + w.annotate(|w| w.write(format!("{}#{}", idx, f)), bt); w.write(": "); a.ast_debug(w); }); w.write("}"); } L::UnpackVariant(m, e, v, tys, fields) => { - w.write(&format!("{}::{}::{}", m, e, v)); + w.write(format!("{}::{}::{}", m, e, v)); w.write("<"); tys.ast_debug(w); w.write(">"); w.write("{"); w.comma(fields, |w, (_, f, idx_bt_a)| { let (idx, (bt, a)) = idx_bt_a; - w.annotate(|w| w.write(&format!("{}#{}", idx, f)), bt); + w.annotate(|w| w.write(format!("{}#{}", idx, f)), bt); w.write(": "); a.ast_debug(w); }); @@ -1121,14 +1123,14 @@ impl AstDebug for LValue_ { if *mut_ { w.write("mut "); } - w.write(&format!("{}::{}::{}", m, e, v)); + w.write(format!("{}::{}::{}", m, e, v)); w.write("<"); tys.ast_debug(w); w.write(">"); w.write("{"); w.comma(fields, |w, (_, f, idx_bt_a)| { let (idx, (bt, a)) = idx_bt_a; - w.annotate(|w| w.write(&format!("{}#{}", idx, f)), bt); + w.annotate(|w| w.write(format!("{}#{}", idx, f)), bt); w.write(": "); a.ast_debug(w); }); diff --git a/external-crates/move/crates/move-compiler/src/typing/core.rs b/external-crates/move/crates/move-compiler/src/typing/core.rs index bb08249b9bd1e..8c99ff9e66074 100644 --- a/external-crates/move/crates/move-compiler/src/typing/core.rs +++ b/external-crates/move/crates/move-compiler/src/typing/core.rs @@ -941,7 +941,7 @@ impl ast_debug::AstDebug for Subst { let mut tvars = tvars.iter().collect::>(); tvars.sort_by_key(|(v, _)| *v); for (tvar, bt) in tvars { - w.write(&format!("{:?} => ", tvar)); + w.write(format!("{:?} => ", tvar)); bt.ast_debug(w); w.new_line(); } @@ -951,7 +951,7 @@ impl ast_debug::AstDebug for Subst { let mut num_vars = num_vars.keys().collect::>(); num_vars.sort(); for tvar in num_vars { - w.writeln(&format!("{:?}", tvar)) + w.writeln(format!("{:?}", tvar)) } }) } diff --git a/external-crates/move/crates/move-compiler/src/typing/translate.rs b/external-crates/move/crates/move-compiler/src/typing/translate.rs index 8f26d2948ae19..f5e374f2a1817 100644 --- a/external-crates/move/crates/move-compiler/src/typing/translate.rs +++ b/external-crates/move/crates/move-compiler/src/typing/translate.rs @@ -88,7 +88,6 @@ pub fn program( info: Arc::new(module_info), }; for v in &compilation_env.visitors().typing { - let mut v = v.borrow_mut(); v.visit(compilation_env, &prog); } prog @@ -283,6 +282,7 @@ fn function(context: &mut Context, name: FunctionName, f: N::Function) -> T::Fun warning_filter, index, attributes, + loc, visibility, entry, macro_, @@ -315,6 +315,7 @@ fn function(context: &mut Context, name: FunctionName, f: N::Function) -> T::Fun warning_filter, index, attributes, + loc, compiled_visibility, visibility, entry, diff --git a/external-crates/move/crates/move-compiler/src/typing/visitor.rs b/external-crates/move/crates/move-compiler/src/typing/visitor.rs index 6100bc774054c..5e5aa490931d7 100644 --- a/external-crates/move/crates/move-compiler/src/typing/visitor.rs +++ b/external-crates/move/crates/move-compiler/src/typing/visitor.rs @@ -16,8 +16,8 @@ use move_proc_macros::growing_stack; pub type TypingVisitorObj = Box; -pub trait TypingVisitor { - fn visit(&mut self, env: &mut CompilationEnv, program: &T::Program); +pub trait TypingVisitor: Send + Sync { + fn visit(&self, env: &mut CompilationEnv, program: &T::Program); fn visitor(self) -> Visitor where @@ -27,12 +27,12 @@ pub trait TypingVisitor { } } -pub trait TypingVisitorConstructor { +pub trait TypingVisitorConstructor: Send + Sync { type Context<'a>: Sized + TypingVisitorContext; fn context<'a>(env: &'a mut CompilationEnv, program: &T::Program) -> Self::Context<'a>; - fn visit(&mut self, env: &mut CompilationEnv, program: &T::Program) { + fn visit(env: &mut CompilationEnv, program: &T::Program) { let mut context = Self::context(env, program); context.visit(program); } @@ -554,9 +554,9 @@ impl From for TypingVisitorObj { } } -impl TypingVisitor for V { - fn visit(&mut self, env: &mut CompilationEnv, program: &T::Program) { - self.visit(env, program) +impl TypingVisitor for V { + fn visit(&self, env: &mut CompilationEnv, program: &T::Program) { + Self::visit(env, program) } } @@ -564,16 +564,16 @@ impl TypingVisitor for V { // Mut Vistor //************************************************************************************************** -pub trait TypingMutVisitor { - fn visit(&mut self, env: &mut CompilationEnv, program: &mut T::Program); +pub trait TypingMutVisitor: Send + Sync { + fn visit(&self, env: &mut CompilationEnv, program: &mut T::Program); } -pub trait TypingMutVisitorConstructor { +pub trait TypingMutVisitorConstructor: Send + Sync { type Context<'a>: Sized + TypingMutVisitorContext; fn context<'a>(env: &'a mut CompilationEnv, program: &T::Program) -> Self::Context<'a>; - fn visit(&mut self, env: &mut CompilationEnv, program: &mut T::Program) { + fn visit(env: &mut CompilationEnv, program: &mut T::Program) { let mut context = Self::context(env, program); context.visit(program); } @@ -1092,8 +1092,8 @@ pub trait TypingMutVisitorContext { } impl TypingMutVisitor for V { - fn visit(&mut self, env: &mut CompilationEnv, program: &mut T::Program) { - self.visit(env, program) + fn visit(&self, env: &mut CompilationEnv, program: &mut T::Program) { + Self::visit(env, program) } } diff --git a/external-crates/move/crates/move-compiler/tests/linter/correct_redundant_ref_deref.move b/external-crates/move/crates/move-compiler/tests/linter/correct_redundant_ref_deref.move new file mode 100644 index 0000000000000..9d985802b70c9 --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/correct_redundant_ref_deref.move @@ -0,0 +1,10 @@ +module 0x42::M { + struct MyResource has copy, drop{ + value: u64, + } + + public fun test_borrow_deref_ref() { + let resource = MyResource { value: 10 }; + let _ref1 = &resource; + } +} diff --git a/external-crates/move/crates/move-compiler/tests/linter/false_negative_self_assigment.move b/external-crates/move/crates/move-compiler/tests/linter/false_negative_self_assigment.move new file mode 100644 index 0000000000000..49416a182b4cd --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/false_negative_self_assigment.move @@ -0,0 +1,26 @@ +// tests for cases that self-assignment could warn, but currently dont + +module a::m { + fun t(cond: bool, other: u64) { + let x = 0; + x = if (cond) x else x; + x; + + x = if (cond) x else other; + x; + + x = { 0; x }; + x; + + x = { let y = 0; y; x }; + x; + + // TODO move most lints to 2024 + // x = match (cond) { true => x, false => x }; + // x; + + let x = &other; + other = *x; + other; + } +} diff --git a/external-crates/move/crates/move-compiler/tests/linter/incorrect_redundant_ref_deref.exp b/external-crates/move/crates/move-compiler/tests/linter/incorrect_redundant_ref_deref.exp new file mode 100644 index 0000000000000..553be5f47cb10 --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/incorrect_redundant_ref_deref.exp @@ -0,0 +1,172 @@ +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/incorrect_redundant_ref_deref.move:10:21 + │ +10 │ let _ref = &*(&resource); // Redundant borrow-dereference + │ ^^^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/incorrect_redundant_ref_deref.move:15:25 + │ +15 │ let _ref = &mut *(&mut resource); // Redundant mutable borrow-dereference + │ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/incorrect_redundant_ref_deref.move:20:22 + │ +20 │ let _value = *(&resource.value); // Redundant dereference of field borrow + │ ^^^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/incorrect_redundant_ref_deref.move:57:21 + │ +57 │ let _ref = &*(&*(&resource)); // Triple nested borrow-dereference, might be missed + │ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the inner expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/incorrect_redundant_ref_deref.move:57:24 + │ +57 │ let _ref = &*(&*(&resource)); // Triple nested borrow-dereference, might be missed + │ ^^^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +error[E04010]: cannot infer type + ┌─ tests/linter/incorrect_redundant_ref_deref.move:68:13 + │ +68 │ let _value = *((&resource).value); // Complex expression, might be missed + │ ^^^^^^ Could not infer this type. Try adding an annotation + +error[E04007]: incompatible types + ┌─ tests/linter/incorrect_redundant_ref_deref.move:68:22 + │ + 3 │ value: u64, + │ --- Given: 'u64' + · +68 │ let _value = *((&resource).value); // Complex expression, might be missed + │ ^^^^^^^^^^^^^^^^^^^^ + │ │ + │ Invalid dereference. + │ Expected: '&_' + +error[E04010]: cannot infer type + ┌─ tests/linter/incorrect_redundant_ref_deref.move:68:22 + │ +68 │ let _value = *((&resource).value); // Complex expression, might be missed + │ ^^^^^^^^^^^^^^^^^^^^ Could not infer this type. Try adding an annotation + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/incorrect_redundant_ref_deref.move:75:21 + │ +75 │ let _ref = &*&resource.value; // Redundant borrow-dereference on field + │ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/incorrect_redundant_ref_deref.move:80:21 + │ +80 │ let _ref = &*&(&resource).value; // Nested redundant borrow-dereference on field + │ ^^^^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/incorrect_redundant_ref_deref.move:86:21 + │ +86 │ let _ref = &*&0; // Redundant borrow-dereference on literal + │ ^^^ Redundant borrow-dereference detected. Remove this borrow-deref and use the expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/incorrect_redundant_ref_deref.move:94:21 + │ +94 │ let _ref = &*&get_resource(); // Redundant borrow-dereference on function call result + │ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Remove this borrow-deref and use the expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/incorrect_redundant_ref_deref.move:107:21 + │ +107 │ let _ref = &*&(&*&resource.value); // Multiple redundant borrows on field + │ ^^^^^^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the inner expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +error[E04004]: expected a single non-reference type + ┌─ tests/linter/incorrect_redundant_ref_deref.move:107:22 + │ +107 │ let _ref = &*&(&*&resource.value); // Multiple redundant borrows on field + │ ^^^^^^^^^^^^^^^^^^^^ + │ │ │ + │ │ Expected a single non-reference type, but found: '&u64' + │ Invalid borrow + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/incorrect_redundant_ref_deref.move:107:25 + │ +107 │ let _ref = &*&(&*&resource.value); // Multiple redundant borrows on field + │ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[W09002]: unused variable + ┌─ tests/linter/incorrect_redundant_ref_deref.move:111:13 + │ +111 │ let mut resource = MyResource { value: 10 }; + │ ^^^ Unused local variable 'mut'. Consider removing or prefixing with an underscore: '_mut' + │ + = This warning can be suppressed with '#[allow(unused_variable)]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +error[E01002]: unexpected token + ┌─ tests/linter/incorrect_redundant_ref_deref.move:111:17 + │ +111 │ let mut resource = MyResource { value: 10 }; + │ ^^^^^^^^ + │ │ + │ Unexpected 'resource' + │ Expected ';' + +error[E04010]: cannot infer type + ┌─ tests/linter/incorrect_redundant_ref_deref.move:112:21 + │ +112 │ let _ref = &*&mut *&resource; // Mixed mutable and immutable redundant borrows + │ ^^^^^^^^^^^^^^^^ Could not infer this type. Try adding an annotation + +error[E04010]: cannot infer type + ┌─ tests/linter/incorrect_redundant_ref_deref.move:112:27 + │ +112 │ let _ref = &*&mut *&resource; // Mixed mutable and immutable redundant borrows + │ ^^^^^^^^^^ Could not infer this type. Try adding an annotation + +error[E03009]: unbound variable + ┌─ tests/linter/incorrect_redundant_ref_deref.move:112:29 + │ +112 │ let _ref = &*&mut *&resource; // Mixed mutable and immutable redundant borrows + │ ^^^^^^^^ Unbound variable 'resource' + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/incorrect_redundant_ref_deref.move:117:25 + │ +117 │ let _value = *&(*&resource.value + 1); // Redundant borrows in complex expression + │ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/incorrect_redundant_ref_deref.move:124:21 + │ +124 │ let _ref = &*(&resource.value); // Complex nested borrow on field, might be missed + │ ^^^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + diff --git a/external-crates/move/crates/move-compiler/tests/linter/incorrect_redundant_ref_deref.move b/external-crates/move/crates/move-compiler/tests/linter/incorrect_redundant_ref_deref.move new file mode 100644 index 0000000000000..fdbe0c3e3b799 --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/incorrect_redundant_ref_deref.move @@ -0,0 +1,140 @@ +module 0x42::M { + struct MyResource has copy, drop { + value: u64, + } + + // True Positive Cases + + public fun true_positive_1() { + let resource = MyResource { value: 10 }; + let _ref = &*(&resource); // Redundant borrow-dereference + } + + public fun true_positive_2() { + let resource = MyResource { value: 10 }; + let _ref = &mut *(&mut resource); // Redundant mutable borrow-dereference + } + + public fun true_positive_3() { + let resource = MyResource { value: 10 }; + let _value = *(&resource.value); // Redundant dereference of field borrow + } + + // True Negative Cases + + public fun true_negative_1() { + let resource = MyResource { value: 10 }; + let _ref = &resource; // Direct borrow, no redundancy + } + + public fun true_negative_2() { + let resource = MyResource { value: 10 }; + let ref = &resource; + let _value = ref.value; // Accessing field through reference, no redundancy + } + + public fun true_negative_3() { + let resource = MyResource { value: 10 }; + let _copy = resource; // Creating a copy, no borrow involved + } + + // False Positive Cases + + public fun false_positive_2() { + let resource = MyResource { value: 10 }; + let ref1 = &resource; + let _ref2 = &*ref1; // Might be intentional for creating a new reference + } + + public fun false_positive_3(resource: &mut MyResource) { + let _ref = &mut *resource; // Might be necessary in generic contexts + } + + // False Negative Cases + + public fun false_negative_1() { + let resource = MyResource { value: 10 }; + let _ref = &*(&*(&resource)); // Triple nested borrow-dereference, might be missed + } + + public fun false_negative_2() { + let resource = MyResource { value: 10 }; + let ref1 = &resource; + let _ref2 = &(*ref1); // Dereference then reference, might be missed + } + + public fun false_negative_3() { + let resource = MyResource { value: 10 }; + let _value = *((&resource).value); // Complex expression, might be missed + } + + // New cases for field borrows + + public fun field_borrow_redundant() { + let resource = MyResource { value: 10 }; + let _ref = &*&resource.value; // Redundant borrow-dereference on field + } + + public fun field_borrow_nested() { + let resource = MyResource { value: 10 }; + let _ref = &*&(&resource).value; // Nested redundant borrow-dereference on field + } + + // New cases for non-local borrows + + public fun non_local_borrow_literal() { + let _ref = &*&0; // Redundant borrow-dereference on literal + } + + public fun get_resource(): MyResource { + MyResource { value: 20 } + } + + public fun non_local_borrow_function_call() { + let _ref = &*&get_resource(); // Redundant borrow-dereference on function call result + } + + // Helper method for the above test cases + public fun do_something(self: &MyResource) { + // Dummy implementation + let _ = self.value; + } + + // Additional test cases to cover more scenarios + + public fun multiple_field_borrows() { + let resource = MyResource { value: 10 }; + let _ref = &*&(&*&resource.value); // Multiple redundant borrows on field + } + + public fun mixed_borrow_types() { + let mut resource = MyResource { value: 10 }; + let _ref = &*&mut *&resource; // Mixed mutable and immutable redundant borrows + } + + public fun complex_expression() { + let resource = MyResource { value: 10 }; + let _value = *&(*&resource.value + 1); // Redundant borrows in complex expression + } + + // False negative cases for the new scenarios + + public fun false_negative_complex_field_borrow() { + let resource = MyResource { value: 10 }; + let _ref = &*(&resource.value); // Complex nested borrow on field, might be missed + } + + // Suppress Cases + + #[allow(lint(redundant_ref_deref))] + public fun suppress_case_1() { + let resource = MyResource { value: 10 }; + let _ref = &*(&resource); // Suppressed warning + } + + #[allow(lint(redundant_ref_deref))] + public fun suppress_case_2() { + let resource = MyResource { value: 10 }; + let _value = *(&resource.value); // Suppressed warning + } +} diff --git a/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_complex.exp b/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_complex.exp new file mode 100644 index 0000000000000..d82ebd52f6441 --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_complex.exp @@ -0,0 +1,72 @@ +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_complex.move:10:17 + │ +10 │ let _ref = &*&0; // Redundant borrow-dereference on literal + │ ^^^ Redundant borrow-dereference detected. Remove this borrow-deref and use the expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_complex.move:18:17 + │ +18 │ let _ref = &*&get_resource(); // Redundant borrow-dereference on function call result + │ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Remove this borrow-deref and use the expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_complex.move:27:17 + │ +27 │ let _ref = &*(&*&resource.a); // Multiple redundant borrows on field + │ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the inner expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_complex.move:27:20 + │ +27 │ let _ref = &*(&*&resource.a); // Multiple redundant borrows on field + │ ^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_complex.move:32:18 + │ +32 │ let _ref = &(copy resource.a); // Multiple redundant borrows on field + │ ^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_complex.move:37:17 + │ +37 │ let _ref = &*&mut *&resource; // Mixed mutable and immutable redundant borrows + │ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the inner expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_complex.move:37:23 + │ +37 │ let _ref = &*&mut *&resource; // Mixed mutable and immutable redundant borrows + │ ^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_complex.move:47:17 + │ +47 │ let _a = *&(*&resource.a + 1); // Redundant borrows in complex expression + │ ^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_complex.move:52:15 + │ +52 │ let _a = (copy resource.a) + 1; // Redundant borrows in complex expression + │ ^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + diff --git a/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_complex.move b/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_complex.move new file mode 100644 index 0000000000000..de1b87a17f134 --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_complex.move @@ -0,0 +1,53 @@ +module 0x42::ComplexCases; + +public struct S has copy, drop { + a: u64, +} + +// Non-Local but Simple Borrow Cases + +public fun literal_case() { + let _ref = &*&0; // Redundant borrow-dereference on literal +} + +public fun literal_case_valid() { let _ref = &0; } + +public fun get_resource(): S { S { a: 20 } } + +public fun function_call_case() { + let _ref = &*&get_resource(); // Redundant borrow-dereference on function call result +} + +public fun function_call_case_valid() { let _ref = &get_resource(); } + +//Complex cases + +public fun field_borrows() { + let resource = S { a: 10 }; + let _ref = &*(&*&resource.a); // Multiple redundant borrows on field +} + +public fun field_borrows_valid() { + let resource = S { a: 10 }; + let _ref = &(copy resource.a); // Multiple redundant borrows on field +} + +public fun mixed_borrow_types() { + let resource = S { a: 10 }; + let _ref = &*&mut *&resource; // Mixed mutable and immutable redundant borrows +} + +public fun mixed_borrow_types_valid() { + let resource = S { a: 10 }; + let _ref = &(copy resource); // Mixed mutable and immutable redundant borrows +} + +public fun complex_expression() { + let resource = S { a: 10 }; + let _a = *&(*&resource.a + 1); // Redundant borrows in complex expression +} + +public fun complex_expression_valid() { + let resource = S { a: 10 }; + let _a = (copy resource.a) + 1; // Redundant borrows in complex expression +} diff --git a/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_conditional.exp b/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_conditional.exp new file mode 100644 index 0000000000000..1be49356b88ea --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_conditional.exp @@ -0,0 +1,113 @@ +error[E01003]: invalid modifier + ┌─ tests/linter/move_2024/ref_deref_conditional.move:3:1 + │ +3 │ struct MyResource has copy, drop { + │ ^^^^^^ Invalid struct declaration. Internal struct declarations are not yet supported + │ + = Visibility annotations are required on struct declarations from the Move 2024 edition onwards. + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_conditional.move:10:17 + │ +10 │ let _ref = &*&resource; // Should be flagged + │ ^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_conditional.move:16:21 + │ +16 │ let _ref = &mut *&resource; // Should be flagged + │ ^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_conditional.move:22:17 + │ +22 │ let _ref = &*&resource.value; // Should be flagged + │ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +error[E04024]: invalid usage of immutable variable + ┌─ tests/linter/move_2024/ref_deref_conditional.move:30:5 + │ +28 │ let resource = MyResource { value: 10 }; + │ -------- To use the variable mutably, it must be declared 'mut', e.g. 'mut resource' +29 │ let ref1 = &resource; +30 │ resource.value = 20; // Modifying the resource + │ ^^^^^^^^^^^^^^ Invalid mutable borrow of immutable variable 'resource' + +error[E07002]: mutable ownership violated + ┌─ tests/linter/move_2024/ref_deref_conditional.move:30:5 + │ +29 │ let ref1 = &resource; + │ --------- It is still being borrowed by this reference +30 │ resource.value = 20; // Modifying the resource + │ ^^^^^^^^^^^^^^ Invalid mutable borrow at field 'value'. + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_conditional.move:37:17 + │ +37 │ let _ref = &*(&*&resource); // Should be flagged + │ ^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the inner expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_conditional.move:37:20 + │ +37 │ let _ref = &*(&*&resource); // Should be flagged + │ ^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_conditional.move:50:17 + │ +50 │ let _copy = *&resource; // Should be flagged, making a copy + │ ^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_conditional.move:59:17 + │ +59 │ let _ref = &*&get_resource(); // Should be flagged + │ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Remove this borrow-deref and use the expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_conditional.move:64:17 + │ +64 │ let _ref = &*&0; // Should be flagged + │ ^^^ Redundant borrow-dereference detected. Remove this borrow-deref and use the expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_conditional.move:72:21 + │ +72 │ let _ref = &*&resource; // Should be flagged regardless + │ ^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_conditional.move:83:17 + │ +83 │ let _ref = &*&E; // Should be flagged + │ ^^^ Redundant borrow-dereference detected. Remove this borrow-deref and use the expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_conditional.move:88:17 + │ +88 │ let _ref = &*&vector[1,2,3]; // Should be flagged + │ ^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Remove this borrow-deref and use the expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + diff --git a/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_conditional.move b/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_conditional.move new file mode 100644 index 0000000000000..652628b3ee3cf --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_conditional.move @@ -0,0 +1,89 @@ +module 0x42::ConstrainedRefDerefCases; + +struct MyResource has copy, drop { + value: u64, +} + +// Case 1: Should be flagged - simple &*& pattern +public fun should_flag_simple() { + let resource = MyResource { value: 10 }; + let _ref = &*&resource; // Should be flagged +} + +// Case 2: Should be flagged - &mut *& pattern +public fun should_flag_mut() { + let resource = MyResource { value: 10 }; + let _ref = &mut *&resource; // Should be flagged +} + +// Case 3: Should be flagged - &*& pattern with field access +public fun should_flag_field() { + let mut resource = MyResource { value: 10 }; + let _ref = &*&resource.value; // Should be flagged + resource.value = 10; +} + +// Case 3: Should not be flagged - &* pattern without extra & +public fun should_not_flag_modified() { + let resource = MyResource { value: 10 }; + let ref1 = &resource; + resource.value = 20; // Modifying the resource + let _ref2 = &*ref1; // No flag -- ref was made elsewhere +} + +// Case 5: Should be flagged - nested &*& pattern +public fun should_flag_nested() { + let resource = MyResource { value: 10 }; + let _ref = &*(&*&resource); // Should be flagged +} + +// Case 6: Should not be flagged - &* pattern without extra & +public fun should_not_flag_deref_only() { + let resource = MyResource { value: 10 }; + let ref1 = &resource; + let _ref2 = &*ref1; // Should not be flagged +} + +// Case 7: Should be flagged - *& pattern without leading & +public fun should_flag_copy() { + let resource = MyResource { value: 10 }; + let _copy = *&resource; // Should be flagged, making a copy +} + +// Case 8: Should be flagged for removal - &*& pattern with function call +public fun get_resource(): MyResource { + MyResource { value: 20 } +} + +public fun should_flag_function_call() { + let _ref = &*&get_resource(); // Should be flagged +} + +// Case 9: Should be flagged for removal - &*& pattern with constant value +public fun should_flag_value() { + let _ref = &*&0; // Should be flagged +} + +// Case 10: Should be flagged - &*& pattern but path is mutated in a loop +public fun should_flag_loop_mutation() { + let mut resource = MyResource { value: 10 }; + let mut i = 0; + while (i < 5) { + let _ref = &*&resource; // Should be flagged regardless + resource.value = resource.value + 1; + i = i + 1; + } +} + +const E: u64 = 0; + +// Case 11: Should be flagged -- constant +#[allow(implicit_const_copy)] +public fun should_flag_constant() { + let _ref = &*&E; // Should be flagged +} + +// Case 12: Should be flagged -- vector +public fun should_flag_vector() { + let _ref = &*&vector[1,2,3]; // Should be flagged +} diff --git a/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_conditional_valid.move b/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_conditional_valid.move new file mode 100644 index 0000000000000..3392b4336cbc5 --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_conditional_valid.move @@ -0,0 +1,85 @@ +module 0x42::ConstrainedRefDerefCases; + +public struct MyResource has copy, drop { + value: u64, +} + +// Case 1 +public fun should_not_flag_simple() { + let resource = MyResource { value: 10 }; + let _ref = & resource; +} + +// Case 2 +public fun should_not_flag_mut() { + let resource = MyResource { value: 10 }; + let _ref = &mut copy resource; +} + +// Case 3 +public fun should_not_flag_field() { + let mut resource = MyResource { value: 10 }; + let _ref = &resource.value; + resource.value = 10; +} + +// Case 4 -- invalid control flow +public fun should_not_flag_modified() { + let mut resource = MyResource { value: 10 }; + let ref1 = © resource; + resource.value = 20; + let _ref2 = &*ref1; // No flag -- ref was made elsewhere +} + +// Case 5 +public fun should_not_flag_nested() { + let resource = MyResource { value: 10 }; + let _ref = © resource; +} + +// Case 6 +public fun should_not_flag_deref_only() { + let resource = MyResource { value: 10 }; + let ref1 = &resource; + let _ref2 = &*ref1; // Should not be flagged +} + +// Case 7 +public fun should_not_flag_copy() { + let resource = MyResource { value: 10 }; + let _copy = copy resource; +} + +// Case 8 +public fun get_resource(): MyResource { + MyResource { value: 20 } +} + +// Case 9 +public fun should_not_flag_value() { + let _ref = &0; +} + +// Case 10 +public fun should_not_flag_loop_mutation() { + let mut resource = MyResource { value: 10 }; + let mut i = 0; + while (i < 5) { + let _ref = © resource; // Should be flagged regardless + resource.value = resource.value + 1; + i = i + 1; + } +} + +const E: u64 = 0; + +// Case 11 +#[allow(implicit_const_copy)] +public fun should_flag_constant() { + let _ref = &E; // Should be flagged +} + +// Case 12 +public fun should_not_flag_vector() { + let _ref = &vector[1,2,3]; // Should be flagged +} diff --git a/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_fields.exp b/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_fields.exp new file mode 100644 index 0000000000000..a2eb4c408d1fb --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_fields.exp @@ -0,0 +1,80 @@ +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_fields.move:26:21 + │ +26 │ let _value: T = *&((&s).value); // Complex field expression + │ ^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the inner expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_fields.move:36:17 + │ +36 │ let _ref = &*(&*(&resource)); // Triple nested borrow-dereference, might be missed + │ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the inner expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_fields.move:36:20 + │ +36 │ let _ref = &*(&*(&resource)); // Triple nested borrow-dereference, might be missed + │ ^^^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_fields.move:44:21 + │ +44 │ let _value: T = *&(copy s.value); // Complex field expression + │ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the inner expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_fields.move:44:24 + │ +44 │ let _value: T = *&(copy s.value); // Complex field expression + │ ^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_fields.move:49:22 + │ +49 │ let _value: T = copy s.value; // Complex field expression -- bad copy + │ ^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_fields.move:53:22 + │ +53 │ let _value: T = copy s.value; // Complex field expression -- bad copy + │ ^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_fields.move:58:17 + │ +58 │ let _ref = &*(&resource.value); // Complex nested borrow on field + │ ^^^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_fields.move:65:17 + │ +65 │ let _ref = &*&resource.value; // Direct, redundant borrow-dereference on field + │ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/move_2024/ref_deref_fields.move:70:17 + │ +70 │ let _ref = &*&(&resource).value; // Nested redundant borrow-dereference on field + │ ^^^^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + diff --git a/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_fields.move b/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_fields.move new file mode 100644 index 0000000000000..6381962bb954c --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_fields.move @@ -0,0 +1,71 @@ +module 0x42::complex_ref_deref; + +public struct MyResource has copy, drop { + value: u64, +} + +// Ignored Cases + +public fun case_1() { + let resource = MyResource { value: 10 }; + let ref1 = &resource; + let _ref2 = &*ref1; // Might be intentional for creating a new reference +} + +public fun case_2(resource: &mut MyResource) { + let _ref = &mut *resource; // Might be necessary in generic contexts +} + +public fun case_3() { + let resource = MyResource { value: 10 }; + let ref1 = &resource; + let _ref2 = &(*ref1); // Dereference then reference -- c'est la vie +} + +public fun case_5(s: S) { + let _value: T = *&((&s).value); // Complex field expression + // Could be removed in favor of the implicit copy, but path + // processing makes it unclear what to do after typing rewrites + // it. See note in lint analysis. +} + +// Flagged Cases + +public fun case_4() { + let resource = MyResource { value: 10 }; + let _ref = &*(&*(&resource)); // Triple nested borrow-dereference, might be missed +} + +public struct S has drop { + value: T, +} + +public fun case_5_b(s: S) { + let _value: T = *&(copy s.value); // Complex field expression + // Could be removed in favor of the implicit copy +} + +public fun case_5_c(s: &S) { + let _value: T = copy s.value; // Complex field expression -- bad copy +} + +public fun case_5_d(s: &mut S) { + let _value: T = copy s.value; // Complex field expression -- bad copy +} + +public fun case_6() { + let resource = MyResource { value: 10 }; + let _ref = &*(&resource.value); // Complex nested borrow on field +} + +//Field Borrow Cases + +public fun redundant_case() { + let resource = MyResource { value: 10 }; + let _ref = &*&resource.value; // Direct, redundant borrow-dereference on field +} + +public fun nested_case() { + let resource = MyResource { value: 10 }; + let _ref = &*&(&resource).value; // Nested redundant borrow-dereference on field +} diff --git a/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_fields_valid.move b/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_fields_valid.move new file mode 100644 index 0000000000000..fc963e8bd1842 --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/move_2024/ref_deref_fields_valid.move @@ -0,0 +1,49 @@ +module 0x42::complex_ref_deref_valid; + +public struct MyResource has copy, drop { + value: u64, +} + +// Flagged Cases + +public fun case_4() { + let resource = MyResource { value: 10 }; + let _ref = & copy resource; +} + +public struct S has drop { + value: T, +} + +public fun case_5(s: S) { + let _value: T = s.value; // Complex field expression +} + +public fun case_5_b(s: S) { + let _value: T = s.value; // Complex field expression +} + +public fun case_5_c(s: &S) { + let _value: T = s.value; // Complex field expression -- bad copy +} + +public fun case_5_d(s: &mut S) { + let _value: T = s.value; // Complex field expression -- bad copy +} + +public fun case_6() { + let resource = MyResource { value: 10 }; + let _ref = &resource.value; // Complex nested borrow on field +} + +//Field Borrow Cases + +public fun redundant_case() { + let resource = MyResource { value: 10 }; + let _ref = &resource.value; // Direct, redundant borrow-dereference on field +} + +public fun nested_case() { + let resource = MyResource { value: 10 }; + let _ref = &(&resource).value; // Nested redundant borrow-dereference on field +} diff --git a/external-crates/move/crates/move-compiler/tests/linter/redundant_ref_deref.exp b/external-crates/move/crates/move-compiler/tests/linter/redundant_ref_deref.exp new file mode 100644 index 0000000000000..63f19d5427feb --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/redundant_ref_deref.exp @@ -0,0 +1,24 @@ +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/redundant_ref_deref.move:8:21 + │ +8 │ let _ref = &*(&resource); // Redundant borrow-dereference + │ ^^^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/redundant_ref_deref.move:13:25 + │ +13 │ let _ref = &mut *(&mut resource); + │ ^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/redundant_ref_deref.move:18:22 + │ +18 │ let _value = *(&resource.value); + │ ^^^^^^^^^^^^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + diff --git a/external-crates/move/crates/move-compiler/tests/linter/redundant_ref_deref.move b/external-crates/move/crates/move-compiler/tests/linter/redundant_ref_deref.move new file mode 100644 index 0000000000000..bf0a3a61ab418 --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/redundant_ref_deref.move @@ -0,0 +1,20 @@ +module 0x42::TruePositiveCases { + struct MyResource has copy, drop { + value: u64, + } + + public fun case_1() { + let resource = MyResource { value: 10 }; + let _ref = &*(&resource); // Redundant borrow-dereference + } + + public fun case_2() { + let resource = MyResource { value: 10 }; + let _ref = &mut *(&mut resource); + } + + public fun case_3() { + let resource = MyResource { value: 10 }; + let _value = *(&resource.value); + } +} diff --git a/external-crates/move/crates/move-compiler/tests/linter/ref_deref_negative.move b/external-crates/move/crates/move-compiler/tests/linter/ref_deref_negative.move new file mode 100644 index 0000000000000..16394f72fefca --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/ref_deref_negative.move @@ -0,0 +1,21 @@ +module 0x42::TrueNegativeCases { + struct MyResource has copy, drop { + value: u64, + } + + public fun case_1() { + let resource = MyResource { value: 10 }; + let _ref = &resource; // Direct borrow, no redundancy + } + + public fun case_2() { + let resource = MyResource { value: 10 }; + let ref = &resource; + let _value = ref.value; // Accessing field through reference, no redundancy + } + + public fun case_3() { + let resource = MyResource { value: 10 }; + let _copy = resource; // Creating a copy, no borrow involved + } +} diff --git a/external-crates/move/crates/move-compiler/tests/linter/ref_deref_path.exp b/external-crates/move/crates/move-compiler/tests/linter/ref_deref_path.exp new file mode 100644 index 0000000000000..070a9b2001d61 --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/ref_deref_path.exp @@ -0,0 +1,16 @@ +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/ref_deref_path.move:5:19 + │ +5 │ let _x = &*&*&s.x.x; + │ ^^^^^^^^^ Redundant borrow-dereference detected. Use the inner expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/ref_deref_path.move:5:21 + │ +5 │ let _x = &*&*&s.x.x; + │ ^^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + diff --git a/external-crates/move/crates/move-compiler/tests/linter/ref_deref_path.move b/external-crates/move/crates/move-compiler/tests/linter/ref_deref_path.move new file mode 100644 index 0000000000000..36287fdd195ec --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/ref_deref_path.move @@ -0,0 +1,7 @@ +module 0x42::m { + struct S has copy, drop { x: T } + + public fun test(s: S>) { + let _x = &*&*&s.x.x; + } +} diff --git a/external-crates/move/crates/move-compiler/tests/linter/ref_deref_triple.exp b/external-crates/move/crates/move-compiler/tests/linter/ref_deref_triple.exp new file mode 100644 index 0000000000000..29908b2782170 --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/ref_deref_triple.exp @@ -0,0 +1,16 @@ +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/ref_deref_triple.move:5:19 + │ +5 │ let _x = &*&*&s; + │ ^^^^^ Redundant borrow-dereference detected. Use the inner expression directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/ref_deref_triple.move:5:21 + │ +5 │ let _x = &*&*&s; + │ ^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + diff --git a/external-crates/move/crates/move-compiler/tests/linter/ref_deref_triple.move b/external-crates/move/crates/move-compiler/tests/linter/ref_deref_triple.move new file mode 100644 index 0000000000000..bb46ed086e04e --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/ref_deref_triple.move @@ -0,0 +1,7 @@ +module 0x42::m { + struct S has copy, drop {} + + public fun test(s: S) { + let _x = &*&*&s; + } +} diff --git a/external-crates/move/crates/move-compiler/tests/linter/suppress_self_assignment.move b/external-crates/move/crates/move-compiler/tests/linter/suppress_self_assignment.move new file mode 100644 index 0000000000000..494d5ca7dac40 --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/suppress_self_assignment.move @@ -0,0 +1,15 @@ +#[allow(lint(self_assignment))] +module a::m { + fun foo(x: u64): u64 { + x = x; + x + } +} + +module a::m2 { + #[allow(lint(self_assignment))] + fun foo(x: u64): u64 { + x = x; + x + } +} diff --git a/external-crates/move/crates/move-compiler/tests/linter/suppressed_case_redundant_ref_deref.move b/external-crates/move/crates/move-compiler/tests/linter/suppressed_case_redundant_ref_deref.move new file mode 100644 index 0000000000000..8ec1a8e5f1d45 --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/suppressed_case_redundant_ref_deref.move @@ -0,0 +1,17 @@ +module 0x42::SuppressCases { + struct MyResource has copy, drop { + value: u64, + } + + #[allow(lint(redundant_ref_deref))] + public fun case_1() { + let resource = MyResource { value: 10 }; + let _ref = &*(&resource); // Suppressed warning + } + + #[allow(lint(redundant_ref_deref))] + public fun case_2() { + let resource = MyResource { value: 10 }; + let _value = *(&resource.value); // Suppressed warning + } +} diff --git a/external-crates/move/crates/move-compiler/tests/linter/true_negative_self_assignment.move b/external-crates/move/crates/move-compiler/tests/linter/true_negative_self_assignment.move new file mode 100644 index 0000000000000..da67aefdcae66 --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/true_negative_self_assignment.move @@ -0,0 +1,63 @@ +// tests for cases that self-assignment should not warn + +module a::m { + const C: u64 = 112; + + fun t() { + let x1 = 5; + x1; + x1 = 5; // we don't track values + x1; + + let c1 = C; + c1; + c1 = C; // we don't track values + c1; + + let x2 = 5; + x2; + let x2 = x2; // shadowing is not self-assignment + x2; + + let (x3, x4) = (5, 5); + x3; + x4; + (x4, x3) = (x3, x4); // swap is not self-assignment + x3; + x4; + + let r1 = &mut 0; + let r2 = &mut 0; + *r1; + *r2; + *r1 = *r2; // different references + *r1; + + let r = &mut 0; + *id(r) = *id(r); + + let x5 = 0; + x5; + x5 = { let x5 = 0; x5 }; // different x's + x5; + } + + + struct S has copy, drop { f1: u64, f2: u64 } + struct P has copy, drop { s1: S, s2: S } + fun fields(m1: &mut S, m2: &mut S, s1: S, s2: S) { + s1.f1 = s1.f2; // different fields + m1.f1 = m1.f2; // different fields + s1.f1 = s2.f1; // different locals + m1.f1 = m2.f1; // different references + } + + fun nested_fields(p1: &mut P, p2: &mut P) { + p1.s1.f1 = p1.s1.f2; // different fields + p1.s1.f1 = p2.s1.f1; // different references + } + + fun id(x: &mut T): &mut T { + x + } +} diff --git a/external-crates/move/crates/move-compiler/tests/linter/true_positive_self_assignment.exp b/external-crates/move/crates/move-compiler/tests/linter/true_positive_self_assignment.exp new file mode 100644 index 0000000000000..464ce10c41225 --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/true_positive_self_assignment.exp @@ -0,0 +1,318 @@ +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:5:9 + │ +5 │ p = p; // warn + │ ^ - Is the same as this location + │ │ + │ Unnecessary self-assignment. The assignment is redundant and will not change the value + │ This location + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:10:9 + │ +10 │ x = x; // warn + │ ^ - Is the same as this location + │ │ + │ Unnecessary self-assignment. The assignment is redundant and will not change the value + │ This location + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:13:9 + │ +13 │ x = move x; // warn + │ ^ - Is the same as this location + │ │ + │ Unnecessary self-assignment. The assignment is redundant and will not change the value + │ This location + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:15:9 + │ +15 │ x = copy x; // warn + │ ^ - Is the same as this location + │ │ + │ Unnecessary self-assignment. The assignment is redundant and will not change the value + │ This location + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:18:10 + │ +18 │ (p, other, x) = (p, 0, x); // warn x2 + │ ^ - Is the same as this location + │ │ + │ Unnecessary self-assignment. The assignment is redundant and will not change the value + │ This location + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:18:20 + │ +18 │ (p, other, x) = (p, 0, x); // warn x2 + │ ^ - Is the same as this location + │ │ + │ Unnecessary self-assignment. The assignment is redundant and will not change the value + │ This location + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:27:9 + │ +27 │ *&mut m.f1 = m.f1; + │ ^^^^^^^^^^^^^^^^^ + │ ││ │ + │ ││ Is the same as this location + │ │This location + │ Unnecessary self-mutation. The mutation is redundant and will not change the value + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:28:9 + │ +28 │ m.f1 = *&mut m.f1; + │ ^^^^^^^^^^^^^^^^^^ + │ │ │ + │ │ Is the same as this location + │ Unnecessary self-mutation. The mutation is redundant and will not change the value + │ This location + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/true_positive_self_assignment.move:28:17 + │ +28 │ m.f1 = *&mut m.f1; + │ ^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:29:9 + │ +29 │ m.f1 = *&m.f1; + │ ^^^^^^^^^^^^^^ + │ │ │ + │ │ Is the same as this location + │ Unnecessary self-mutation. The mutation is redundant and will not change the value + │ This location + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/true_positive_self_assignment.move:29:17 + │ +29 │ m.f1 = *&m.f1; + │ ^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:30:9 + │ +30 │ *&mut m.f1 = *&m.f1; + │ ^^^^^^^^^^^^^^^^^^^^ + │ ││ │ + │ ││ Is the same as this location + │ │This location + │ Unnecessary self-mutation. The mutation is redundant and will not change the value + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/true_positive_self_assignment.move:30:23 + │ +30 │ *&mut m.f1 = *&m.f1; + │ ^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:31:9 + │ +31 │ *&mut m.f1 = *&mut m.f1; + │ ^^^^^^^^^^^^^^^^^^^^^^^^ + │ ││ │ + │ ││ Is the same as this location + │ │This location + │ Unnecessary self-mutation. The mutation is redundant and will not change the value + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/true_positive_self_assignment.move:31:23 + │ +31 │ *&mut m.f1 = *&mut m.f1; + │ ^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:33:9 + │ +33 │ *&mut s.f1 = s.f1; + │ ^^^^^^^^^^^^^^^^^ + │ ││ │ + │ ││ Is the same as this location + │ │This location + │ Unnecessary self-mutation. The mutation is redundant and will not change the value + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:34:9 + │ +34 │ s.f1 = *&mut s.f1; + │ ^^^^^^^^^^^^^^^^^^ + │ │ │ + │ │ Is the same as this location + │ Unnecessary self-mutation. The mutation is redundant and will not change the value + │ This location + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/true_positive_self_assignment.move:34:17 + │ +34 │ s.f1 = *&mut s.f1; + │ ^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:35:9 + │ +35 │ s.f1 = *&s.f1; + │ ^^^^^^^^^^^^^^ + │ │ │ + │ │ Is the same as this location + │ Unnecessary self-mutation. The mutation is redundant and will not change the value + │ This location + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/true_positive_self_assignment.move:35:17 + │ +35 │ s.f1 = *&s.f1; + │ ^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:36:9 + │ +36 │ *&mut s.f1 = *&s.f1; + │ ^^^^^^^^^^^^^^^^^^^^ + │ ││ │ + │ ││ Is the same as this location + │ │This location + │ Unnecessary self-mutation. The mutation is redundant and will not change the value + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/true_positive_self_assignment.move:36:23 + │ +36 │ *&mut s.f1 = *&s.f1; + │ ^^^^^^ Redundant borrow-dereference detected. Use the field access directly. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:37:9 + │ +37 │ *&mut s.f1 = *&mut s.f1; + │ ^^^^^^^^^^^^^^^^^^^^^^^^ + │ ││ │ + │ ││ Is the same as this location + │ │This location + │ Unnecessary self-mutation. The mutation is redundant and will not change the value + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/true_positive_self_assignment.move:37:23 + │ +37 │ *&mut s.f1 = *&mut s.f1; + │ ^^^^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:43:9 + │ +43 │ p.s1.f1 = p.s1.f1; + │ ^^^^^^^^^^^^^^^^^ + │ │ │ + │ │ Is the same as this location + │ Unnecessary self-mutation. The mutation is redundant and will not change the value + │ This location + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:47:9 + │ +47 │ *r = *r; + │ ^^^^^^^ + │ ││ │ + │ ││ Is the same as this location + │ │This location + │ Unnecessary self-mutation. The mutation is redundant and will not change the value + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:49:9 + │ +49 │ *copy r = *r; + │ ^^^^^^^^^^^^ + │ │ │ │ + │ │ │ Is the same as this location + │ │ This location + │ Unnecessary self-mutation. The mutation is redundant and will not change the value + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:50:9 + │ +50 │ *move r = *copy r; + │ ^^^^^^^^^^^^^^^^^ + │ │ │ │ + │ │ │ Is the same as this location + │ │ This location + │ Unnecessary self-mutation. The mutation is redundant and will not change the value + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W02008]: assignment preserves the same value + ┌─ tests/linter/true_positive_self_assignment.move:53:9 + │ +53 │ *&mut x = *&mut x; + │ ^^^^^^^^^^^^^^^^^ + │ │ │ │ + │ │ │ Is the same as this location + │ │ This location + │ Unnecessary self-mutation. The mutation is redundant and will not change the value + │ + = This warning can be suppressed with '#[allow(lint(self_assignment))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + +warning[Lint W01009]: redundant reference/dereference + ┌─ tests/linter/true_positive_self_assignment.move:53:19 + │ +53 │ *&mut x = *&mut x; + │ ^^^^^^^ Redundant borrow-dereference detected. Replace this borrow-deref with 'copy'. + │ + = This warning can be suppressed with '#[allow(lint(redundant_ref_deref))]' applied to the 'module' or module member ('const', 'fun', or 'struct') + diff --git a/external-crates/move/crates/move-compiler/tests/linter/true_positive_self_assignment.move b/external-crates/move/crates/move-compiler/tests/linter/true_positive_self_assignment.move new file mode 100644 index 0000000000000..efddd0a5da103 --- /dev/null +++ b/external-crates/move/crates/move-compiler/tests/linter/true_positive_self_assignment.move @@ -0,0 +1,55 @@ +// tests for cases that self-assignment should warn + +module a::m { + fun variables(p: u64) { + p = p; // warn + p; + + let x = 0; + x; + x = x; // warn + x; + + x = move x; // warn + x; + x = copy x; // warn + + let other; + (p, other, x) = (p, 0, x); // warn x2 + p; + x; + other; + } + + struct S has copy, drop { f1: u64, f2: u64 } + + fun fields(m: &mut S, s: S) { + *&mut m.f1 = m.f1; + m.f1 = *&mut m.f1; + m.f1 = *&m.f1; + *&mut m.f1 = *&m.f1; + *&mut m.f1 = *&mut m.f1; + + *&mut s.f1 = s.f1; + s.f1 = *&mut s.f1; + s.f1 = *&s.f1; + *&mut s.f1 = *&s.f1; + *&mut s.f1 = *&mut s.f1; + } + + struct P has copy, drop { s1: S, s2: S } + + fun nested_fields(p: &mut P) { + p.s1.f1 = p.s1.f1; + } + + fun references(r: &mut u64) { + *r = *r; + *r; + *copy r = *r; + *move r = *copy r; + + let x = 0; + *&mut x = *&mut x; + } +} diff --git a/external-crates/move/crates/move-core-types/src/annotated_value.rs b/external-crates/move/crates/move-core-types/src/annotated_value.rs index b3624fc398741..f5a31cc571941 100644 --- a/external-crates/move/crates/move-core-types/src/annotated_value.rs +++ b/external-crates/move/crates/move-core-types/src/annotated_value.rs @@ -4,7 +4,7 @@ use crate::{ account_address::AccountAddress, - annotated_visitor::{visit_struct, visit_value, Error as VError, Visitor}, + annotated_visitor::{visit_struct, visit_value, Error as VError, ValueDriver, Visitor}, identifier::Identifier, language_storage::{StructTag, TypeTag}, runtime_value::{self as R, MOVE_STRUCT_FIELDS, MOVE_STRUCT_TYPE}, @@ -19,6 +19,7 @@ use serde::{ use std::{ collections::BTreeMap, fmt::{self, Debug}, + io::Cursor, }; /// In the `WithTypes` configuration, a Move struct gets serialized into a Serde struct with this name @@ -71,7 +72,7 @@ pub enum MoveValue { Variant(MoveVariant), } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct MoveFieldLayout { pub name: Identifier, pub layout: MoveTypeLayout, @@ -83,14 +84,14 @@ impl MoveFieldLayout { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct MoveStructLayout { /// An decorated representation with both types and human-readable field names pub type_: StructTag, pub fields: Box>, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub struct MoveEnumLayout { pub type_: StructTag, pub variants: BTreeMap<(Identifier, u16), Vec>, @@ -111,7 +112,7 @@ impl MoveDatatypeLayout { } } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)] pub enum MoveTypeLayout { #[serde(rename(serialize = "bool", deserialize = "bool"))] Bool, @@ -166,19 +167,21 @@ impl MoveValue { /// /// Deserialization can fail because of an issue in the serialized format (data doesn't match /// layout, unexpected bytes or trailing bytes), or a custom error expressed by the visitor. - pub fn visit_deserialize( - mut blob: &[u8], - ty: &MoveTypeLayout, + pub fn visit_deserialize<'b, 'l, V: Visitor<'b, 'l>>( + blob: &'b [u8], + ty: &'l MoveTypeLayout, visitor: &mut V, ) -> AResult where V::Error: std::error::Error + Send + Sync + 'static, { - let res = visit_value(&mut blob, ty, visitor)?; - if blob.is_empty() { + let mut bytes = Cursor::new(blob); + let res = visit_value(&mut bytes, ty, visitor)?; + if bytes.position() as usize == blob.len() { Ok(res) } else { - Err(VError::TrailingBytes(blob.len()).into()) + let remaining = blob.len() - bytes.position() as usize; + Err(VError::TrailingBytes(remaining).into()) } } @@ -231,19 +234,22 @@ impl MoveStruct { /// Like `MoveValue::visit_deserialize` (see for details), but specialized to visiting a struct /// (the `blob` is known to be a serialized Move struct, and the layout is a /// `MoveStructLayout`). - pub fn visit_deserialize( - mut blob: &[u8], - ty: &MoveStructLayout, + pub fn visit_deserialize<'b, 'l, V: Visitor<'b, 'l>>( + blob: &'b [u8], + ty: &'l MoveStructLayout, visitor: &mut V, ) -> AResult where V::Error: std::error::Error + Send + Sync + 'static, { - let res = visit_struct(&mut blob, ty, visitor)?; - if blob.is_empty() { + let mut bytes = Cursor::new(blob); + let driver = ValueDriver::new(&mut bytes, None); + let res = visit_struct(driver, ty, visitor)?; + if bytes.position() as usize == blob.len() { Ok(res) } else { - Err(VError::TrailingBytes(blob.len()).into()) + let remaining = blob.len() - bytes.position() as usize; + Err(VError::TrailingBytes(remaining).into()) } } diff --git a/external-crates/move/crates/move-core-types/src/annotated_visitor.rs b/external-crates/move/crates/move-core-types/src/annotated_visitor.rs index 4cb42b7e81444..9160d4565dc27 100644 --- a/external-crates/move/crates/move-core-types/src/annotated_visitor.rs +++ b/external-crates/move/crates/move-core-types/src/annotated_visitor.rs @@ -1,7 +1,7 @@ // Copyright (c) The Move Contributors // SPDX-License-Identifier: Apache-2.0 -use std::io::Read; +use std::io::{Cursor, Read}; use crate::{ account_address::AccountAddress, @@ -12,7 +12,7 @@ use crate::{ }; /// Visitors can be used for building values out of a serialized Move struct or value. -pub trait Visitor { +pub trait Visitor<'b, 'l> { type Value; /// Visitors can return any error as long as it can represent an error from the visitor itself. @@ -29,29 +29,73 @@ pub trait Visitor { /// ``` type Error: From; - fn visit_u8(&mut self, value: u8) -> Result; - fn visit_u16(&mut self, value: u16) -> Result; - fn visit_u32(&mut self, value: u32) -> Result; - fn visit_u64(&mut self, value: u64) -> Result; - fn visit_u128(&mut self, value: u128) -> Result; - fn visit_u256(&mut self, value: U256) -> Result; - fn visit_bool(&mut self, value: bool) -> Result; - fn visit_address(&mut self, value: AccountAddress) -> Result; - fn visit_signer(&mut self, value: AccountAddress) -> Result; + fn visit_u8( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u8, + ) -> Result; + + fn visit_u16( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u16, + ) -> Result; + + fn visit_u32( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u32, + ) -> Result; + + fn visit_u64( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u64, + ) -> Result; + + fn visit_u128( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u128, + ) -> Result; + + fn visit_u256( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: U256, + ) -> Result; + + fn visit_bool( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: bool, + ) -> Result; + + fn visit_address( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result; + + fn visit_signer( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result; fn visit_vector( &mut self, - driver: &mut VecDriver<'_, '_, '_>, + driver: &mut VecDriver<'_, 'b, 'l>, ) -> Result; fn visit_struct( &mut self, - driver: &mut StructDriver<'_, '_, '_>, + driver: &mut StructDriver<'_, 'b, 'l>, ) -> Result; fn visit_variant( &mut self, - driver: &mut VariantDriver<'_, '_, '_>, + driver: &mut VariantDriver<'_, 'b, 'l>, ) -> Result; } @@ -68,53 +112,89 @@ pub trait Visitor { /// Ok(()) /// } /// ``` -pub trait Traversal { +pub trait Traversal<'b, 'l> { type Error: From; - fn traverse_u8(&mut self, _value: u8) -> Result<(), Self::Error> { + fn traverse_u8( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u8, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_u16(&mut self, _value: u16) -> Result<(), Self::Error> { + fn traverse_u16( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u16, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_u32(&mut self, _value: u32) -> Result<(), Self::Error> { + fn traverse_u32( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u32, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_u64(&mut self, _value: u64) -> Result<(), Self::Error> { + fn traverse_u64( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u64, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_u128(&mut self, _value: u128) -> Result<(), Self::Error> { + fn traverse_u128( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u128, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_u256(&mut self, _value: U256) -> Result<(), Self::Error> { + fn traverse_u256( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: U256, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_bool(&mut self, _value: bool) -> Result<(), Self::Error> { + fn traverse_bool( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: bool, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_address(&mut self, _value: AccountAddress) -> Result<(), Self::Error> { + fn traverse_address( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: AccountAddress, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_signer(&mut self, _value: AccountAddress) -> Result<(), Self::Error> { + fn traverse_signer( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: AccountAddress, + ) -> Result<(), Self::Error> { Ok(()) } - fn traverse_vector(&mut self, driver: &mut VecDriver<'_, '_, '_>) -> Result<(), Self::Error> { + fn traverse_vector(&mut self, driver: &mut VecDriver<'_, 'b, 'l>) -> Result<(), Self::Error> { while driver.next_element(self)?.is_some() {} Ok(()) } fn traverse_struct( &mut self, - driver: &mut StructDriver<'_, '_, '_>, + driver: &mut StructDriver<'_, 'b, 'l>, ) -> Result<(), Self::Error> { while driver.next_field(self)?.is_some() {} Ok(()) @@ -122,7 +202,7 @@ pub trait Traversal { fn traverse_variant( &mut self, - driver: &mut VariantDriver<'_, '_, '_>, + driver: &mut VariantDriver<'_, 'b, 'l>, ) -> Result<(), Self::Error> { while driver.next_field(self)?.is_some() {} Ok(()) @@ -130,73 +210,118 @@ pub trait Traversal { } /// Default implementation converting any traversal into a visitor. -impl Visitor for T { +impl<'b, 'l, T: Traversal<'b, 'l> + ?Sized> Visitor<'b, 'l> for T { type Value = (); type Error = T::Error; - fn visit_u8(&mut self, value: u8) -> Result { - self.traverse_u8(value) + fn visit_u8( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u8, + ) -> Result { + self.traverse_u8(driver, value) } - fn visit_u16(&mut self, value: u16) -> Result { - self.traverse_u16(value) + fn visit_u16( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u16, + ) -> Result { + self.traverse_u16(driver, value) } - fn visit_u32(&mut self, value: u32) -> Result { - self.traverse_u32(value) + fn visit_u32( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u32, + ) -> Result { + self.traverse_u32(driver, value) } - fn visit_u64(&mut self, value: u64) -> Result { - self.traverse_u64(value) + fn visit_u64( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u64, + ) -> Result { + self.traverse_u64(driver, value) } - fn visit_u128(&mut self, value: u128) -> Result { - self.traverse_u128(value) + fn visit_u128( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u128, + ) -> Result { + self.traverse_u128(driver, value) } - fn visit_u256(&mut self, value: U256) -> Result { - self.traverse_u256(value) + fn visit_u256( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: U256, + ) -> Result { + self.traverse_u256(driver, value) } - fn visit_bool(&mut self, value: bool) -> Result { - self.traverse_bool(value) + fn visit_bool( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: bool, + ) -> Result { + self.traverse_bool(driver, value) } - fn visit_address(&mut self, value: AccountAddress) -> Result { - self.traverse_address(value) + fn visit_address( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result { + self.traverse_address(driver, value) } - fn visit_signer(&mut self, value: AccountAddress) -> Result { - self.traverse_signer(value) + fn visit_signer( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result { + self.traverse_signer(driver, value) } fn visit_vector( &mut self, - driver: &mut VecDriver<'_, '_, '_>, + driver: &mut VecDriver<'_, 'b, 'l>, ) -> Result { self.traverse_vector(driver) } fn visit_struct( &mut self, - driver: &mut StructDriver<'_, '_, '_>, + driver: &mut StructDriver<'_, 'b, 'l>, ) -> Result { self.traverse_struct(driver) } fn visit_variant( &mut self, - driver: &mut VariantDriver<'_, '_, '_>, + driver: &mut VariantDriver<'_, 'b, 'l>, ) -> Result { self.traverse_variant(driver) } } +/// Exposes information about the byte stream that the value being visited came from, namely the +/// bytes themselves, and the offset at which the value starts. Also exposes the layout of the +/// value being visited. +pub struct ValueDriver<'c, 'b, 'l> { + bytes: &'c mut Cursor<&'b [u8]>, + layout: Option<&'l MoveTypeLayout>, + start: usize, +} + /// Exposes information about a vector being visited (the element layout) to a visitor /// implementation, and allows that visitor to progress the traversal (by visiting or skipping /// elements). -pub struct VecDriver<'r, 'b, 'l> { - bytes: &'r mut &'b [u8], +pub struct VecDriver<'c, 'b, 'l> { + inner: ValueDriver<'c, 'b, 'l>, layout: &'l MoveTypeLayout, len: u64, off: u64, @@ -205,8 +330,8 @@ pub struct VecDriver<'r, 'b, 'l> { /// Exposes information about a struct being visited (its layout, details about the next field to be /// visited) to a visitor implementation, and allows that visitor to progress the traversal (by /// visiting or skipping fields). -pub struct StructDriver<'r, 'b, 'l> { - bytes: &'r mut &'b [u8], +pub struct StructDriver<'c, 'b, 'l> { + inner: ValueDriver<'c, 'b, 'l>, layout: &'l MoveStructLayout, off: usize, } @@ -214,8 +339,8 @@ pub struct StructDriver<'r, 'b, 'l> { /// Exposes information about a variant being visited (its layout, details about the next field to /// be visited, the variant's tag, and name) to a visitor implementation, and allows that visitor /// to progress the traversal (by visiting or skipping fields). -pub struct VariantDriver<'r, 'b, 'l> { - bytes: &'r mut &'b [u8], +pub struct VariantDriver<'c, 'b, 'l> { + inner: ValueDriver<'c, 'b, 'l>, layout: &'l MoveEnumLayout, tag: u16, variant_name: &'l IdentStr, @@ -236,6 +361,9 @@ pub enum Error { #[error("invalid variant tag: {0}")] UnexpectedVariantTag(usize), + + #[error("no layout available for value")] + NoValueLayout, } /// The null traversal implements `Traversal` and `Visitor` but without doing anything (does not @@ -243,21 +371,91 @@ pub enum Error { /// value structure. pub struct NullTraversal; -impl Traversal for NullTraversal { +impl<'b, 'l> Traversal<'b, 'l> for NullTraversal { type Error = Error; } -#[allow(clippy::len_without_is_empty)] -impl<'r, 'b, 'l> VecDriver<'r, 'b, 'l> { - fn new(bytes: &'r mut &'b [u8], layout: &'l MoveTypeLayout, len: u64) -> Self { +impl<'c, 'b, 'l> ValueDriver<'c, 'b, 'l> { + pub(crate) fn new(bytes: &'c mut Cursor<&'b [u8]>, layout: Option<&'l MoveTypeLayout>) -> Self { + let start = bytes.position() as usize; Self { bytes, layout, + start, + } + } + + /// The offset at which the value being visited starts in the byte stream. + pub fn start(&self) -> usize { + self.start + } + + /// The current position in the byte stream. + pub fn position(&self) -> usize { + self.bytes.position() as usize + } + + /// All the bytes in the byte stream (including the ones that have been read). + pub fn bytes(&self) -> &'b [u8] { + self.bytes.get_ref() + } + /// + /// The bytes that haven't been consumed by the visitor yet. + pub fn remaining_bytes(&self) -> &'b [u8] { + &self.bytes.get_ref()[self.position()..] + } + + /// Type layout for the value being visited. May produce an error if a layout was not supplied + /// when the driver was created (which should only happen if the driver was created for + /// visiting a struct specifically). + pub fn layout(&self) -> Result<&'l MoveTypeLayout, Error> { + self.layout.ok_or(Error::NoValueLayout) + } + + fn read_exact(&mut self) -> Result<[u8; N], Error> { + let mut buf = [0u8; N]; + self.bytes + .read_exact(&mut buf) + .map_err(|_| Error::UnexpectedEof)?; + Ok(buf) + } + + fn read_leb128(&mut self) -> Result { + leb128::read::unsigned(self.bytes).map_err(|_| Error::UnexpectedEof) + } +} + +#[allow(clippy::len_without_is_empty)] +impl<'c, 'b, 'l> VecDriver<'c, 'b, 'l> { + fn new(inner: ValueDriver<'c, 'b, 'l>, layout: &'l MoveTypeLayout, len: u64) -> Self { + Self { + inner, + layout, len, off: 0, } } + /// The offset at which the value being visited starts in the byte stream. + pub fn start(&self) -> usize { + self.inner.start() + } + + /// The current position in the byte stream. + pub fn position(&self) -> usize { + self.inner.position() + } + + /// All the bytes in the byte stream (including the ones that have been read). + pub fn bytes(&self) -> &'b [u8] { + self.inner.bytes() + } + + /// The bytes that haven't been consumed by the visitor yet. + pub fn remaining_bytes(&self) -> &'b [u8] { + self.inner.remaining_bytes() + } + /// Type layout for the vector's inner type. pub fn element_layout(&self) -> &'l MoveTypeLayout { self.layout @@ -280,14 +478,14 @@ impl<'r, 'b, 'l> VecDriver<'r, 'b, 'l> { /// Returns `Ok(None)` if there are no more elements in the vector, `Ok(v)` if there was an /// element and it was successfully visited (where `v` is the value returned by the visitor) or /// an error if there was an underlying deserialization error, or an error during visitation. - pub fn next_element( + pub fn next_element + ?Sized>( &mut self, visitor: &mut V, ) -> Result, V::Error> { Ok(if self.off >= self.len { None } else { - let res = visit_value(self.bytes, self.layout, visitor)?; + let res = visit_value(self.inner.bytes, self.layout, visitor)?; self.off += 1; Some(res) }) @@ -300,15 +498,35 @@ impl<'r, 'b, 'l> VecDriver<'r, 'b, 'l> { } } -impl<'r, 'b, 'l> StructDriver<'r, 'b, 'l> { - fn new(bytes: &'r mut &'b [u8], layout: &'l MoveStructLayout) -> Self { +impl<'c, 'b, 'l> StructDriver<'c, 'b, 'l> { + fn new(inner: ValueDriver<'c, 'b, 'l>, layout: &'l MoveStructLayout) -> Self { Self { - bytes, + inner, layout, off: 0, } } + /// The offset at which the value being visited starts in the byte stream. + pub fn start(&self) -> usize { + self.inner.start() + } + + /// The current position in the byte stream. + pub fn position(&self) -> usize { + self.inner.position() + } + + /// All the bytes in the byte stream (including the ones that have been read). + pub fn bytes(&self) -> &'b [u8] { + self.inner.bytes() + } + + /// The bytes that haven't been consumed by the visitor yet. + pub fn remaining_bytes(&self) -> &'b [u8] { + self.inner.remaining_bytes() + } + /// The layout of the struct being visited. pub fn struct_layout(&self) -> &'l MoveStructLayout { self.layout @@ -327,7 +545,7 @@ impl<'r, 'b, 'l> StructDriver<'r, 'b, 'l> { /// field and it was successfully visited (where `v` is the value returned by the visitor, and /// `f` is the layout of the field that was visited) or an error if there was an underlying /// deserialization error, or an error during visitation. - pub fn next_field( + pub fn next_field + ?Sized>( &mut self, visitor: &mut V, ) -> Result, V::Error> { @@ -335,7 +553,7 @@ impl<'r, 'b, 'l> StructDriver<'r, 'b, 'l> { return Ok(None); }; - let res = visit_value(self.bytes, &field.layout, visitor)?; + let res = visit_value(self.inner.bytes, &field.layout, visitor)?; self.off += 1; Ok(Some((field, res))) } @@ -348,16 +566,16 @@ impl<'r, 'b, 'l> StructDriver<'r, 'b, 'l> { } } -impl<'r, 'b, 'l> VariantDriver<'r, 'b, 'l> { +impl<'c, 'b, 'l> VariantDriver<'c, 'b, 'l> { fn new( - bytes: &'r mut &'b [u8], + inner: ValueDriver<'c, 'b, 'l>, layout: &'l MoveEnumLayout, variant_layout: &'l [MoveFieldLayout], variant_name: &'l IdentStr, tag: u16, ) -> Self { Self { - bytes, + inner, layout, tag, variant_name, @@ -366,6 +584,26 @@ impl<'r, 'b, 'l> VariantDriver<'r, 'b, 'l> { } } + /// The offset at which the value being visited starts in the byte stream. + pub fn start(&self) -> usize { + self.inner.start() + } + + /// The current position in the byte stream. + pub fn position(&self) -> usize { + self.inner.position() + } + + /// All the bytes in the byte stream (including the ones that have been read). + pub fn bytes(&self) -> &'b [u8] { + self.inner.bytes() + } + + /// The bytes that haven't been consumed by the visitor yet. + pub fn remaining_bytes(&self) -> &'b [u8] { + self.inner.remaining_bytes() + } + /// The layout of the enum being visited. pub fn enum_layout(&self) -> &'l MoveEnumLayout { self.layout @@ -399,7 +637,7 @@ impl<'r, 'b, 'l> VariantDriver<'r, 'b, 'l> { /// field and it was successfully visited (where `v` is the value returned by the visitor, and /// `f` is the layout of the field that was visited) or an error if there was an underlying /// deserialization error, or an error during visitation. - pub fn next_field( + pub fn next_field + ?Sized>( &mut self, visitor: &mut V, ) -> Result, V::Error> { @@ -407,7 +645,7 @@ impl<'r, 'b, 'l> VariantDriver<'r, 'b, 'l> { return Ok(None); }; - let res = visit_value(self.bytes, &field.layout, visitor)?; + let res = visit_value(self.inner.bytes, &field.layout, visitor)?; self.off += 1; Ok(Some((field, res))) } @@ -423,49 +661,89 @@ impl<'r, 'b, 'l> VariantDriver<'r, 'b, 'l> { /// Visit a serialized Move value with the provided `layout`, held in `bytes`, using the provided /// visitor to build a value out of it. See `annoted_value::MoveValue::visit_deserialize` for /// details. -pub(crate) fn visit_value( - bytes: &mut &[u8], - layout: &MoveTypeLayout, +pub(crate) fn visit_value<'c, 'b, 'l, V: Visitor<'b, 'l> + ?Sized>( + bytes: &'c mut Cursor<&'b [u8]>, + layout: &'l MoveTypeLayout, visitor: &mut V, ) -> Result { use MoveTypeLayout as L; + let mut driver = ValueDriver::new(bytes, Some(layout)); match layout { - L::Bool => match read_exact::<1>(bytes)? { - [0] => visitor.visit_bool(false), - [1] => visitor.visit_bool(true), + L::Bool => match driver.read_exact()? { + [0] => visitor.visit_bool(&driver, false), + [1] => visitor.visit_bool(&driver, true), [b] => Err(Error::UnexpectedByte(b).into()), }, - L::U8 => visitor.visit_u8(u8::from_le_bytes(read_exact::<1>(bytes)?)), - L::U16 => visitor.visit_u16(u16::from_le_bytes(read_exact::<2>(bytes)?)), - L::U32 => visitor.visit_u32(u32::from_le_bytes(read_exact::<4>(bytes)?)), - L::U64 => visitor.visit_u64(u64::from_le_bytes(read_exact::<8>(bytes)?)), - L::U128 => visitor.visit_u128(u128::from_le_bytes(read_exact::<16>(bytes)?)), - L::U256 => visitor.visit_u256(U256::from_le_bytes(&read_exact::<32>(bytes)?)), - L::Address => visitor.visit_address(AccountAddress::new(read_exact::<32>(bytes)?)), - L::Signer => visitor.visit_signer(AccountAddress::new(read_exact::<32>(bytes)?)), - - L::Vector(l) => { - let len = leb128::read::unsigned(bytes).map_err(|_| Error::UnexpectedEof)?; - let mut driver = VecDriver::new(bytes, l.as_ref(), len); - let res = visitor.visit_vector(&mut driver)?; - while driver.skip_element()? {} - Ok(res) + L::U8 => { + let v = u8::from_le_bytes(driver.read_exact()?); + visitor.visit_u8(&driver, v) + } + + L::U16 => { + let v = u16::from_le_bytes(driver.read_exact()?); + visitor.visit_u16(&driver, v) + } + + L::U32 => { + let v = u32::from_le_bytes(driver.read_exact()?); + visitor.visit_u32(&driver, v) + } + + L::U64 => { + let v = u64::from_le_bytes(driver.read_exact()?); + visitor.visit_u64(&driver, v) + } + + L::U128 => { + let v = u128::from_le_bytes(driver.read_exact()?); + visitor.visit_u128(&driver, v) } - L::Enum(e) => visit_variant(bytes, e, visitor), - L::Struct(l) => visit_struct(bytes, l, visitor), + + L::U256 => { + let v = U256::from_le_bytes(&driver.read_exact()?); + visitor.visit_u256(&driver, v) + } + + L::Address => { + let v = AccountAddress::new(driver.read_exact()?); + visitor.visit_address(&driver, v) + } + + L::Signer => { + let v = AccountAddress::new(driver.read_exact()?); + visitor.visit_signer(&driver, v) + } + + L::Vector(l) => visit_vector(driver, l.as_ref(), visitor), + L::Struct(l) => visit_struct(driver, l, visitor), + L::Enum(e) => visit_variant(driver, e, visitor), } } +/// Like `visit_value` but specialized to visiting a vector (where the `bytes` is known to be a +/// serialized move vector), and the layout is the vector's element's layout. +fn visit_vector<'c, 'b, 'l, V: Visitor<'b, 'l> + ?Sized>( + mut inner: ValueDriver<'c, 'b, 'l>, + layout: &'l MoveTypeLayout, + visitor: &mut V, +) -> Result { + let len = inner.read_leb128()?; + let mut driver = VecDriver::new(inner, layout, len); + let res = visitor.visit_vector(&mut driver)?; + while driver.skip_element()? {} + Ok(res) +} + /// Like `visit_value` but specialized to visiting a struct (where the `bytes` is known to be a /// serialized move struct), and the layout is a struct layout. -pub(crate) fn visit_struct( - bytes: &mut &[u8], - layout: &MoveStructLayout, +pub(crate) fn visit_struct<'c, 'b, 'l, V: Visitor<'b, 'l> + ?Sized>( + inner: ValueDriver<'c, 'b, 'l>, + layout: &'l MoveStructLayout, visitor: &mut V, ) -> Result { - let mut driver = StructDriver::new(bytes, layout); + let mut driver = StructDriver::new(inner, layout); let res = visitor.visit_struct(&mut driver)?; while driver.skip_field()?.is_some() {} Ok(res) @@ -473,15 +751,15 @@ pub(crate) fn visit_struct( /// Like `visit_struct` but specialized to visiting a variant (where the `bytes` is known to be a /// serialized move variant), and the layout is an enum layout. -pub(crate) fn visit_variant( - bytes: &mut &[u8], - layout: &MoveEnumLayout, +fn visit_variant<'c, 'b, 'l, V: Visitor<'b, 'l> + ?Sized>( + mut inner: ValueDriver<'c, 'b, 'l>, + layout: &'l MoveEnumLayout, visitor: &mut V, ) -> Result { // Since variants are bounded at 127, we can read the tag as a single byte. // When we add true ULEB encoding for enum variants switch to this: - // let tag = leb128::read::unsigned(bytes).map_err(|_| Error::UnexpectedEof)?; - let [tag] = read_exact::<1>(bytes)?; + // let tag = inner.read_leb128()?; + let [tag] = inner.read_exact()?; if tag >= VARIANT_COUNT_MAX as u8 { return Err(Error::UnexpectedVariantTag(tag as usize).into()); } @@ -492,7 +770,7 @@ pub(crate) fn visit_variant( .ok_or(Error::UnexpectedVariantTag(tag as usize))?; let mut driver = VariantDriver::new( - bytes, + inner, layout, variant_layout.1, &variant_layout.0 .0, @@ -502,11 +780,3 @@ pub(crate) fn visit_variant( while driver.skip_field()?.is_some() {} Ok(res) } - -fn read_exact(bytes: &mut &[u8]) -> Result<[u8; N], Error> { - let mut buf = [0u8; N]; - bytes - .read_exact(&mut buf) - .map_err(|_| Error::UnexpectedEof)?; - Ok(buf) -} diff --git a/external-crates/move/crates/move-core-types/src/unit_tests/visitor_test.rs b/external-crates/move/crates/move-core-types/src/unit_tests/visitor_test.rs index 2daee77142270..7d0c06b8696af 100644 --- a/external-crates/move/crates/move-core-types/src/unit_tests/visitor_test.rs +++ b/external-crates/move/crates/move-core-types/src/unit_tests/visitor_test.rs @@ -10,7 +10,8 @@ use crate::{ MoveVariant, }, annotated_visitor::{ - self, NullTraversal, StructDriver, Traversal, VariantDriver, VecDriver, Visitor, + self, NullTraversal, StructDriver, Traversal, ValueDriver, VariantDriver, VecDriver, + Visitor, }, identifier::Identifier, language_storage::StructTag, @@ -26,57 +27,93 @@ fn traversal() { #[derive(Default)] struct CountingTraversal(usize); - impl Traversal for CountingTraversal { + impl<'b, 'l> Traversal<'b, 'l> for CountingTraversal { type Error = annotated_visitor::Error; - fn traverse_u8(&mut self, _: u8) -> Result<(), Self::Error> { + fn traverse_u8( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u8, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } - fn traverse_u16(&mut self, _: u16) -> Result<(), Self::Error> { + fn traverse_u16( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u16, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } - fn traverse_u32(&mut self, _: u32) -> Result<(), Self::Error> { + fn traverse_u32( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u32, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } - fn traverse_u64(&mut self, _: u64) -> Result<(), Self::Error> { + fn traverse_u64( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u64, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } - fn traverse_u128(&mut self, _: u128) -> Result<(), Self::Error> { + fn traverse_u128( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: u128, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } - fn traverse_u256(&mut self, _: U256) -> Result<(), Self::Error> { + fn traverse_u256( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: U256, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } - fn traverse_bool(&mut self, _: bool) -> Result<(), Self::Error> { + fn traverse_bool( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: bool, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } - fn traverse_address(&mut self, _: AccountAddress) -> Result<(), Self::Error> { + fn traverse_address( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: AccountAddress, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } - fn traverse_signer(&mut self, _: AccountAddress) -> Result<(), Self::Error> { + fn traverse_signer( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + _value: AccountAddress, + ) -> Result<(), Self::Error> { self.0 += 1; Ok(()) } fn traverse_vector( &mut self, - driver: &mut VecDriver<'_, '_, '_>, + driver: &mut VecDriver<'_, 'b, 'l>, ) -> Result<(), Self::Error> { self.0 += 1; while driver.next_element(self)?.is_some() {} @@ -85,7 +122,7 @@ fn traversal() { fn traverse_struct( &mut self, - driver: &mut StructDriver<'_, '_, '_>, + driver: &mut StructDriver<'_, 'b, 'l>, ) -> Result<(), Self::Error> { self.0 += 1; while driver.next_field(self)?.is_some() {} @@ -94,7 +131,7 @@ fn traversal() { fn traverse_variant( &mut self, - driver: &mut VariantDriver<'_, '_, '_>, + driver: &mut VariantDriver<'_, 'b, 'l>, ) -> Result<(), Self::Error> { self.0 += 1; while driver.next_field(self)?.is_some() {} @@ -303,58 +340,94 @@ fn nested_datatype_visit() { output: String, } - impl Visitor for PrintVisitor { + impl<'b, 'l> Visitor<'b, 'l> for PrintVisitor { type Value = MoveValue; type Error = annotated_visitor::Error; - fn visit_u8(&mut self, value: u8) -> Result { + fn visit_u8( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u8, + ) -> Result { write!(self.output, "\n[{}] {value}: u8", self.depth).unwrap(); Ok(V::U8(value)) } - fn visit_u16(&mut self, value: u16) -> Result { + fn visit_u16( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u16, + ) -> Result { write!(self.output, "\n[{}] {value}: u16", self.depth).unwrap(); Ok(V::U16(value)) } - fn visit_u32(&mut self, value: u32) -> Result { + fn visit_u32( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u32, + ) -> Result { write!(self.output, "\n[{}] {value}: u32", self.depth).unwrap(); Ok(V::U32(value)) } - fn visit_u64(&mut self, value: u64) -> Result { + fn visit_u64( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u64, + ) -> Result { write!(self.output, "\n[{}] {value}: u64", self.depth).unwrap(); Ok(V::U64(value)) } - fn visit_u128(&mut self, value: u128) -> Result { + fn visit_u128( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u128, + ) -> Result { write!(self.output, "\n[{}] {value}: u128", self.depth).unwrap(); Ok(V::U128(value)) } - fn visit_u256(&mut self, value: U256) -> Result { + fn visit_u256( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: U256, + ) -> Result { write!(self.output, "\n[{}] {value}: u256", self.depth).unwrap(); Ok(V::U256(value)) } - fn visit_bool(&mut self, value: bool) -> Result { + fn visit_bool( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: bool, + ) -> Result { write!(self.output, "\n[{}] {value}: bool", self.depth).unwrap(); Ok(V::Bool(value)) } - fn visit_address(&mut self, value: AccountAddress) -> Result { + fn visit_address( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result { write!(self.output, "\n[{}] {value}: address", self.depth).unwrap(); Ok(V::Address(value)) } - fn visit_signer(&mut self, value: AccountAddress) -> Result { + fn visit_signer( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result { write!(self.output, "\n[{}] {value}: signer", self.depth).unwrap(); Ok(V::Signer(value)) } fn visit_vector( &mut self, - driver: &mut VecDriver<'_, '_, '_>, + driver: &mut VecDriver<'_, 'b, 'l>, ) -> Result { let layout = driver.element_layout(); write!(self.output, "\n[{}] vector<{layout:#}>", self.depth).unwrap(); @@ -375,7 +448,7 @@ fn nested_datatype_visit() { fn visit_struct( &mut self, - driver: &mut StructDriver<'_, '_, '_>, + driver: &mut StructDriver<'_, 'b, 'l>, ) -> Result { let layout = driver.struct_layout(); write!(self.output, "\n[{}] {layout:#}", self.depth).unwrap(); @@ -397,7 +470,7 @@ fn nested_datatype_visit() { fn visit_variant( &mut self, - driver: &mut VariantDriver<'_, '_, '_>, + driver: &mut VariantDriver<'_, 'b, 'l>, ) -> Result { let layout = driver.enum_layout(); write!(self.output, "\n[{}] {layout:#}", self.depth).unwrap(); @@ -520,17 +593,21 @@ fn peek_field_test() { fields: &'f [&'f str], } - impl<'f> Visitor for PeekU64Visitor<'f> { + impl<'b, 'l, 'f> Visitor<'b, 'l> for PeekU64Visitor<'f> { type Value = Option; type Error = annotated_visitor::Error; - fn visit_u64(&mut self, value: u64) -> Result { + fn visit_u64( + &mut self, + _driver: &ValueDriver<'_, 'b, 'l>, + value: u64, + ) -> Result { Ok(self.fields.is_empty().then_some(value)) } fn visit_struct( &mut self, - driver: &mut StructDriver<'_, '_, '_>, + driver: &mut StructDriver<'_, 'b, 'l>, ) -> Result { let [field, fields @ ..] = self.fields else { return Ok(None); @@ -551,7 +628,7 @@ fn peek_field_test() { fn visit_variant( &mut self, - driver: &mut VariantDriver<'_, '_, '_>, + driver: &mut VariantDriver<'_, 'b, 'l>, ) -> Result { let [field, fields @ ..] = self.fields else { return Ok(None); @@ -572,35 +649,67 @@ fn peek_field_test() { // === Empty/default cases === - fn visit_u8(&mut self, _: u8) -> Result { + fn visit_u8( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: u8, + ) -> Result { Ok(None) } - fn visit_u16(&mut self, _: u16) -> Result { + fn visit_u16( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: u16, + ) -> Result { Ok(None) } - fn visit_u32(&mut self, _: u32) -> Result { + fn visit_u32( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: u32, + ) -> Result { Ok(None) } - fn visit_u128(&mut self, _: u128) -> Result { + fn visit_u128( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: u128, + ) -> Result { Ok(None) } - fn visit_u256(&mut self, _: U256) -> Result { + fn visit_u256( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: U256, + ) -> Result { Ok(None) } - fn visit_bool(&mut self, _: bool) -> Result { + fn visit_bool( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: bool, + ) -> Result { Ok(None) } - fn visit_address(&mut self, _: AccountAddress) -> Result { + fn visit_address( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: AccountAddress, + ) -> Result { Ok(None) } - fn visit_signer(&mut self, _: AccountAddress) -> Result { + fn visit_signer( + &mut self, + _: &ValueDriver<'_, 'b, 'l>, + _: AccountAddress, + ) -> Result { Ok(None) } @@ -608,7 +717,7 @@ fn peek_field_test() { /// under here. fn visit_vector( &mut self, - _: &mut VecDriver<'_, '_, '_>, + _: &mut VecDriver<'_, 'b, 'l>, ) -> Result { Ok(None) } @@ -670,6 +779,284 @@ fn peek_field_test() { assert_eq!(visit_struct(&["f", "h"]), Some(46)); } +#[test] +fn byte_offset_test() { + use MoveTypeLayout as T; + use MoveValue as V; + + #[derive(Default)] + struct ByteOffsetVisitor(String); + + impl<'b, 'l> Traversal<'b, 'l> for ByteOffsetVisitor { + type Error = annotated_visitor::Error; + + fn traverse_u8( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u8, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {value}: u8", + driver.start(), + driver.position() + ) + .unwrap(); + Ok(()) + } + + fn traverse_u16( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u16, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {value}: u16", + driver.start(), + driver.position() + ) + .unwrap(); + Ok(()) + } + + fn traverse_u32( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u32, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {value}: u32", + driver.start(), + driver.position() + ) + .unwrap(); + Ok(()) + } + + fn traverse_u64( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u64, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {value}: u64", + driver.start(), + driver.position() + ) + .unwrap(); + Ok(()) + } + + fn traverse_u128( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: u128, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {value}: u128", + driver.start(), + driver.position() + ) + .unwrap(); + Ok(()) + } + + fn traverse_u256( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: U256, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {value}: u256", + driver.start(), + driver.position() + ) + .unwrap(); + Ok(()) + } + + fn traverse_bool( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: bool, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {value}: bool", + driver.start(), + driver.position() + ) + .unwrap(); + Ok(()) + } + + fn traverse_address( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {}: address", + driver.start(), + driver.position(), + value.to_canonical_display(/* with_prefix */ true), + ) + .unwrap(); + Ok(()) + } + + fn traverse_signer( + &mut self, + driver: &ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {}: address", + driver.start(), + driver.position(), + value.to_canonical_display(/* with_prefix */ true), + ) + .unwrap(); + Ok(()) + } + + fn traverse_vector( + &mut self, + driver: &mut VecDriver<'_, 'b, 'l>, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] vector<{:#}>", + driver.start(), + driver.position(), + driver.element_layout(), + ) + .unwrap(); + while driver.next_element(self)?.is_some() {} + Ok(()) + } + + fn traverse_struct( + &mut self, + driver: &mut StructDriver<'_, 'b, 'l>, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {:#}", + driver.start(), + driver.position(), + driver.struct_layout(), + ) + .unwrap(); + + while let Some((_, ())) = driver.next_field(self)? {} + Ok(()) + } + + fn traverse_variant( + &mut self, + driver: &mut VariantDriver<'_, 'b, 'l>, + ) -> Result<(), Self::Error> { + write!( + &mut self.0, + "\n[{:>3} .. {:>3}] {:#}", + driver.start(), + driver.position(), + driver.enum_layout(), + ) + .unwrap(); + + while let Some((_, ())) = driver.next_field(self)? {} + Ok(()) + } + } + + let type_layout = struct_layout_( + "0x0::foo::Bar", + vec![ + ( + "inner", + struct_layout_( + "0x0::baz::Qux", + vec![("f", T::U64), ("g", T::Vector(Box::new(T::U32)))], + ), + ), + ( + "last", + enum_layout_("0x0::foo::Baz", vec![("e", vec![("h", T::U64)])]), + ), + ], + ); + + let T::Struct(struct_layout) = &type_layout else { + panic!("Not a struct layout"); + }; + + let bytes = serialize(struct_value_( + "0x0::foo::Bar", + vec![ + ( + "inner", + struct_value_( + "0x0::baz::Qux", + vec![ + ("f", V::U64(7)), + ("g", V::Vector(vec![V::U32(1), V::U32(2), V::U32(3)])), + ], + ), + ), + ( + "last", + variant_value_("0x0::foo::Baz", "e", 0, vec![("h", V::U64(4))]), + ), + ], + )); + + let mut value_visitor = ByteOffsetVisitor::default(); + MoveValue::visit_deserialize(&bytes, &type_layout, &mut value_visitor).unwrap(); + + let mut struct_visitor = ByteOffsetVisitor::default(); + MoveStruct::visit_deserialize(&bytes, struct_layout, &mut struct_visitor).unwrap(); + + let expected_output = r#" +[ 0 .. 0] struct 0x0::foo::Bar { + inner: struct 0x0::baz::Qux { + f: u64, + g: vector, + }, + last: enum 0x0::foo::Baz { + e { + h: u64, + }, + }, +} +[ 0 .. 0] struct 0x0::baz::Qux { + f: u64, + g: vector, +} +[ 0 .. 8] 7: u64 +[ 8 .. 9] vector +[ 9 .. 13] 1: u32 +[ 13 .. 17] 2: u32 +[ 17 .. 21] 3: u32 +[ 21 .. 22] enum 0x0::foo::Baz { + e { + h: u64, + }, +} +[ 22 .. 30] 4: u64"#; + + assert_eq!(value_visitor.0, expected_output); + assert_eq!(struct_visitor.0, expected_output); +} + /// Create a struct value for test purposes. fn struct_value_(rep: &str, fields: Vec<(&str, MoveValue)>) -> MoveValue { let type_ = StructTag::from_str(rep).unwrap(); diff --git a/external-crates/move/crates/move-docgen/src/docgen.rs b/external-crates/move/crates/move-docgen/src/docgen.rs index 8aec76705e526..a3292443f5564 100644 --- a/external-crates/move/crates/move-docgen/src/docgen.rs +++ b/external-crates/move/crates/move-docgen/src/docgen.rs @@ -9,7 +9,7 @@ use codespan::{ByteIndex, Span}; use itertools::Itertools; use move_compiler::parser::keywords::{BUILTINS, CONTEXTUAL_KEYWORDS, KEYWORDS}; use move_model::{ - ast::ModuleName, + ast::{Attribute, ModuleName, Value}, code_writer::{CodeWriter, CodeWriterLabel}, emit, emitln, model::{ @@ -947,14 +947,29 @@ impl<'env> Docgen<'env> { /// Generates declaration for named constant fn named_constant_display(&self, const_env: &NamedConstantEnv<'_>) -> String { let name = self.name_string(const_env.get_name()); + let is_error_const = const_env.get_attributes().iter().any(|attr| + matches!(attr, Attribute::Apply(_, sym, _) if self.name_string(*sym).to_string() == *"error") + ); + let rendered_value = match (is_error_const, const_env.get_value()) { + (true, Value::ByteArray(bytes)) => { + if let Ok(s) = std::str::from_utf8(&bytes) { + format!("b\"{s}\"") + } else { + format!("{bytes:?}") + } + } + (_, value) => value.to_string(), + }; + let error_const_annot = if is_error_const { "#[error]\n" } else { "" }; format!( - "const {}: {} = {};", + "{}const {}: {} = {};", + error_const_annot, name, const_env.get_type().display(&TypeDisplayContext::WithEnv { env: self.env, type_param_names: None, }), - const_env.get_value(), + rendered_value, ) } diff --git a/external-crates/move/crates/move-docgen/tests/sources/const_string_test.move b/external-crates/move/crates/move-docgen/tests/sources/const_string_test.move new file mode 100644 index 0000000000000..1e47c84992bfe --- /dev/null +++ b/external-crates/move/crates/move-docgen/tests/sources/const_string_test.move @@ -0,0 +1,15 @@ +#[allow(unused)] +module 0x42::m { + #[error] + /// This is a doc comment above an error constant that should be rendered as a string + const AString: vector = b"Hello, world 🦀 "; + + #[error] + /// This is a doc comment above an error constant that should not be rendered as a string + const ErrorNotString: u64 = 10; + + const AStringNotError: vector = b"Hello, world 🦀 "; + + const NotAString: vector = vector[1, 2, 3]; +} + diff --git a/external-crates/move/crates/move-docgen/tests/sources/const_string_test.spec_inline.md b/external-crates/move/crates/move-docgen/tests/sources/const_string_test.spec_inline.md new file mode 100644 index 0000000000000..2ad75d7192be1 --- /dev/null +++ b/external-crates/move/crates/move-docgen/tests/sources/const_string_test.spec_inline.md @@ -0,0 +1,56 @@ + + + +# Module `0x42::m` + + + +- [Constants](#@Constants_0) + + +
+ + + + + +## Constants + + + + +This is a doc comment above an error constant that should be rendered as a string + + +
#[error]
+const AString: vector<u8> = b"Hello, world  🦀   ";
+
+ + + + + + + +
const AStringNotError: vector<u8> = [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 32, 32, 240, 159, 166, 128, 32, 32, 32];
+
+ + + + + +This is a doc comment above an error constant that should not be rendered as a string + + +
#[error]
+const ErrorNotString: u64 = 10;
+
+ + + + + + + +
const NotAString: vector<u8> = [1, 2, 3];
+
diff --git a/external-crates/move/crates/move-docgen/tests/sources/const_string_test.spec_inline_no_fold.md b/external-crates/move/crates/move-docgen/tests/sources/const_string_test.spec_inline_no_fold.md new file mode 100644 index 0000000000000..2ad75d7192be1 --- /dev/null +++ b/external-crates/move/crates/move-docgen/tests/sources/const_string_test.spec_inline_no_fold.md @@ -0,0 +1,56 @@ + + + +# Module `0x42::m` + + + +- [Constants](#@Constants_0) + + +
+ + + + + +## Constants + + + + +This is a doc comment above an error constant that should be rendered as a string + + +
#[error]
+const AString: vector<u8> = b"Hello, world  🦀   ";
+
+ + + + + + + +
const AStringNotError: vector<u8> = [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 32, 32, 240, 159, 166, 128, 32, 32, 32];
+
+ + + + + +This is a doc comment above an error constant that should not be rendered as a string + + +
#[error]
+const ErrorNotString: u64 = 10;
+
+ + + + + + + +
const NotAString: vector<u8> = [1, 2, 3];
+
diff --git a/external-crates/move/crates/move-docgen/tests/sources/const_string_test.spec_separate.md b/external-crates/move/crates/move-docgen/tests/sources/const_string_test.spec_separate.md new file mode 100644 index 0000000000000..2ad75d7192be1 --- /dev/null +++ b/external-crates/move/crates/move-docgen/tests/sources/const_string_test.spec_separate.md @@ -0,0 +1,56 @@ + + + +# Module `0x42::m` + + + +- [Constants](#@Constants_0) + + +
+ + + + + +## Constants + + + + +This is a doc comment above an error constant that should be rendered as a string + + +
#[error]
+const AString: vector<u8> = b"Hello, world  🦀   ";
+
+ + + + + + + +
const AStringNotError: vector<u8> = [72, 101, 108, 108, 111, 44, 32, 119, 111, 114, 108, 100, 32, 32, 240, 159, 166, 128, 32, 32, 32];
+
+ + + + + +This is a doc comment above an error constant that should not be rendered as a string + + +
#[error]
+const ErrorNotString: u64 = 10;
+
+ + + + + + + +
const NotAString: vector<u8> = [1, 2, 3];
+
diff --git a/external-crates/move/crates/move-ir-to-bytecode-syntax/src/lexer.rs b/external-crates/move/crates/move-ir-to-bytecode-syntax/src/lexer.rs index da0c9347547ca..c787c7a41741a 100644 --- a/external-crates/move/crates/move-ir-to-bytecode-syntax/src/lexer.rs +++ b/external-crates/move/crates/move-ir-to-bytecode-syntax/src/lexer.rs @@ -153,7 +153,7 @@ impl<'input> Lexer<'input> { loop { // Trim the only whitespace characters we recognize: newline, tab, and space. text = text.trim_start_matches("\r\n"); - text = text.trim_start_matches(|c: char| matches!(c, '\n' | '\t' | ' ')); + text = text.trim_start_matches(['\n', '\t', ' ']); // Trim the only comments we recognize: '// ... \n'. if text.starts_with("//") { text = text.trim_start_matches(|c: char| c != '\n'); diff --git a/external-crates/move/crates/move-ir-to-bytecode-syntax/src/syntax.rs b/external-crates/move/crates/move-ir-to-bytecode-syntax/src/syntax.rs index dd7a22df0f5f4..40a8dcceb7b92 100644 --- a/external-crates/move/crates/move-ir-to-bytecode-syntax/src/syntax.rs +++ b/external-crates/move/crates/move-ir-to-bytecode-syntax/src/syntax.rs @@ -1330,64 +1330,65 @@ fn parse_ability(tokens: &mut Lexer) -> Result<(Ability, Loc), ParseError Result> { + let start_loc = tokens.start_loc(); let t = match tokens.peek() { Tok::NameValue if matches!(tokens.content(), "address") => { tokens.advance()?; - Type::Address + Type_::Address } Tok::NameValue if matches!(tokens.content(), "u8") => { tokens.advance()?; - Type::U8 + Type_::U8 } Tok::NameValue if matches!(tokens.content(), "u16") => { tokens.advance()?; - Type::U16 + Type_::U16 } Tok::NameValue if matches!(tokens.content(), "u32") => { tokens.advance()?; - Type::U32 + Type_::U32 } Tok::NameValue if matches!(tokens.content(), "u64") => { tokens.advance()?; - Type::U64 + Type_::U64 } Tok::NameValue if matches!(tokens.content(), "u128") => { tokens.advance()?; - Type::U128 + Type_::U128 } Tok::NameValue if matches!(tokens.content(), "u256") => { tokens.advance()?; - Type::U256 + Type_::U256 } Tok::NameValue if matches!(tokens.content(), "bool") => { tokens.advance()?; - Type::Bool + Type_::Bool } Tok::NameValue if matches!(tokens.content(), "signer") => { tokens.advance()?; - Type::Signer + Type_::Signer } Tok::NameBeginTyValue if matches!(tokens.content(), "vector<") => { tokens.advance()?; let ty = parse_type(tokens)?; adjust_token(tokens, &[Tok::Greater])?; consume_token(tokens, Tok::Greater)?; - Type::Vector(Box::new(ty)) + Type_::Vector(Box::new(ty)) } Tok::DotNameValue => { let s = parse_qualified_struct_ident(tokens)?; let tys = parse_type_actuals(tokens)?; - Type::Datatype(s, tys) + Type_::Datatype(s, tys) } Tok::Amp => { tokens.advance()?; - Type::Reference(false, Box::new(parse_type(tokens)?)) + Type_::Reference(false, Box::new(parse_type(tokens)?)) } Tok::AmpMut => { tokens.advance()?; - Type::Reference(true, Box::new(parse_type(tokens)?)) + Type_::Reference(true, Box::new(parse_type(tokens)?)) } - Tok::NameValue => Type::TypeParameter(TypeVar_(parse_name(tokens)?)), + Tok::NameValue => Type_::TypeParameter(TypeVar_(parse_name(tokens)?)), t => { return Err(ParseError::InvalidToken { location: current_token_loc(tokens), @@ -1395,7 +1396,8 @@ fn parse_type(tokens: &mut Lexer) -> Result }) } }; - Ok(t) + let end_loc = tokens.previous_end_loc(); + Ok(spanned(tokens.file_hash(), start_loc, end_loc, t)) } // TypeVar: TypeVar = { diff --git a/external-crates/move/crates/move-ir-to-bytecode/src/compiler.rs b/external-crates/move/crates/move-ir-to-bytecode/src/compiler.rs index 020bf1c6523b5..adaa773b53614 100644 --- a/external-crates/move/crates/move-ir-to-bytecode/src/compiler.rs +++ b/external-crates/move/crates/move-ir-to-bytecode/src/compiler.rs @@ -47,6 +47,11 @@ macro_rules! record_src_loc { .source_map .add_parameter_mapping($context.current_function_definition_index(), source_name)?; }}; + (return_: $context:expr, $_type:expr) => {{ + $context + .source_map + .add_return_mapping($context.current_function_definition_index(), $_type.loc)?; + }}; (field: $context:expr, $idx: expr, $field:expr) => {{ $context .source_map @@ -340,7 +345,7 @@ fn constant_name_as_constant_value_index( ) -> Result { let name_constant = compile_constant( context, - Type::Vector(Box::new(Type::U8)), + &MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)), MoveValue::vector_u8(const_name.to_string().into_bytes()), )?; context.constant_index(name_constant) @@ -407,7 +412,11 @@ pub fn compile_module<'a>( constant_name_as_constant_value_index(&mut context, &ir_constant.name)?; } - let constant = compile_constant(&mut context, ir_constant.signature, ir_constant.value)?; + let constant = compile_constant( + &mut context, + &type_to_constant_type_layout(ir_constant.signature)?, + ir_constant.value, + )?; context.declare_constant(ir_constant.name.clone(), constant.clone())?; let const_idx = context.constant_index(constant)?; record_src_loc!(const_decl: context, const_idx, ir_constant.name); @@ -672,22 +681,22 @@ fn compile_type( type_parameters: &HashMap, ty: &Type, ) -> Result { - Ok(match ty { - Type::Address => SignatureToken::Address, - Type::Signer => SignatureToken::Signer, - Type::U8 => SignatureToken::U8, - Type::U16 => SignatureToken::U16, - Type::U32 => SignatureToken::U32, - Type::U64 => SignatureToken::U64, - Type::U128 => SignatureToken::U128, - Type::U256 => SignatureToken::U256, - Type::Bool => SignatureToken::Bool, - Type::Vector(inner_type) => SignatureToken::Vector(Box::new(compile_type( + Ok(match &ty.value { + Type_::Address => SignatureToken::Address, + Type_::Signer => SignatureToken::Signer, + Type_::U8 => SignatureToken::U8, + Type_::U16 => SignatureToken::U16, + Type_::U32 => SignatureToken::U32, + Type_::U64 => SignatureToken::U64, + Type_::U128 => SignatureToken::U128, + Type_::U256 => SignatureToken::U256, + Type_::Bool => SignatureToken::Bool, + Type_::Vector(inner_type) => SignatureToken::Vector(Box::new(compile_type( context, type_parameters, inner_type, )?)), - Type::Reference(is_mutable, inner_type) => { + Type_::Reference(is_mutable, inner_type) => { let inner_token = Box::new(compile_type(context, type_parameters, inner_type)?); if *is_mutable { SignatureToken::MutableReference(inner_token) @@ -695,7 +704,7 @@ fn compile_type( SignatureToken::Reference(inner_token) } } - Type::Datatype(ident, tys) => { + Type_::Datatype(ident, tys) => { let sh_idx = context.datatype_handle_index(ident.clone())?; if tys.is_empty() { @@ -705,7 +714,7 @@ fn compile_type( SignatureToken::DatatypeInstantiation(Box::new((sh_idx, tokens))) } } - Type::TypeParameter(ty_var) => { + Type_::TypeParameter(ty_var) => { let idx = match type_parameters.get(ty_var) { None => bail!("Unbound type parameter {}", ty_var), Some(idx) => *idx, @@ -878,6 +887,7 @@ fn compile_function_body_impl( context, m, ast_function.signature.formals, + ast_function.signature.return_type, locals, code, )?) @@ -894,6 +904,7 @@ fn compile_function_body_impl( context, m, ast_function.signature.formals, + ast_function.signature.return_type, locals, code, )?) @@ -903,6 +914,9 @@ fn compile_function_body_impl( for (var, _) in ast_function.signature.formals.into_iter() { record_src_loc!(parameter: context, var) } + for _type in ast_function.signature.return_type.into_iter() { + record_src_loc!(return_: context, _type) + } None } }) @@ -950,6 +964,7 @@ fn compile_function_body( context: &mut Context, type_parameters: HashMap, formals: Vec<(Var, Type)>, + return_type: Vec, locals: Vec<(Var, Type)>, blocks: Vec, ) -> Result { @@ -960,6 +975,9 @@ fn compile_function_body( record_src_loc!(parameter: context, var); } + for _type in return_type { + record_src_loc!(return_: context, _type); + } let mut locals_signature = Signature(vec![]); for (var_, t) in locals { let sig = compile_type(context, function_frame.type_parameters(), &t)?; @@ -1267,7 +1285,7 @@ fn compile_expression( Exp_::Value(cv) => match cv.value { CopyableVal_::Address(address) => { let address_value = MoveValue::Address(address); - let constant = compile_constant(context, Type::Address, address_value)?; + let constant = compile_constant(context, &MoveTypeLayout::Address, address_value)?; let idx = context.constant_index(constant)?; push_instr!(exp.loc, Bytecode::LdConst(idx)); function_frame.push()?; @@ -1298,8 +1316,8 @@ fn compile_expression( } CopyableVal_::ByteArray(buf) => { let vec_value = MoveValue::vector_u8(buf); - let ty = Type::Vector(Box::new(Type::U8)); - let constant = compile_constant(context, ty, vec_value)?; + let ty = MoveTypeLayout::Vector(Box::new(MoveTypeLayout::U8)); + let constant = compile_constant(context, &ty, vec_value)?; let idx = context.constant_index(constant)?; push_instr!(exp.loc, Bytecode::LdConst(idx)); function_frame.push()?; @@ -1669,30 +1687,38 @@ fn compile_call( Ok(()) } -fn compile_constant(_context: &mut Context, ty: Type, value: MoveValue) -> Result { - fn type_layout(ty: Type) -> Result { - Ok(match ty { - Type::Address => MoveTypeLayout::Address, - Type::Signer => MoveTypeLayout::Signer, - Type::U8 => MoveTypeLayout::U8, - Type::U16 => MoveTypeLayout::U16, - Type::U32 => MoveTypeLayout::U32, - Type::U64 => MoveTypeLayout::U64, - Type::U128 => MoveTypeLayout::U128, - Type::U256 => MoveTypeLayout::U256, - Type::Bool => MoveTypeLayout::Bool, - Type::Vector(inner_type) => MoveTypeLayout::Vector(Box::new(type_layout(*inner_type)?)), - Type::Reference(_, _) => bail!("References are not supported in constant type layouts"), - Type::TypeParameter(_) => { - bail!("Type parameters are not supported in constant type layouts") - } - Type::Datatype(_ident, _tys) => { - bail!("TODO Structs are not *yet* supported in constant type layouts") - } - }) - } +fn type_to_constant_type_layout(ty: Type) -> Result { + Ok(match ty.value { + Type_::Address => MoveTypeLayout::Address, + Type_::Signer => MoveTypeLayout::Signer, + Type_::U8 => MoveTypeLayout::U8, + Type_::U16 => MoveTypeLayout::U16, + Type_::U32 => MoveTypeLayout::U32, + Type_::U64 => MoveTypeLayout::U64, + Type_::U128 => MoveTypeLayout::U128, + Type_::U256 => MoveTypeLayout::U256, + Type_::Bool => MoveTypeLayout::Bool, + Type_::Vector(inner_type) => { + MoveTypeLayout::Vector(Box::new(type_to_constant_type_layout(*inner_type)?)) + } + Type_::Reference(_, _) => { + bail!("References are not supported in constant type layouts") + } + Type_::TypeParameter(_) => { + bail!("Type parameters are not supported in constant type layouts") + } + Type_::Datatype(_ident, _tys) => { + bail!("TODO Structs are not *yet* supported in constant type layouts") + } + }) +} - Constant::serialize_constant(&type_layout(ty)?, &value) +fn compile_constant( + _context: &mut Context, + layout: &MoveTypeLayout, + value: MoveValue, +) -> Result { + Constant::serialize_constant(layout, &value) .ok_or_else(|| format_err!("Could not serialize constant")) } @@ -1704,6 +1730,7 @@ fn compile_function_body_bytecode( context: &mut Context, type_parameters: HashMap, formals: Vec<(Var, Type)>, + return_type: Vec, locals: Vec<(Var, Type)>, blocks: BytecodeBlocks, ) -> Result { @@ -1714,6 +1741,9 @@ fn compile_function_body_bytecode( function_frame.define_local(&var.value, sig.clone())?; record_src_loc!(parameter: context, var); } + for _type in return_type { + record_src_loc!(return_: context, _type); + } for (var_, t) in locals { let sig = compile_type(context, function_frame.type_parameters(), &t)?; function_frame.define_local(&var_.value, sig.clone())?; @@ -1793,7 +1823,7 @@ fn compile_bytecode( IRBytecode_::LdTrue => Bytecode::LdTrue, IRBytecode_::LdFalse => Bytecode::LdFalse, IRBytecode_::LdConst(ty, v) => { - let constant = compile_constant(context, ty, v)?; + let constant = compile_constant(context, &type_to_constant_type_layout(ty)?, v)?; Bytecode::LdConst(context.constant_index(constant)?) } IRBytecode_::LdNamedConst(c) => Bytecode::LdConst(context.named_constant_index(&c)?), diff --git a/external-crates/move/crates/move-ir-types/src/ast.rs b/external-crates/move/crates/move-ir-types/src/ast.rs index e17c328967703..0fcd68c82dee4 100644 --- a/external-crates/move/crates/move-ir-types/src/ast.rs +++ b/external-crates/move/crates/move-ir-types/src/ast.rs @@ -135,9 +135,12 @@ pub enum Ability { // Types //************************************************************************************************** +/// The type of a single value coupled with source location information. +pub type Type = Spanned; + /// The type of a single value #[derive(Debug, PartialEq, Clone)] -pub enum Type { +pub enum Type_ { /// `address` Address, /// `signer` @@ -821,33 +824,6 @@ impl Ability { pub const KEY: &'static str = "key"; } -impl Type { - /// Creates a new struct type - pub fn r#struct(ident: QualifiedDatatypeIdent, type_actuals: Vec) -> Type { - Type::Datatype(ident, type_actuals) - } - - /// Creates a new reference type from its mutability and underlying type - pub fn reference(is_mutable: bool, t: Type) -> Type { - Type::Reference(is_mutable, Box::new(t)) - } - - /// Creates a new address type - pub fn address() -> Type { - Type::Address - } - - /// Creates a new u64 type - pub fn u64() -> Type { - Type::U64 - } - - /// Creates a new bool type - pub fn bool() -> Type { - Type::Bool - } -} - impl QualifiedDatatypeIdent { /// Creates a new StructType handle from the name of the module alias and the name of the struct pub fn new(module: ModuleName, name: DatatypeName) -> Self { @@ -1440,24 +1416,24 @@ fn format_struct_type_formals(formals: &[DatatypeTypeParameter]) -> String { } } -impl fmt::Display for Type { +impl fmt::Display for Type_ { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - Type::U8 => write!(f, "u8"), - Type::U16 => write!(f, "u16"), - Type::U32 => write!(f, "u32"), - Type::U64 => write!(f, "u64"), - Type::U128 => write!(f, "u128"), - Type::U256 => write!(f, "u256"), - Type::Bool => write!(f, "bool"), - Type::Address => write!(f, "address"), - Type::Signer => write!(f, "signer"), - Type::Vector(ty) => write!(f, "vector<{}>", ty), - Type::Datatype(ident, tys) => write!(f, "{}{}", ident, format_type_actuals(tys)), - Type::Reference(is_mutable, t) => { + Type_::U8 => write!(f, "u8"), + Type_::U16 => write!(f, "u16"), + Type_::U32 => write!(f, "u32"), + Type_::U64 => write!(f, "u64"), + Type_::U128 => write!(f, "u128"), + Type_::U256 => write!(f, "u256"), + Type_::Bool => write!(f, "bool"), + Type_::Address => write!(f, "address"), + Type_::Signer => write!(f, "signer"), + Type_::Vector(ty) => write!(f, "vector<{}>", ty), + Type_::Datatype(ident, tys) => write!(f, "{}{}", ident, format_type_actuals(tys)), + Type_::Reference(is_mutable, t) => { write!(f, "&{}{}", if *is_mutable { "mut " } else { "" }, t) } - Type::TypeParameter(s) => write!(f, "{}", s), + Type_::TypeParameter(s) => write!(f, "{}", s), } } } diff --git a/external-crates/move/crates/move-model/src/builder/model_builder.rs b/external-crates/move/crates/move-model/src/builder/model_builder.rs index 73ffd986c2f37..0f11aaa4dbd69 100644 --- a/external-crates/move/crates/move-model/src/builder/model_builder.rs +++ b/external-crates/move/crates/move-model/src/builder/model_builder.rs @@ -74,6 +74,7 @@ pub(crate) struct ConstEntry { pub loc: Loc, pub ty: Type, pub value: Value, + pub attributes: Vec, } impl<'env> ModelBuilder<'env> { diff --git a/external-crates/move/crates/move-model/src/builder/module_builder.rs b/external-crates/move/crates/move-model/src/builder/module_builder.rs index 69c8112b85db0..29da0ec84d658 100644 --- a/external-crates/move/crates/move-model/src/builder/module_builder.rs +++ b/external-crates/move/crates/move-model/src/builder/module_builder.rs @@ -287,13 +287,20 @@ impl<'env, 'translator> ModuleBuilder<'env, 'translator> { let move_value = Constant::deserialize_constant(&compiled_module.constant_pool()[*const_idx as usize]) .unwrap(); + let attributes = self.translate_attributes(&def.attributes); let mut et = ExpTranslator::new(self); let loc = et.to_loc(&def.loc); let ty = et.translate_type(&def.signature); let value = et.translate_from_move_value(&loc, &ty, &move_value); - et.parent - .parent - .define_const(qsym, ConstEntry { loc, ty, value }); + et.parent.parent.define_const( + qsym, + ConstEntry { + loc, + ty, + value, + attributes, + }, + ); } fn decl_ana_struct(&mut self, name: &PA::DatatypeName, def: &EA::StructDefinition) { @@ -626,12 +633,21 @@ impl<'env, 'translator> ModuleBuilder<'env, 'translator> { .iter() .filter(|(name, _)| name.module_name == self.module_name) .map(|(name, const_entry)| { - let ConstEntry { loc, value, ty } = const_entry.clone(); + let ConstEntry { + loc, + value, + ty, + attributes, + } = const_entry.clone(); ( NamedConstantId::new(name.symbol), - self.parent - .env - .create_named_constant_data(name.symbol, loc, ty, value), + self.parent.env.create_named_constant_data( + name.symbol, + loc, + ty, + value, + attributes, + ), ) }) .collect(); diff --git a/external-crates/move/crates/move-model/src/model.rs b/external-crates/move/crates/move-model/src/model.rs index 859be6bf46cc8..f80c4c15b9ba1 100644 --- a/external-crates/move/crates/move-model/src/model.rs +++ b/external-crates/move/crates/move-model/src/model.rs @@ -944,12 +944,14 @@ impl GlobalEnv { loc: Loc, typ: Type, value: Value, + attributes: Vec, ) -> NamedConstantData { NamedConstantData { name, loc, typ, value, + attributes, } } @@ -2997,6 +2999,9 @@ pub struct NamedConstantData { /// The value of this constant value: Value, + + /// Attributes attached to this constant + attributes: Vec, } #[derive(Debug)] @@ -3037,6 +3042,11 @@ impl<'env> NamedConstantEnv<'env> { pub fn get_value(&self) -> Value { self.data.value.clone() } + + /// Returns the attributes attached to this constant + pub fn get_attributes(&self) -> &[Attribute] { + &self.data.attributes + } } // ================================================================================================= diff --git a/external-crates/move/crates/move-package/src/compilation/compiled_package.rs b/external-crates/move/crates/move-package/src/compilation/compiled_package.rs index 998b18ea0e346..ff03ab46dbf1a 100644 --- a/external-crates/move/crates/move-package/src/compilation/compiled_package.rs +++ b/external-crates/move/crates/move-package/src/compilation/compiled_package.rs @@ -15,7 +15,7 @@ use anyhow::{ensure, Result}; use colored::Colorize; use itertools::{Either, Itertools}; use move_binary_format::file_format::CompiledModule; -use move_bytecode_source_map::utils::source_map_from_file; +use move_bytecode_source_map::utils::{serialize_to_json, source_map_from_file}; use move_bytecode_utils::Modules; use move_command_line_common::files::{ extension_equals, find_filenames, try_exists, MOVE_COMPILED_EXTENSION, MOVE_EXTENSION, @@ -321,6 +321,13 @@ impl OnDiskCompiledPackage { .with_extension(SOURCE_MAP_EXTENSION), compiled_unit.unit.serialize_source_map().as_slice(), )?; + self.save_under( + CompiledPackageLayout::SourceMaps + .path() + .join(&file_path) + .with_extension("json"), + &serialize_to_json(&compiled_unit.unit.source_map)?, + )?; self.save_under( CompiledPackageLayout::Sources .path() diff --git a/external-crates/move/crates/move-package/src/lock_file/schema.rs b/external-crates/move/crates/move-package/src/lock_file/schema.rs index c4904198a74c2..2502bb9d5ef8c 100644 --- a/external-crates/move/crates/move-package/src/lock_file/schema.rs +++ b/external-crates/move/crates/move-package/src/lock_file/schema.rs @@ -31,7 +31,8 @@ use super::LockFile; /// V0: Base version. /// V1: Adds toolchain versioning support. /// V2: Adds support for managing addresses on package publish and upgrades. -pub const VERSION: u64 = 2; +/// V3: Renames dependency `name` field to `id` and adds a `name` field to store the name from the manifest. +pub const VERSION: u16 = 3; /// Table for storing package info under an environment. const ENV_TABLE_NAME: &str = "env"; @@ -56,8 +57,8 @@ pub struct Packages { #[derive(Deserialize)] pub struct Package { - /// The name of the package (corresponds to the name field from its source manifest). - pub name: String, + /// Package identifier (as resolved by the package hook). + pub id: String, /// Where to find this dependency. Schema is not described in terms of serde-compatible /// structs, so it is deserialized into a generic data structure. @@ -73,6 +74,9 @@ pub struct Package { #[derive(Deserialize)] pub struct Dependency { + /// Package identifier (as resolved by the package hook). + pub id: String, + /// The name of the dependency (corresponds to the key for the dependency in the depending /// package's source manifest). pub name: String, @@ -110,7 +114,7 @@ pub struct ManagedPackage { #[derive(Serialize, Deserialize)] pub struct Header { - pub version: u64, + pub version: u16, /// A hash of the manifest file content this lock file was generated from computed using SHA-256 /// hashing algorithm. pub manifest_digest: String, @@ -199,9 +203,9 @@ impl Header { let Schema { move_: header } = toml::de::from_str::>(contents).context("Deserializing lock header")?; - if header.version > VERSION { + if header.version != VERSION { bail!( - "Lock file format is too new, expected version {} or below, found {}", + "Lock file format mismatch, expected version {}, found {}", VERSION, header.version ); @@ -252,7 +256,8 @@ pub fn update_dependency_graph( .as_table_mut() .ok_or_else(|| anyhow!("Could not find or create move table in Move.lock"))?; - // Update `manifest_digest` and `deps_digest` in `[move]` table section. + // Update `version`, `manifest_digest`, and `deps_digest` in `[move]` table section. + move_table["version"] = value(VERSION as i64); move_table["manifest_digest"] = value(manifest_digest); move_table["deps_digest"] = value(deps_digest); diff --git a/external-crates/move/crates/move-package/src/package_hooks.rs b/external-crates/move/crates/move-package/src/package_hooks.rs index 32f69e2af6ad0..1920632c456bd 100644 --- a/external-crates/move/crates/move-package/src/package_hooks.rs +++ b/external-crates/move/crates/move-package/src/package_hooks.rs @@ -1,7 +1,7 @@ // Copyright (c) The Move Contributors // SPDX-License-Identifier: Apache-2.0 -use crate::source_package::parsed_manifest::{CustomDepInfo, SourceManifest}; +use crate::source_package::parsed_manifest::{OnChainInfo, SourceManifest}; use anyhow::bail; use move_symbol_pool::Symbol; use once_cell::sync::Lazy; @@ -17,17 +17,13 @@ pub trait PackageHooks { /// Returns custom fields allowed in `PackageInfo`. fn custom_package_info_fields(&self) -> Vec; - /// Returns a custom key for dependencies, if available. This is the string used - /// in dependencies `{ = value, address = addr }. - fn custom_dependency_key(&self) -> Option; - - /// A resolver for custom dependencies in the manifest. This is called to download the + /// A resolver for on-chain dependencies in the manifest. This is called to download the /// dependency from the dependency into the `info.local_path` location, similar as with git /// dependencies. - fn resolve_custom_dependency( + fn resolve_on_chain_dependency( &self, dep_name: Symbol, - info: &CustomDepInfo, + info: &OnChainInfo, ) -> anyhow::Result<()>; fn custom_resolve_pkg_id(&self, manifest: &SourceManifest) @@ -44,22 +40,14 @@ pub fn register_package_hooks(hooks: Box) { } /// Calls any registered hook to resolve a node dependency. Bails if none is registered. -pub(crate) fn resolve_custom_dependency( +pub(crate) fn resolve_on_chain_dependency( dep_name: Symbol, - info: &CustomDepInfo, + info: &OnChainInfo, ) -> anyhow::Result<()> { if let Some(hooks) = &*HOOKS.lock().unwrap() { - hooks.resolve_custom_dependency(dep_name, info) - } else { - bail!("use of unsupported custom dependency in package manifest") - } -} - -pub(crate) fn custom_dependency_key() -> Option { - if let Some(hooks) = &*HOOKS.lock().unwrap() { - hooks.custom_dependency_key() + hooks.resolve_on_chain_dependency(dep_name, info) } else { - None + bail!("use of unsupported on-chain dependency in package manifest") } } diff --git a/external-crates/move/crates/move-package/src/resolution/dependency_cache.rs b/external-crates/move/crates/move-package/src/resolution/dependency_cache.rs index 4cd38aa334b9e..dc733271acca5 100644 --- a/external-crates/move/crates/move-package/src/resolution/dependency_cache.rs +++ b/external-crates/move/crates/move-package/src/resolution/dependency_cache.rs @@ -48,12 +48,12 @@ impl DependencyCache { match kind { DependencyKind::Local(_) => Ok(()), - DependencyKind::Custom(node_info) => { + DependencyKind::OnChain(info) => { // check if a give dependency type has already been fetched if !self.fetched_deps.insert(repository_path(kind)) { return Ok(()); } - package_hooks::resolve_custom_dependency(dep_name, node_info) + package_hooks::resolve_on_chain_dependency(dep_name, info) } DependencyKind::Git(GitInfo { diff --git a/external-crates/move/crates/move-package/src/resolution/dependency_graph.rs b/external-crates/move/crates/move-package/src/resolution/dependency_graph.rs index e5fea3ce123fb..94405446e1211 100644 --- a/external-crates/move/crates/move-package/src/resolution/dependency_graph.rs +++ b/external-crates/move/crates/move-package/src/resolution/dependency_graph.rs @@ -5,18 +5,20 @@ use anyhow::{bail, Context, Result}; use colored::Colorize; use move_symbol_pool::Symbol; use petgraph::{algo, prelude::DiGraphMap, Direction}; + +use std::io::BufRead; use std::{ collections::{btree_map::Entry, BTreeMap, BTreeSet, VecDeque}, fmt, fs::File, - io::{Read, Write}, + io::{BufReader, Read, Write}, path::{Path, PathBuf}, process::Command, }; use crate::{ lock_file::{schema, LockFile}, - package_hooks::{self, custom_resolve_pkg_id, resolve_version, PackageIdentifier}, + package_hooks::{custom_resolve_pkg_id, resolve_version, PackageIdentifier}, source_package::{ layout::SourcePackageLayout, manifest_parser::{ @@ -232,6 +234,7 @@ impl DependencyGraphBuilder { let lock_file = File::open(lock_path); let digest_and_lock_contents = lock_file .map(|mut lock_file| match schema::Header::read(&mut lock_file) { + Ok(header) if header.version < schema::VERSION => None, // outdated lock file - regenerate Ok(header) => Some((header.manifest_digest, header.deps_digest, lock_string_opt)), Err(_) => None, // malformed header - regenerate lock file }) @@ -375,8 +378,9 @@ impl DependencyGraphBuilder { let mut resolved_id_deps = BTreeMap::new(); let mut dep_orig_names = BTreeMap::new(); let mut overrides = BTreeMap::new(); + for (dep_pkg_name, dep) in dependencies { - let (pkg_graph, is_override, is_external, resolved_pkg_id, resolved_version) = self + let new_deps = self .new_for_dep( parent, &dep, @@ -392,34 +396,38 @@ impl DependencyGraphBuilder { parent_pkg_name ) })?; - dep_graphs.insert( - resolved_pkg_id, - DependencyGraphInfo::new( - pkg_graph, - mode, - is_override, - is_external, - resolved_version, - ), - ); - resolved_id_deps.insert(resolved_pkg_id, dep.clone()); - dep_orig_names.insert(resolved_pkg_id, dep_pkg_name); - - if is_override { - let kind = match dep { - PM::Dependency::Internal(d) => d.kind, - PM::Dependency::External(_) => { - // external dependencies cannot be overrides - panic!("Unexpected external dependency override") - } - }; - let mut dep_pkg = Package { - kind, - resolver: None, - version: resolved_version, - }; - dep_pkg.kind.reroot(parent)?; - overrides.insert(resolved_pkg_id, dep_pkg); + + for (pkg_graph, is_override, is_external, resolved_pkg_id, resolved_version) in new_deps + { + dep_graphs.insert( + resolved_pkg_id, + DependencyGraphInfo::new( + pkg_graph, + mode, + is_override, + is_external, + resolved_version, + ), + ); + resolved_id_deps.insert(resolved_pkg_id, dep.clone()); + dep_orig_names.insert(resolved_pkg_id, dep_pkg_name); + + if is_override { + let kind = match dep { + PM::Dependency::Internal(ref d) => d.kind.clone(), + PM::Dependency::External(_) => { + // external dependencies cannot be overrides + panic!("Unexpected external dependency override") + } + }; + let mut dep_pkg = Package { + kind, + resolver: None, + version: resolved_version, + }; + dep_pkg.kind.reroot(parent)?; + overrides.insert(resolved_pkg_id, dep_pkg); + } } } Ok((dep_graphs, resolved_id_deps, dep_orig_names, overrides)) @@ -435,8 +443,8 @@ impl DependencyGraphBuilder { parent_pkg_name: PM::PackageName, dep_pkg_name: PM::PackageName, dep_pkg_path: PathBuf, - ) -> Result<(DependencyGraph, bool, bool, Symbol, Option)> { - let (pkg_graph, is_override, is_external, resolved_pkg_name, resolved_version) = match dep { + ) -> Result)>> { + match dep { PM::Dependency::Internal(d) => { self.dependency_cache .download_and_update_if_remote(dep_pkg_name, &d.kind, &mut self.progress_output) @@ -481,16 +489,16 @@ impl DependencyGraphBuilder { p.kind.reroot(&d.kind)?; } } - ( + Ok(vec![( pkg_graph, d.dep_override, false, resolved_pkg_id, resolved_version, - ) + )]) } PM::Dependency::External(resolver) => { - let pkg_graph = DependencyGraph::get_external( + let external_deps = DependencyGraph::get_external( mode, parent_pkg_id, parent_pkg_name, @@ -499,18 +507,21 @@ impl DependencyGraphBuilder { &dep_pkg_path, &mut self.progress_output, )?; - // TODO: support resolved_pkg_name and resolved_version for - // externally resolved deps. - (pkg_graph, false, true, dep_pkg_name, None) + + Ok(external_deps + .into_iter() + .map(|(pkg_graph, _, resolved_pkg_id, resolved_pkg_version)| { + ( + pkg_graph, + false, + true, + resolved_pkg_id, + resolved_pkg_version, + ) + }) + .collect()) } - }; - Ok(( - pkg_graph, - is_override, - is_external, - resolved_pkg_name, - resolved_version, - )) + } } /// Computes dependency hashes. @@ -959,9 +970,8 @@ impl DependencyGraph { Ok(true) } PM::Dependency::External(_) => { - // the way that external graphs are constructed, edges between the (root) package of - // the outer graph and dependencies in the sub-graph are already present in the - // sub-graph + // External dependencies exist in the subgraph of the root package and are added to + // the package_graph as such. let d = sub_graph .package_graph .edge_weight(self.root_package_id, dep_pkg_id) @@ -1066,39 +1076,41 @@ impl DependencyGraph { package_graph.add_node(root_package_id); for schema::Dependency { - name, + id: dep_id, + name: dep_name, subst, digest, } in packages.root_dependencies.into_iter().flatten() { package_graph.add_edge( root_package_id, - Symbol::from(name.as_str()), + PackageIdentifier::from(dep_id.as_str()), Dependency { mode: DependencyMode::Always, subst: subst.map(parse_substitution).transpose()?, digest: digest.map(Symbol::from), dep_override: false, - dep_name: PM::PackageName::from(name), + dep_name: PM::PackageName::from(dep_name), }, ); } for schema::Dependency { - name, + id: dep_id, + name: dep_name, subst, digest, } in packages.root_dev_dependencies.into_iter().flatten() { package_graph.add_edge( root_package_id, - Symbol::from(name.as_str()), + PackageIdentifier::from(dep_id.as_str()), Dependency { mode: DependencyMode::DevOnly, subst: subst.map(parse_substitution).transpose()?, digest: digest.map(Symbol::from), dep_override: false, - dep_name: PM::PackageName::from(name.as_str()), + dep_name: PM::PackageName::from(dep_name), }, ); } @@ -1106,30 +1118,30 @@ impl DependencyGraph { // Fill in the remaining dependencies, and the package source information from the lock // file. for schema::Package { - name: pkg_name, + id: pkg_id, source, version, dependencies, dev_dependencies, } in packages.packages.into_iter().flatten() { - let pkg_name = PM::PackageName::from(pkg_name.as_str()); - let source = parse_dependency(pkg_name.as_str(), source) - .with_context(|| format!("Deserializing dependency '{pkg_name}'"))?; + let pkg_id = PackageIdentifier::from(pkg_id.as_str()); + let source = parse_dependency(source) + .with_context(|| format!("Deserializing dependency '{pkg_id}'"))?; let source = match source { PM::Dependency::Internal(source) => source, PM::Dependency::External(resolver) => { - bail!("Unexpected dependency '{pkg_name}' resolved externally by '{resolver}'"); + bail!("Unexpected dependency '{pkg_id}' resolved externally by '{resolver}'"); } }; if source.subst.is_some() { - bail!("Unexpected 'addr_subst' in source for '{pkg_name}'") + bail!("Unexpected 'addr_subst' in source for '{pkg_id}'") } if source.digest.is_some() { - bail!("Unexpected 'digest' in source for '{pkg_name}'") + bail!("Unexpected 'digest' in source for '{pkg_id}'") } let pkg = Package { @@ -1138,7 +1150,7 @@ impl DependencyGraph { version: version.map(Symbol::from), }; - match package_table.entry(pkg_name) { + match package_table.entry(pkg_id) { Entry::Vacant(entry) => { entry.insert(pkg); } @@ -1148,7 +1160,7 @@ impl DependencyGraph { Entry::Occupied(entry) => { bail!( "Conflicting dependencies found:\n{0} = {1}\n{0} = {2}", - pkg_name, + pkg_id, PackageWithResolverTOML(entry.get()), PackageWithResolverTOML(&pkg), ); @@ -1156,14 +1168,15 @@ impl DependencyGraph { }; for schema::Dependency { + id: dep_id, name: dep_name, subst, digest, } in dependencies.into_iter().flatten() { package_graph.add_edge( - pkg_name, - PM::PackageName::from(dep_name.as_str()), + pkg_id, + PackageIdentifier::from(dep_id.as_str()), Dependency { mode: DependencyMode::Always, subst: subst.map(parse_substitution).transpose()?, @@ -1175,14 +1188,15 @@ impl DependencyGraph { } for schema::Dependency { + id: dep_id, name: dep_name, subst, digest, } in dev_dependencies.into_iter().flatten() { package_graph.add_edge( - pkg_name, - PM::PackageName::from(dep_name.as_str()), + pkg_id, + PackageIdentifier::from(dep_id.as_str()), Dependency { mode: DependencyMode::DevOnly, subst: subst.map(parse_substitution).transpose()?, @@ -1232,7 +1246,7 @@ impl DependencyGraph { for (id, pkg) in &self.package_table { writeln!(writer, "\n[[move.package]]")?; - writeln!(writer, "name = {}", str_escape(id.as_str())?)?; + writeln!(writer, "id = {}", str_escape(id.as_str())?)?; writeln!(writer, "source = {}", PackageTOML(pkg))?; if let Some(version) = &pkg.version { writeln!(writer, "version = {}", str_escape(version.as_str())?)?; @@ -1350,13 +1364,15 @@ impl DependencyGraph { .map(|(_, dep_name, dep)| (dep_name, dep, &self.package_table[&dep_name])) } - /// Resolves the packages described at dependency `to` of package `from` with manifest at path - /// `package_path` by running the binary `resolver. `mode` decides whether the resulting - /// packages are added to `self` as dependencies of `package_name` or dev-dependencies. + /// Resolves the packages described at dependency `to` the dependency specified by`from` + /// with manifest at path `package_path` by running the binary `resolver`. `mode` + /// decides whether the resulting packages are added to `self` as dependencies of + /// `package_name` or dev-dependencies. /// /// Sends progress updates to `progress_output`, including stderr from the resolver, and - /// captures stdout, which is assumed to be a lock file containing the result of package - /// resolution. + /// captures stdout. The output is expected to be one or more null-separated string content. + /// Each string is interpreted as a dependency graph, as represented by the Move.lock TOML + /// schema. It returns each subgraph to be merged into the whole program dependency graph. fn get_external( mode: DependencyMode, from_id: PackageIdentifier, @@ -1365,7 +1381,14 @@ impl DependencyGraph { resolver: Symbol, package_path: &Path, progress_output: &mut Progress, - ) -> Result { + ) -> Result< + Vec<( + DependencyGraph, + PM::Dependency, + PM::PackageName, + Option, // version + )>, + > { let mode_label = if mode == DependencyMode::DevOnly { "dev-dependencies" } else { @@ -1384,6 +1407,7 @@ impl DependencyGraph { )?; // Call out to the external resolver + // TODO(optimization): this will collect all stdout in memory, but can be streamed instead. let output = Command::new(resolver.as_str()) .arg(format!("--resolve-move-{mode_label}")) .arg(to_name.as_str()) @@ -1411,20 +1435,59 @@ impl DependencyGraph { } } - let sub_graph = DependencyGraph::read_from_lock( - package_path.to_path_buf(), - from_id, - from_name, - &mut output.stdout.as_slice(), - Some(resolver), - ) - .with_context(|| { - format!( - "Parsing response from '{resolver}' for dependency '{to_name}' of package '{from_id}'" - ) - })?; + let mut result = Vec::new(); + let mut reader = BufReader::new(output.stdout.as_slice()); + let mut buffer = Vec::new(); + // Loop over null-separated lock file contents, creating the graph and adding it to the result. + loop { + match reader.read_until(0, &mut buffer) { + Ok(0) => break, // EOF + Ok(_) => { + // Remove the null byte if it's present + if buffer.last() == Some(&0) { + buffer.pop(); + } + + let sub_graph = DependencyGraph::read_from_lock( + package_path.to_path_buf(), + from_id, + from_name, + &mut buffer.as_slice(), + Some(resolver), + ).with_context(|| { + format!("Parsing response from '{resolver}' for dependency '{to_name}' of package '{from_id}'") + })?; + + let root_sub_package_id = match sub_graph + .package_graph + .edges(from_id) + .collect::>() + .as_slice() + { + [(_, id, _)] => *id, + // TODO: We can in fact allow allow multiple root packages / graphs and relax this constraint. + _ => bail!("Expected a single root dependency but none or multiple found"), + }; + let root_sub_package_version = sub_graph + .package_table + .get(&root_sub_package_id) + .unwrap() + .version; + + let new_dep = PM::Dependency::External(root_sub_package_id); + result.push(( + sub_graph, + new_dep, + root_sub_package_id, + root_sub_package_version, + )); + buffer.clear(); + } + Err(e) => return Err(e.into()), + } + } - Ok(sub_graph) + Ok(result) } /// Checks that every dependency in the graph, excluding the root package, is present in the @@ -1518,23 +1581,9 @@ impl fmt::Display for Package { f.write_str(&path_escape(subdir)?)?; } - PM::DependencyKind::Custom(PM::CustomDepInfo { - node_url, - package_address, - subdir, - package_name: _, - }) => { - let custom_key = package_hooks::custom_dependency_key().ok_or(fmt::Error)?; - - f.write_str(&custom_key)?; - write!(f, " = ")?; - f.write_str(&str_escape(node_url.as_str())?)?; - - write!(f, ", address = ")?; - f.write_str(&str_escape(package_address.as_str())?)?; - - write!(f, ", subdir = ")?; - f.write_str(&path_escape(subdir)?)?; + PM::DependencyKind::OnChain(PM::OnChainInfo { id }) => { + write!(f, "id = ")?; + f.write_str(&str_escape(id.as_str())?)?; } } @@ -1566,20 +1615,23 @@ impl<'a> fmt::Display for PackageWithResolverTOML<'a> { impl<'a> fmt::Display for DependencyTOML<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let DependencyTOML( - name, + id, Dependency { mode: _, subst, digest, dep_override: _, - dep_name: _, + dep_name, }, ) = self; f.write_str("{ ")?; - write!(f, "name = ")?; - f.write_str(&str_escape(name.as_str())?)?; + write!(f, "id = ")?; + f.write_str(&str_escape(id.as_str())?)?; + + write!(f, ", name = ")?; + f.write_str(&str_escape(dep_name.as_str())?)?; if let Some(digest) = digest { write!(f, ", digest = ")?; diff --git a/external-crates/move/crates/move-package/src/resolution/mod.rs b/external-crates/move/crates/move-package/src/resolution/mod.rs index 2fbd3959e7850..e0932294e2209 100644 --- a/external-crates/move/crates/move-package/src/resolution/mod.rs +++ b/external-crates/move/crates/move-package/src/resolution/mod.rs @@ -10,7 +10,7 @@ use std::{ }; use crate::{ - source_package::parsed_manifest::{CustomDepInfo, DependencyKind, GitInfo}, + source_package::parsed_manifest::{DependencyKind, GitInfo, OnChainInfo}, BuildConfig, }; @@ -93,23 +93,12 @@ fn repository_path(kind: &DependencyKind) -> PathBuf { .iter() .collect(), - // Downloaded packages are of the form _
_ - DependencyKind::Custom(CustomDepInfo { - node_url, - package_address, - package_name, - subdir: _, - }) => [ - &*MOVE_HOME, - &format!( - "{}_{}_{}", - url_to_file_name(node_url.as_str()), - package_address.as_str(), - package_name.as_str(), - ), - ] - .iter() - .collect(), + // Downloaded packages are of the form + DependencyKind::OnChain(OnChainInfo { id }) => { + [&*MOVE_HOME, &url_to_file_name(id.as_str()).to_string()] + .iter() + .collect() + } } } @@ -117,9 +106,7 @@ fn repository_path(kind: &DependencyKind) -> PathBuf { fn local_path(kind: &DependencyKind) -> PathBuf { let mut repo_path = repository_path(kind); - if let DependencyKind::Git(GitInfo { subdir, .. }) - | DependencyKind::Custom(CustomDepInfo { subdir, .. }) = kind - { + if let DependencyKind::Git(GitInfo { subdir, .. }) = kind { repo_path.push(subdir); } diff --git a/external-crates/move/crates/move-package/src/resolution/resolution_graph.rs b/external-crates/move/crates/move-package/src/resolution/resolution_graph.rs index f8979af95857a..d6aa0ed8b2f4f 100644 --- a/external-crates/move/crates/move-package/src/resolution/resolution_graph.rs +++ b/external-crates/move/crates/move-package/src/resolution/resolution_graph.rs @@ -125,7 +125,7 @@ impl ResolvedGraph { match dep { PM::Dependency::External(_) => continue, PM::Dependency::Internal(internal) => { - if let PM::DependencyKind::Custom(_) = internal.kind { + if let PM::DependencyKind::OnChain(_) = internal.kind { continue; } let dep_path = &resolved_pkg.package_path.join(local_path(&internal.kind)); diff --git a/external-crates/move/crates/move-package/src/resolution/resolving_table.rs b/external-crates/move/crates/move-package/src/resolution/resolving_table.rs index 2b82f0450c2e8..daa6a793460d1 100644 --- a/external-crates/move/crates/move-package/src/resolution/resolving_table.rs +++ b/external-crates/move/crates/move-package/src/resolution/resolving_table.rs @@ -30,6 +30,12 @@ enum Assignment { Linked(usize), } +impl Default for ResolvingTable { + fn default() -> Self { + Self::new() + } +} + impl ResolvingTable { /// A fresh `ResolvingTable` with no bindings. pub fn new() -> ResolvingTable { diff --git a/external-crates/move/crates/move-package/src/source_package/manifest_parser.rs b/external-crates/move/crates/move-package/src/source_package/manifest_parser.rs index d2ff820047089..04848ee650d2c 100644 --- a/external-crates/move/crates/move-package/src/source_package/manifest_parser.rs +++ b/external-crates/move/crates/move-package/src/source_package/manifest_parser.rs @@ -25,6 +25,8 @@ const DEV_ADDRESSES_NAME: &str = "dev-addresses"; const DEPENDENCY_NAME: &str = "dependencies"; const DEV_DEPENDENCY_NAME: &str = "dev-dependencies"; +const EXTERNAL_RESOLVER_PREFIX: &str = "r"; + const KNOWN_NAMES: &[&str] = &[ PACKAGE_NAME, BUILD_NAME, @@ -32,6 +34,7 @@ const KNOWN_NAMES: &[&str] = &[ DEV_ADDRESSES_NAME, DEPENDENCY_NAME, DEV_DEPENDENCY_NAME, + EXTERNAL_RESOLVER_PREFIX, ]; const REQUIRED_FIELDS: &[&str] = &[PACKAGE_NAME]; @@ -206,7 +209,7 @@ pub fn parse_dependencies(tval: TV) -> Result { let mut deps = BTreeMap::new(); for (dep_name, dep) in table.into_iter() { let dep_name_ident = PM::PackageName::from(dep_name.clone()); - let dep = parse_dependency(&dep_name, dep)?; + let dep = parse_dependency(dep)?; deps.insert(dep_name_ident, dep); } Ok(deps) @@ -329,27 +332,43 @@ fn parse_address_literal(address_str: &str) -> Result Result { - let Some(table) = tval.as_table_mut() else { - bail!("Malformed dependency {}", tval); +fn parse_external_resolver_name(resolver_val: &TV) -> Result> { + let Some(table) = resolver_val.as_table() else { + bail!("Malformed dependency {}", resolver_val); }; - if let Some(resolver) = table.remove("resolver") { - let Some(resolver) = resolver.as_str().map(Symbol::from) else { - bail!("Resolver name is not a string") - }; + if table.len() != 1 { + bail!("Malformed external resolver declaration for dependency {EXTERNAL_RESOLVER_PREFIX}.{resolver_val}",); + } - // Not relevant except for the external resolver, but remove it to mark it as a - // recognised part of the manifest. - let _ = table.remove("packages"); + let key = table + .keys() + .next() + .expect("Exactly one key by check above") + .as_str(); - // Any fields that are left are unknown - warn_if_unknown_field_names(table, &[]); + let key_value = table.get(key).ok_or_else(|| { + format_err!("Malformed external resolver declaration for dependency {EXTERNAL_RESOLVER_PREFIX}.{resolver_val}",) + })?; - return Ok(PM::Dependency::External(resolver)); + if !key_value.is_str() { + bail!("Malformed external resolver declaration for dependency {EXTERNAL_RESOLVER_PREFIX}.{resolver_val}",); } - let custom_key_opt = &package_hooks::custom_dependency_key(); + Ok(Some(Symbol::from(key))) +} + +pub fn parse_dependency(mut tval: TV) -> Result { + let Some(table) = tval.as_table_mut() else { + bail!("Malformed dependency {}", tval); + }; + + if let Some(external_resolver_binary_name) = table + .get(EXTERNAL_RESOLVER_PREFIX) + .and_then(|e| parse_external_resolver_name(e).transpose()) + { + return Ok(PM::Dependency::External(external_resolver_binary_name?)); + } let subst = table .remove("addr_subst") @@ -366,7 +385,7 @@ pub fn parse_dependency(dep_name: &str, mut tval: TV) -> Result table.remove("local"), table.remove("subdir"), table.remove("git"), - custom_key_opt.as_ref().and_then(|k| table.remove(k)), + table.remove("id"), ) { (Some(local), subdir, None, None) => { if subdir.is_some() { @@ -411,43 +430,16 @@ pub fn parse_dependency(dep_name: &str, mut tval: TV) -> Result }) } - (None, subdir, None, Some(custom_key)) => { - let Some(package_address) = table.remove("address") else { - bail!("Address not supplied for 'node' dependency"); - }; - - let Some(package_address) = package_address.as_str().map(Symbol::from) else { - bail!("Node address not a string") - }; - - let Some(node_url) = custom_key.as_str().map(Symbol::from) else { - bail!("Git URL not a string") - }; - - let subdir = match subdir { - None => PathBuf::new(), - Some(path) => path - .as_str() - .map(PathBuf::from) - .ok_or_else(|| anyhow!("'subdir' not a string"))?, + (None, None, None, Some(id)) => { + let Some(id) = id.as_str().map(Symbol::from) else { + bail!("ID not a string") }; - let package_name = Symbol::from(dep_name); - - PM::DependencyKind::Custom(PM::CustomDepInfo { - node_url, - package_address, - package_name, - subdir, - }) + PM::DependencyKind::OnChain(PM::OnChainInfo { id }) } _ => { - let mut keys = vec!["'local'", "'git'", "'resolver'"]; - let quoted_custom_key = custom_key_opt.as_ref().map(|k| format!("'{}'", k)); - if let Some(k) = "ed_custom_key { - keys.push(k.as_str()) - } + let keys = ["'local'", "'git'", "'r.'"]; bail!( "must provide exactly one of {} for dependency.", keys.join(" or ") @@ -535,7 +527,7 @@ fn parse_dep_override(tval: TV) -> Result { Ok(tval.as_bool().unwrap()) } -// check that only recognized names are provided at the top-level +// Check that only recognized names are provided at the top-level. fn warn_if_unknown_field_names(table: &toml::map::Map, known_names: &[&str]) { let mut unknown_names = BTreeSet::new(); for key in table.keys() { diff --git a/external-crates/move/crates/move-package/src/source_package/parsed_manifest.rs b/external-crates/move/crates/move-package/src/source_package/parsed_manifest.rs index bdbe1a8581a89..e99de21fc7f6b 100644 --- a/external-crates/move/crates/move-package/src/source_package/parsed_manifest.rs +++ b/external-crates/move/crates/move-package/src/source_package/parsed_manifest.rs @@ -63,7 +63,7 @@ pub struct InternalDependency { pub enum DependencyKind { Local(PathBuf), Git(GitInfo), - Custom(CustomDepInfo), + OnChain(OnChainInfo), } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] @@ -78,16 +78,8 @@ pub struct GitInfo { } #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] -pub struct CustomDepInfo { - /// The url of the node to download from - pub node_url: Symbol, - /// The address where the package is published. The representation depends - /// on the registered node resolver. - pub package_address: Symbol, - /// The package's name (i.e. the dependency name). - pub package_name: Symbol, - /// The path under this repo where the move package can be found - pub subdir: PathBuf, +pub struct OnChainInfo { + pub id: Symbol, } #[derive(Default, Debug, Clone, Eq, PartialEq)] @@ -116,7 +108,7 @@ impl DependencyKind { // If `self` is a git or custom dependency kind, it does not need to be re-rooted // because its URI is already absolute. (i.e. the location of an absolute URI does not // change if referenced relative to some other URI). - (_, DependencyKind::Git(_) | DependencyKind::Custom(_)) => return Ok(()), + (_, DependencyKind::Git(_) | DependencyKind::OnChain(_)) => return Ok(()), (DependencyKind::Local(parent), DependencyKind::Local(subdir)) => { parent.push(subdir); @@ -128,10 +120,7 @@ impl DependencyKind { git.subdir = normalize_path(&git.subdir, /* allow_cwd_parent */ false)?; } - (DependencyKind::Custom(custom), DependencyKind::Local(subdir)) => { - custom.subdir.push(subdir); - custom.subdir = normalize_path(&custom.subdir, /* allow_cwd_parent */ false)?; - } + (DependencyKind::OnChain(_), _) => return Ok(()), }; *self = parent; diff --git a/external-crates/move/crates/move-package/tests/test_dependency_graph.rs b/external-crates/move/crates/move-package/tests/test_dependency_graph.rs index ebcab116022dd..b648942a1cbfe 100644 --- a/external-crates/move/crates/move-package/tests/test_dependency_graph.rs +++ b/external-crates/move/crates/move-package/tests/test_dependency_graph.rs @@ -124,7 +124,11 @@ fn lock_file_missing_dependency() { .expect("Creating new lock file"); // Write a reference to a dependency that there isn't package information for. - writeln!(&*lock, r#"dependencies = [{{ name = "OtherDep" }}]"#).unwrap(); + writeln!( + &*lock, + r#"dependencies = [{{ id = "OtherDep", name = "OtherDep" }}]"# + ) + .unwrap(); lock.commit(&commit).expect("Writing partial lock file"); let Err(err) = DependencyGraph::read_from_lock( @@ -619,61 +623,61 @@ fn dev_dep_test_package() -> PathBuf { const EMPTY_LOCK: &str = r#" [move] -version = 0 +version = 3 manifest_digest = "42" deps_digest = "" "#; const A_LOCK: &str = r#" [move] -version = 0 +version = 3 manifest_digest = "42" deps_digest = "7" dependencies = [ - { name = "A" }, + { id = "A", name = "A" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "./A" } "#; const AB_LOCK: &str = r#" [move] -version = 0 +version = 3 manifest_digest = "42" deps_digest = "7" dependencies = [ - { name = "A" }, - { name = "B" }, + { id = "A", name = "A" }, + { id = "B", name = "A" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "./A" } [[move.package]] -name = "B" +id = "B" source = { local = "./B" } "#; const A_DEP_B_LOCK: &str = r#" [move] -version = 0 +version = 3 manifest_digest = "42" deps_digest = "7" dependencies = [ - { name = "A" }, + { id = "A", name = "A" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "./A" } dependencies = [ - { name = "B" }, + { id = "B", name = "A" }, ] [[move.package]] -name = "B" +id = "B" source = { local = "./B" } "#; diff --git a/external-crates/move/crates/move-package/tests/test_lock_file.rs b/external-crates/move/crates/move-package/tests/test_lock_file.rs index 452ecbeaff372..60d62af3f022b 100644 --- a/external-crates/move/crates/move-package/tests/test_lock_file.rs +++ b/external-crates/move/crates/move-package/tests/test_lock_file.rs @@ -96,16 +96,16 @@ flavor = "sui" # @generated by Move, please check-in and do not edit manually. [move] - version = 1 + version = 3 manifest_digest = "0" deps_digest = "0" dependencies = [ - { name = "Dep" } + { id = "Dep", name = "Dep" } ] [[move.package]] - name = "Dep" + id = "Dep" source = { local = "some/path" } "#; fs::write(lock_path_with_graph.clone(), lock_graph_contents).unwrap(); @@ -135,15 +135,15 @@ flavor = "sui" # @generated by Move, please check-in and do not edit manually. [move] - version = 1 + version = 3 manifest_digest = "0" deps_digest = "0" dependencies = [ - { name = "Dep" }, + { id = "Dep", name = "Dep" }, ] [[move.package]] - name = "Dep" + id = "Dep" source = { local = "some/path" } [move.toolchain-version] diff --git a/external-crates/move/crates/move-package/tests/test_runner.rs b/external-crates/move/crates/move-package/tests/test_runner.rs index f837b05b89ef5..ebac97bd71934 100644 --- a/external-crates/move/crates/move-package/tests/test_runner.rs +++ b/external-crates/move/crates/move-package/tests/test_runner.rs @@ -14,7 +14,7 @@ use move_package::{ package_hooks::PackageHooks, package_hooks::PackageIdentifier, resolution::resolution_graph::Package, - source_package::parsed_manifest::{CustomDepInfo, PackageDigest, SourceManifest}, + source_package::parsed_manifest::{OnChainInfo, PackageDigest, SourceManifest}, BuildConfig, ModelConfig, }; use move_symbol_pool::Symbol; @@ -203,23 +203,12 @@ impl PackageHooks for TestHooks { vec!["test_hooks_field".to_owned(), "version".to_owned()] } - fn custom_dependency_key(&self) -> Option { - Some("custom".to_owned()) - } - - fn resolve_custom_dependency( + fn resolve_on_chain_dependency( &self, dep_name: Symbol, - info: &CustomDepInfo, + info: &OnChainInfo, ) -> anyhow::Result<()> { - bail!( - "TestHooks resolve dep {:?} = {:?} {:?} {:?} {:?}", - dep_name, - info.node_url, - info.package_name, - info.package_address, - info.subdir.to_string_lossy(), - ) + bail!("TestHooks resolve dep {:?} = {:?}", dep_name, info.id,) } fn custom_resolve_pkg_id( diff --git a/external-crates/move/crates/move-package/tests/test_sources/basic_no_deps/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/basic_no_deps/Move.locked index f9e4c5cb2c1f7..0532d57b75984 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/basic_no_deps/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/basic_no_deps/Move.locked @@ -1,6 +1,6 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "919A5B078B47AD46674F36E1605578927D5BC4536A7646D78D1320A25DDD57CC" deps_digest = "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855" diff --git a/external-crates/move/crates/move-package/tests/test_sources/basic_no_deps_address_assigned/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/basic_no_deps_address_assigned/Move.locked index 53a4e43bc8db6..1f5b1eca3f5fd 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/basic_no_deps_address_assigned/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/basic_no_deps_address_assigned/Move.locked @@ -1,6 +1,6 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "151286C56FC37AEF93D980A892F558C8EE65FBF062991BDF23C9FC88478D3648" deps_digest = "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855" diff --git a/external-crates/move/crates/move-package/tests/test_sources/dep_dev_dep_diamond/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/dep_dev_dep_diamond/Move.locked index c3237740cfe1a..ef3a0e9ee9c3d 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/dep_dev_dep_diamond/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/dep_dev_dep_diamond/Move.locked @@ -1,41 +1,41 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "98BBCE8D1C29472825E598691218A0CBC5BDA1A56C4429F5C2311C245DEC28CE" deps_digest = "060AD7E57DFB13104F21BE5F5C3759D03F0553FC3229247D9A7A6B45F50D03A3" dependencies = [ - { name = "A" }, - { name = "C" }, + { id = "A", name = "A" }, + { id = "C", name = "C" }, ] dev-dependencies = [ - { name = "B" }, + { id = "B", name = "B" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } dependencies = [ - { name = "B" }, + { id = "B", name = "B" }, ] dev-dependencies = [ - { name = "D" }, + { id = "D", name = "D" }, ] [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B" } dev-dependencies = [ - { name = "C" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "C" +id = "C" source = { local = "deps_only/C" } [[move.package]] -name = "D" +id = "D" source = { local = "deps_only/D" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/dep_good_digest/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/dep_good_digest/Move.locked index be3f6885701eb..0d99c2ea197bd 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/dep_good_digest/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/dep_good_digest/Move.locked @@ -1,13 +1,13 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "A4DB860CC2BC78C04706A7383CA52121876F87057538DF814C66F7EE6025E644" deps_digest = "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082" dependencies = [ - { name = "OtherDep", digest = "A666AE1AFDBA7E287476E851C0011503D19EE73E89CBDB307D4E0067E8BBA22C", addr_subst = { "A" = "B" } }, + { id = "OtherDep", name = "OtherDep", digest = "A666AE1AFDBA7E287476E851C0011503D19EE73E89CBDB307D4E0067E8BBA22C", addr_subst = { "A" = "B" } }, ] [[move.package]] -name = "OtherDep" +id = "OtherDep" source = { local = "deps_only/other_dep" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_dev_override_with_reg/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_dev_override_with_reg/Move.locked index 42c70993e3498..b0bbb4fd01a3b 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_dev_override_with_reg/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_dev_override_with_reg/Move.locked @@ -1,33 +1,33 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "CB01A8B6F9859E70A0A5DA10F8547C13EDDD63E9EDF72E930DD37C8EFC41F3F3" deps_digest = "060AD7E57DFB13104F21BE5F5C3759D03F0553FC3229247D9A7A6B45F50D03A3" dependencies = [ - { name = "C" }, + { id = "C", name = "C" }, ] dev-dependencies = [ - { name = "A" }, - { name = "B" }, + { id = "A", name = "A" }, + { id = "B", name = "B" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } dev-dependencies = [ - { name = "C" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B" } dev-dependencies = [ - { name = "C" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "C" +id = "C" source = { local = "deps_only/C-v1" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_conflict/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_conflict/Move.toml index 057b0f70d4bec..5e1b47bd85ee8 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_conflict/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_conflict/Move.toml @@ -3,10 +3,4 @@ name = "Root" [dependencies] B = { local = "./deps_only/B" } - -[dependencies.A] -resolver = "../resolvers/successful.sh" - -[dependencies.A.packages] -Contains = "Anything" -Has = { No = "Schema" } +A = { r."../resolvers/successful.sh" = "A" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_incorrect_override_v1/deps_only/C/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_incorrect_override_v1/deps_only/C/Move.toml index 0778ff1464108..84247e7fb0c1c 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_incorrect_override_v1/deps_only/C/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_incorrect_override_v1/deps_only/C/Move.toml @@ -1,9 +1,5 @@ [package] name = "C" -[dependencies.A] -resolver = "../../../resolvers/successful.sh" - -[dependencies.A.packages] -Contains = "Anything" -Has = { No = "Schema" } +[dependencies] +A = { r."../../../resolvers/successful.sh" = "A" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_incorrect_override_v2/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_incorrect_override_v2/Move.toml index 4db3ca63a2725..d006f450b4006 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_incorrect_override_v2/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_incorrect_override_v2/Move.toml @@ -18,10 +18,4 @@ name = "Root" [dependencies] B = { local = "./deps_only/B" } - -[dependencies.A] -resolver = "../resolvers/successful.sh" - -[dependencies.A.packages] -Contains = "Anything" -Has = { No = "Schema" } +A = {r."../resolvers/successful.sh" = "A" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_no_conflict/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_no_conflict/Move.locked index 4926f3c472087..c4b22862f35db 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_no_conflict/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_no_conflict/Move.locked @@ -1,30 +1,30 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 -manifest_digest = "B06FCED8E0EF6B62EC0B572DC233C0D70206B1C10EEDEA0403CA64AFBB3E439B" +version = 3 +manifest_digest = "1A19D99A36EA0D72B36FC64B2108B42452C99FF87E9DF6E0E54A38AD8679D431" deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" dependencies = [ - { name = "A" }, - { name = "B" }, + { id = "A", name = "A" }, + { id = "B", name = "B" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } dependencies = [ - { name = "ADep" }, + { id = "ADep", name = "ADep" }, ] [[move.package]] -name = "ADep" +id = "ADep" source = { local = "deps_only/ADep" } [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B" } dependencies = [ - { name = "ADep" }, + { id = "ADep", name = "ADep" }, ] diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_no_conflict/Move.resolved b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_no_conflict/Move.resolved index d17a7b94d3ea2..e59c52416df07 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_no_conflict/Move.resolved +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_no_conflict/Move.resolved @@ -78,7 +78,7 @@ ResolvedGraph { "B", "Root", }, - manifest_digest: "B06FCED8E0EF6B62EC0B572DC233C0D70206B1C10EEDEA0403CA64AFBB3E439B", + manifest_digest: "1A19D99A36EA0D72B36FC64B2108B42452C99FF87E9DF6E0E54A38AD8679D431", deps_digest: "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600", }, build_options: BuildConfig { diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_no_conflict/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_no_conflict/Move.toml index 057b0f70d4bec..5e1b47bd85ee8 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_no_conflict/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_no_conflict/Move.toml @@ -3,10 +3,4 @@ name = "Root" [dependencies] B = { local = "./deps_only/B" } - -[dependencies.A] -resolver = "../resolvers/successful.sh" - -[dependencies.A.packages] -Contains = "Anything" -Has = { No = "Schema" } +A = { r."../resolvers/successful.sh" = "A" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override/Move.locked index 7896e12c4aa77..e71addc3e7194 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override/Move.locked @@ -1,31 +1,31 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 -manifest_digest = "82E01E336DD3374BDC42CA355AB6ACA69E44DB65E36DC0A831F36B06F6832574" +version = 3 +manifest_digest = "962A6DC626FF53896265D6D3CECA6A95E461EA4C97B32A8527807F7225BFF183" deps_digest = "060AD7E57DFB13104F21BE5F5C3759D03F0553FC3229247D9A7A6B45F50D03A3" dependencies = [ - { name = "A" }, - { name = "ADep" }, - { name = "B" }, + { id = "A", name = "A" }, + { id = "ADep", name = "ADep" }, + { id = "B", name = "B" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } dependencies = [ - { name = "ADep" }, + { id = "ADep", name = "ADep" }, ] [[move.package]] -name = "ADep" +id = "ADep" source = { local = "deps_only/ADep-v1" } [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B" } dependencies = [ - { name = "ADep" }, + { id = "ADep", name = "ADep" }, ] diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override/Move.resolved b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override/Move.resolved index a2cfba467ba63..0585fff693f3b 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override/Move.resolved +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override/Move.resolved @@ -84,7 +84,7 @@ ResolvedGraph { "B", "Root", }, - manifest_digest: "82E01E336DD3374BDC42CA355AB6ACA69E44DB65E36DC0A831F36B06F6832574", + manifest_digest: "962A6DC626FF53896265D6D3CECA6A95E461EA4C97B32A8527807F7225BFF183", deps_digest: "060AD7E57DFB13104F21BE5F5C3759D03F0553FC3229247D9A7A6B45F50D03A3", }, build_options: BuildConfig { diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override/Move.toml index 030330425bd81..55b8398e5f35e 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override/Move.toml @@ -4,10 +4,4 @@ name = "Root" [dependencies] B = { local = "./deps_only/B" } ADep = { local = "./deps_only/ADep-v1", override = true } - -[dependencies.A] -resolver = "../resolvers/successful.sh" - -[dependencies.A.packages] -Contains = "Anything" -Has = { No = "Schema" } +A = { r."../resolvers/successful.sh" = "A" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override_root/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override_root/Move.locked index 9bdc1d2d2e585..31247485a9693 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override_root/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override_root/Move.locked @@ -1,39 +1,39 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "E1BD09BE802FCCF437672321DEBFD0C8000F9D4A0AC5E54ED432087ABACA9667" deps_digest = "060AD7E57DFB13104F21BE5F5C3759D03F0553FC3229247D9A7A6B45F50D03A3" dependencies = [ - { name = "A" }, - { name = "B" }, - { name = "C" }, + { id = "A", name = "A" }, + { id = "B", name = "B" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A-v1" } dependencies = [ - { name = "ADep" }, + { id = "ADep", name = "ADep" }, ] [[move.package]] -name = "ADep" +id = "ADep" source = { local = "deps_only/ADep" } [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B" } dependencies = [ - { name = "A" }, + { id = "A", name = "A" }, ] [[move.package]] -name = "C" +id = "C" source = { local = "deps_only/C" } dependencies = [ - { name = "A" }, + { id = "A", name = "A" }, ] diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override_root/deps_only/C/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override_root/deps_only/C/Move.toml index 0778ff1464108..84247e7fb0c1c 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override_root/deps_only/C/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_external_override_root/deps_only/C/Move.toml @@ -1,9 +1,5 @@ [package] name = "C" -[dependencies.A] -resolver = "../../../resolvers/successful.sh" - -[dependencies.A.packages] -Contains = "Anything" -Has = { No = "Schema" } +[dependencies] +A = { r."../../../resolvers/successful.sh" = "A" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_nested_dep_conflict/deps_only/C/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_nested_dep_conflict/deps_only/C/Move.toml index 5c8747777c81e..e7c7e31433d80 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_nested_dep_conflict/deps_only/C/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_nested_dep_conflict/deps_only/C/Move.toml @@ -1,9 +1,5 @@ [package] name = "C" -[dependencies.A] -resolver = "../../../resolvers/successful_subst.sh" - -[dependencies.A.packages] -Contains = "Anything" -Has = { No = "Schema" } +[dependencies] +A = { r."../../../resolvers/successful_subst.sh" = "A" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_nested_override/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_nested_override/Move.locked index 31954ffc4c272..57b12d457a03b 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_nested_override/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_nested_override/Move.locked @@ -1,48 +1,48 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "86DBE490660052E70AF19AEB6DB3CDFE90F770D67E008E12C753575AB346B43C" deps_digest = "060AD7E57DFB13104F21BE5F5C3759D03F0553FC3229247D9A7A6B45F50D03A3" dependencies = [ - { name = "A" }, - { name = "B" }, - { name = "E" }, + { id = "A", name = "A" }, + { id = "B", name = "B" }, + { id = "E", name = "E" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } dependencies = [ - { name = "C" }, - { name = "D" }, + { id = "C", name = "C" }, + { id = "D", name = "D" }, ] [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B" } dependencies = [ - { name = "E" }, + { id = "E", name = "E" }, ] [[move.package]] -name = "C" +id = "C" source = { local = "deps_only/C" } dependencies = [ - { name = "E" }, + { id = "E", name = "E" }, ] [[move.package]] -name = "D" +id = "D" source = { local = "deps_only/D" } dependencies = [ - { name = "E" }, + { id = "E", name = "E" }, ] [[move.package]] -name = "E" +id = "E" source = { local = "deps_only/E-v1" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_override/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_override/Move.locked index 203629a53228d..d542969613365 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_override/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_override/Move.locked @@ -1,31 +1,31 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "F64B7E3BA42923C4AFD7490B03D34813BA434044DC83D46F869DD5BB9A6052B2" deps_digest = "060AD7E57DFB13104F21BE5F5C3759D03F0553FC3229247D9A7A6B45F50D03A3" dependencies = [ - { name = "A" }, - { name = "B" }, - { name = "C" }, + { id = "A", name = "A" }, + { id = "B", name = "B" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } dependencies = [ - { name = "C" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B" } dependencies = [ - { name = "C" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "C" +id = "C" source = { local = "deps_only/C-v1" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_transitive_nested_override/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_transitive_nested_override/Move.locked index e99fb2cf04761..edd0baa8b1384 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_transitive_nested_override/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_transitive_nested_override/Move.locked @@ -1,44 +1,44 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "B2F5516D0E0D7FC1D1A91EF42181BC28609979CA8D75F7EF01B473AF22303C1D" deps_digest = "060AD7E57DFB13104F21BE5F5C3759D03F0553FC3229247D9A7A6B45F50D03A3" dependencies = [ - { name = "A" }, - { name = "B" }, - { name = "C" }, + { id = "A", name = "A" }, + { id = "B", name = "B" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } dependencies = [ - { name = "B" }, + { id = "B", name = "B" }, ] [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B1" } [[move.package]] -name = "C" +id = "C" source = { local = "deps_only/C" } dependencies = [ - { name = "D" }, - { name = "E" }, + { id = "D", name = "D" }, + { id = "E", name = "E" }, ] [[move.package]] -name = "D" +id = "D" source = { local = "deps_only/D-v3" } [[move.package]] -name = "E" +id = "E" source = { local = "deps_only/E" } dependencies = [ - { name = "D" }, + { id = "D", name = "D" }, ] diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_two_nested_overrides/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_two_nested_overrides/Move.locked index ed33e57cb7ba7..25fd4a8e570db 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_two_nested_overrides/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_dep_two_nested_overrides/Move.locked @@ -1,49 +1,49 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "88451CA3B87F330C2224714E02829B02787D5AEA4F9CCD9FF239ED0344CF0632" deps_digest = "060AD7E57DFB13104F21BE5F5C3759D03F0553FC3229247D9A7A6B45F50D03A3" dependencies = [ - { name = "A" }, - { name = "B" }, - { name = "E" }, + { id = "A", name = "A" }, + { id = "B", name = "B" }, + { id = "E", name = "E" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } dependencies = [ - { name = "C" }, - { name = "D" }, - { name = "E" }, + { id = "C", name = "C" }, + { id = "D", name = "D" }, + { id = "E", name = "E" }, ] [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B" } dependencies = [ - { name = "E" }, + { id = "E", name = "E" }, ] [[move.package]] -name = "C" +id = "C" source = { local = "deps_only/C" } dependencies = [ - { name = "E" }, + { id = "E", name = "E" }, ] [[move.package]] -name = "D" +id = "D" source = { local = "deps_only/D" } dependencies = [ - { name = "E" }, + { id = "E", name = "E" }, ] [[move.package]] -name = "E" +id = "E" source = { local = "deps_only/E-v1" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_no_conflict/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_no_conflict/Move.locked index 50e7097ccae2f..5179dcebc035c 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_no_conflict/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/diamond_problem_no_conflict/Move.locked @@ -1,30 +1,30 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "2CE4CA7B1785FEAE60C59A993DB1182E09DB665E694C6104DF566E065752C030" deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" dependencies = [ - { name = "A", addr_subst = { "AA" = "0000000000000000000000000000000000000000000000000000000000000001" } }, - { name = "B", addr_subst = { "BA" = "0000000000000000000000000000000000000000000000000000000000000001" } }, + { id = "A", name = "A", addr_subst = { "AA" = "0000000000000000000000000000000000000000000000000000000000000001" } }, + { id = "B", name = "B", addr_subst = { "BA" = "0000000000000000000000000000000000000000000000000000000000000001" } }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } dependencies = [ - { name = "C", addr_subst = { "AA" = "A" } }, + { id = "C", name = "C", addr_subst = { "AA" = "A" } }, ] [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B" } dependencies = [ - { name = "C", addr_subst = { "BA" = "A" } }, + { id = "C", name = "C", addr_subst = { "BA" = "A" } }, ] [[move.package]] -name = "C" +id = "C" source = { local = "deps_only/C" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/direct_and_indirect_dep/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/direct_and_indirect_dep/Move.locked index 13413683eb260..26bf01775d595 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/direct_and_indirect_dep/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/direct_and_indirect_dep/Move.locked @@ -1,30 +1,30 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "373B3598A66D05FBD7DE398D2F588C9C79C9F38757140E8717C1D851CBC5C15F" deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" dependencies = [ - { name = "A" }, - { name = "B" }, + { id = "A", name = "A" }, + { id = "B", name = "B" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } dependencies = [ - { name = "C" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B" } dependencies = [ - { name = "A" }, + { id = "A", name = "A" }, ] [[move.package]] -name = "C" +id = "C" source = { local = "deps_only/C" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/external/Move.locked index 07bcc6f99d159..9f5f2fccc4b0f 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/external/Move.locked @@ -1,21 +1,21 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 -manifest_digest = "8D09D19521F36950C0698F14ED09FE4F6175C022796F1E401F2F5A1BCA6FCE98" +version = 3 +manifest_digest = "0B6E0EF231AAF334D40D1FE28680D7A9B8939FCEF885743368FAF858533CD0E2" deps_digest = "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082" dependencies = [ - { name = "A" }, + { id = "A", name = "A" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } dependencies = [ - { name = "ADep" }, + { id = "ADep", name = "ADep" }, ] [[move.package]] -name = "ADep" +id = "ADep" source = { local = "deps_only/ADep" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external/Move.resolved b/external-crates/move/crates/move-package/tests/test_sources/external/Move.resolved index 8df7c4efb4cd6..0237bb21e49b2 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external/Move.resolved +++ b/external-crates/move/crates/move-package/tests/test_sources/external/Move.resolved @@ -52,7 +52,7 @@ ResolvedGraph { "ADep", "Root", }, - manifest_digest: "8D09D19521F36950C0698F14ED09FE4F6175C022796F1E401F2F5A1BCA6FCE98", + manifest_digest: "0B6E0EF231AAF334D40D1FE28680D7A9B8939FCEF885743368FAF858533CD0E2", deps_digest: "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082", }, build_options: BuildConfig { diff --git a/external-crates/move/crates/move-package/tests/test_sources/external/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/external/Move.toml index 4f483386be05e..81642bf65ef8e 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/external/Move.toml @@ -1,9 +1,5 @@ [package] name = "Root" -[dependencies.A] -resolver = "../resolvers/successful.sh" - -[dependencies.A.packages] -Contains = "Anything" -Has = { No = "Schema" } +[dependencies] +A = { r."../resolvers/successful.sh" = "A" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_bad_dep/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/external_bad_dep/Move.locked index 78482d3c9158d..59d149a9ef852 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external_bad_dep/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/external_bad_dep/Move.locked @@ -1,21 +1,21 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 -manifest_digest = "725168ABE1A1677C13C020BCA80D7C059B53A9C7E2E4D15FD0BE671E0D2A87B0" +version = 3 +manifest_digest = "6CC399FE60217A66068CDC5CCDBB3518CC81C965A4B093EEA0C8FFC55B77D8DA" deps_digest = "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082" dependencies = [ - { name = "A" }, + { id = "A", name = "A" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } dependencies = [ - { name = "ADep" }, + { id = "ADep", name = "ADep" }, ] [[move.package]] -name = "ADep" +id = "ADep" source = { local = "deps_only/ADep" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_bad_dep/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/external_bad_dep/Move.toml index b6df3e40a035c..f380f8035d32c 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external_bad_dep/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/external_bad_dep/Move.toml @@ -1,7 +1,7 @@ [package] name = "Root" -[dependencies.A] +[dependencies] # The resolver will introduce Root -> A -> ADep into the dependency # graph, but ADep doesn't exist, so it will fail. -resolver = "../resolvers/successful.sh" +A = { r."../resolvers/successful.sh" = "A" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_broken/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/external_broken/Move.toml index b328da1ae0087..5373156df9fe5 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external_broken/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/external_broken/Move.toml @@ -1,7 +1,7 @@ [package] name = "Root" -[dependencies.A] # This resolver exits with success but returns bad output (not a lock # file), so resolution fails. -resolver = "../resolvers/broken.sh" +[dependencies] +A = {r."../resolvers/broken.sh" = "a" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_dev_dep/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/external_dev_dep/Move.locked index f685ef0302c09..044f15ff8aeac 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external_dev_dep/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/external_dev_dep/Move.locked @@ -1,36 +1,36 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 -manifest_digest = "84A0B503BE9F9B341AC66860713D12704876E5C2425891E3B94626B12E4313E5" +version = 3 +manifest_digest = "A0EB64199385F1312BF72CA00B135B4C6CB0531F538FFCF9156C6D96FBE10F39" deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" dependencies = [ - { name = "A" }, + { id = "A", name = "A" }, ] dev-dependencies = [ - { name = "B" }, + { id = "B", name = "B" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } dependencies = [ - { name = "ADep" }, + { id = "ADep", name = "ADep" }, ] [[move.package]] -name = "ADep" +id = "ADep" source = { local = "deps_only/ADep" } [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B" } dependencies = [ - { name = "BDep" }, + { id = "BDep", name = "BDep" }, ] [[move.package]] -name = "BDep" +id = "BDep" source = { local = "deps_only/BDep" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_dev_dep/Move.resolved b/external-crates/move/crates/move-package/tests/test_sources/external_dev_dep/Move.resolved index 4052c2f5bfbdf..aaa0fc6dc3e34 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external_dev_dep/Move.resolved +++ b/external-crates/move/crates/move-package/tests/test_sources/external_dev_dep/Move.resolved @@ -90,7 +90,7 @@ ResolvedGraph { "ADep", "Root", }, - manifest_digest: "84A0B503BE9F9B341AC66860713D12704876E5C2425891E3B94626B12E4313E5", + manifest_digest: "A0EB64199385F1312BF72CA00B135B4C6CB0531F538FFCF9156C6D96FBE10F39", deps_digest: "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600", }, build_options: BuildConfig { diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_dev_dep/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/external_dev_dep/Move.toml index 11cf3c5d76cd0..1e142c9b643d5 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external_dev_dep/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/external_dev_dep/Move.toml @@ -1,10 +1,10 @@ [package] name = "Root" -[dependencies.A] -resolver = "../resolvers/successful.sh" +[dependencies] +A = { r."../resolvers/successful.sh" = "A" } -[dev-dependencies.B] +[dev-dependencies] # External resolvers can be used for dev dependencies as well as # regular dependencies -resolver = "../resolvers/successful.sh" +B = { r."../resolvers/successful.sh" = "B" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_failing/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/external_failing/Move.toml index 1d9b52f42c4f0..bd7c2528329e0 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external_failing/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/external_failing/Move.toml @@ -1,7 +1,7 @@ [package] name = "Root" -[dependencies.A] +[dependencies] # This resolver returns a non-zero exit code, which should cause # resolution to fail. -resolver = "../resolvers/failing.sh" +A = { r."../resolvers/failing.sh" = "A" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_no_resolver/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/external_no_resolver/Move.toml index 46932071dec4e..ecb31b3fa2966 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external_no_resolver/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/external_no_resolver/Move.toml @@ -1,6 +1,6 @@ [package] name = "Root" -[dependencies.A] # This resolver doesn't exist, so calling out to it will fail. -resolver = "../resolvers/doesnt_exist.sh" +[dependencies] +A = { r."../resolvers/doesnt_exist.sh" = "A" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_overlap/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/external_overlap/Move.locked index 38a5db2e8deb2..df681e9e15073 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external_overlap/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/external_overlap/Move.locked @@ -1,22 +1,22 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 -manifest_digest = "2401A97D2FE979752E0726963BFC3F677A4FA8041F3C6FAE5A4302401B165463" +version = 3 +manifest_digest = "F8724912814B06DCBDAFDA91E456B456D299C39AB59519BA190B6C7523884CFD" deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" dependencies = [ - { name = "A" }, - { name = "ADep" }, + { id = "A", name = "A" }, + { id = "ADep", name = "ADep" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } dependencies = [ - { name = "ADep" }, + { id = "ADep", name = "ADep" }, ] [[move.package]] -name = "ADep" +id = "ADep" source = { local = "deps_only/ADep" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_overlap/Move.resolved b/external-crates/move/crates/move-package/tests/test_sources/external_overlap/Move.resolved index efa2d48f3004b..c8cc30c36935a 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external_overlap/Move.resolved +++ b/external-crates/move/crates/move-package/tests/test_sources/external_overlap/Move.resolved @@ -60,7 +60,7 @@ ResolvedGraph { "ADep", "Root", }, - manifest_digest: "2401A97D2FE979752E0726963BFC3F677A4FA8041F3C6FAE5A4302401B165463", + manifest_digest: "F8724912814B06DCBDAFDA91E456B456D299C39AB59519BA190B6C7523884CFD", deps_digest: "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600", }, build_options: BuildConfig { diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_overlap/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/external_overlap/Move.toml index c95adb14fa674..33362536cb613 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external_overlap/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/external_overlap/Move.toml @@ -5,10 +5,4 @@ name = "Root" # This should succeed even though the external resolver will also return # `ADep` as a transitive dependency as `ADep` has the same dependencies in both cases. ADep = { local = "./deps_only/ADep"} - -[dependencies.A] -resolver = "../resolvers/successful.sh" - -[dependencies.A.packages] -Contains = "Anything" -Has = { No = "Schema" } +A = { r."../resolvers/successful.sh" = "A" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_overlap_fail/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/external_overlap_fail/Move.toml index 19dea389375d4..25aa3a31e3b6e 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external_overlap_fail/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/external_overlap_fail/Move.toml @@ -6,10 +6,4 @@ name = "Root" # and the set of `ADep`s own dependencies is empty in the "external" case and non-empty in the # "internal" one. ADep = { local = "./deps_only/ADep"} - -[dependencies.A] -resolver = "../resolvers/successful.sh" - -[dependencies.A.packages] -Contains = "Anything" -Has = { No = "Schema" } +A = { r."../resolvers/successful.sh" = "A" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_overlap_fail_symmetric/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/external_overlap_fail_symmetric/Move.toml index a809d74b6b729..ee37ac879ff4f 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external_overlap_fail_symmetric/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/external_overlap_fail_symmetric/Move.toml @@ -6,10 +6,4 @@ name = "Root" # `ADep` as a transitive dependency and the set of `ADep`s own # dependencies is different in both cases. ADep = { local = "./deps_only/ADep"} - -[dependencies.A] -resolver = "../resolvers/successful_dep.sh" - -[dependencies.A.packages] -Contains = "Anything" -Has = { No = "Schema" } +A = { r."../resolvers/successful_dep.sh" = "A" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/Move.locked new file mode 100644 index 0000000000000..ee0b78596d3a9 --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/Move.locked @@ -0,0 +1,19 @@ +# @generated by Move, please check-in and do not edit manually. + +[move] +version = 3 +manifest_digest = "C998DF0F473DF6AB3E7B06E0E596FDE9836950D07EDED905464AA1337F176738" +deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" +dependencies = [ + { id = "bar", name = "bar" }, + { id = "foo", name = "foo" }, +] + +[[move.package]] +id = "bar" +source = { local = "deps_only/bar" } +version = "5" + +[[move.package]] +id = "foo" +source = { local = "deps_only/foo" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/Move.progress b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/Move.progress new file mode 100644 index 0000000000000..76b6523ded0b6 --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/Move.progress @@ -0,0 +1,6 @@ +RESOLVING DEPENDENCIES IN Anything FROM Root WITH ../resolvers/successful_package_batch_response.sh +../resolvers/successful_package_batch_response.sh stderr: +Successful External Resolver +PWD: $ROOT/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response +Type: dependencies +Package: Anything diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/Move.resolved b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/Move.resolved new file mode 100644 index 0000000000000..dd5a8f90cd2a1 --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/Move.resolved @@ -0,0 +1,180 @@ +ResolvedGraph { + graph: DependencyGraph { + root_path: "tests/test_sources/external_package_batch_response", + root_package_id: "Root", + root_package_name: "Root", + package_graph: { + "Root": [ + ( + "bar", + Outgoing, + ), + ( + "foo", + Outgoing, + ), + ], + "bar": [ + ( + "Root", + Incoming, + ), + ], + "foo": [ + ( + "Root", + Incoming, + ), + ], + }, + package_table: { + "bar": Package { + kind: Local( + "deps_only/bar", + ), + version: Some( + "5", + ), + resolver: Some( + "../resolvers/successful_package_batch_response.sh", + ), + }, + "foo": Package { + kind: Local( + "deps_only/foo", + ), + version: None, + resolver: Some( + "../resolvers/successful_package_batch_response.sh", + ), + }, + }, + always_deps: { + "Root", + "bar", + "foo", + }, + manifest_digest: "C998DF0F473DF6AB3E7B06E0E596FDE9836950D07EDED905464AA1337F176738", + deps_digest: "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600", + }, + build_options: BuildConfig { + dev_mode: true, + test_mode: false, + generate_docs: false, + install_dir: Some( + "ELIDED_FOR_TEST", + ), + force_recompilation: false, + lock_file: Some( + "ELIDED_FOR_TEST", + ), + fetch_deps_only: false, + skip_fetch_latest_git_deps: false, + default_flavor: None, + default_edition: None, + deps_as_root: false, + silence_warnings: false, + warnings_are_errors: false, + json_errors: false, + additional_named_addresses: {}, + lint_flag: LintFlag { + no_lint: false, + lint: false, + }, + }, + package_table: { + "Root": Package { + source_package: SourceManifest { + package: PackageInfo { + name: "Root", + authors: [], + license: None, + edition: None, + flavor: None, + custom_properties: {}, + }, + addresses: Some( + { + "root": Some( + 0000000000000000000000000000000000000000000000000000000000000000, + ), + }, + ), + dev_address_assignments: None, + build: None, + dependencies: { + "Anything": External( + "../resolvers/successful_package_batch_response.sh", + ), + }, + dev_dependencies: {}, + }, + package_path: "ELIDED_FOR_TEST", + renaming: {}, + resolved_table: { + "bar": 0000000000000000000000000000000000000000000000000000000000000000, + "foo": 0000000000000000000000000000000000000000000000000000000000000000, + "root": 0000000000000000000000000000000000000000000000000000000000000000, + }, + source_digest: "ELIDED_FOR_TEST", + }, + "bar": Package { + source_package: SourceManifest { + package: PackageInfo { + name: "bar", + authors: [], + license: None, + edition: None, + flavor: None, + custom_properties: {}, + }, + addresses: Some( + { + "bar": Some( + 0000000000000000000000000000000000000000000000000000000000000000, + ), + }, + ), + dev_address_assignments: None, + build: None, + dependencies: {}, + dev_dependencies: {}, + }, + package_path: "ELIDED_FOR_TEST", + renaming: {}, + resolved_table: { + "bar": 0000000000000000000000000000000000000000000000000000000000000000, + }, + source_digest: "ELIDED_FOR_TEST", + }, + "foo": Package { + source_package: SourceManifest { + package: PackageInfo { + name: "foo", + authors: [], + license: None, + edition: None, + flavor: None, + custom_properties: {}, + }, + addresses: Some( + { + "foo": Some( + 0000000000000000000000000000000000000000000000000000000000000000, + ), + }, + ), + dev_address_assignments: None, + build: None, + dependencies: {}, + dev_dependencies: {}, + }, + package_path: "ELIDED_FOR_TEST", + renaming: {}, + resolved_table: { + "foo": 0000000000000000000000000000000000000000000000000000000000000000, + }, + source_digest: "ELIDED_FOR_TEST", + }, + }, +} diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/Move.toml new file mode 100644 index 0000000000000..d6dc943246da4 --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/Move.toml @@ -0,0 +1,8 @@ +[package] +name = "Root" + +[dependencies] +Anything = { r."../resolvers/successful_package_batch_response.sh" = "AlsoAnything" } + +[addresses] +root = "0x0" diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/deps_only/bar/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/deps_only/bar/Move.toml new file mode 100644 index 0000000000000..008ac4dab92da --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/deps_only/bar/Move.toml @@ -0,0 +1,5 @@ +[package] +name = "bar" + +[addresses] +bar = "0x0" diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/deps_only/bar/sources/bar.move b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/deps_only/bar/sources/bar.move new file mode 100644 index 0000000000000..e1eb4beaae918 --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/deps_only/bar/sources/bar.move @@ -0,0 +1 @@ +module bar::bar {} diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/deps_only/foo/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/deps_only/foo/Move.toml new file mode 100644 index 0000000000000..a739d8d46088a --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/deps_only/foo/Move.toml @@ -0,0 +1,5 @@ +[package] +name = "foo" + +[addresses] +foo = "0x0" diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/deps_only/foo/sources/foo.move b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/deps_only/foo/sources/foo.move new file mode 100644 index 0000000000000..28436d11ecb66 --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/deps_only/foo/sources/foo.move @@ -0,0 +1 @@ +module foo::foo {} diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/sources/root.move b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/sources/root.move new file mode 100644 index 0000000000000..1a16facc09fb3 --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_package_batch_response/sources/root.move @@ -0,0 +1 @@ +module root::root {} diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_resolver_config/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_config/Move.locked new file mode 100644 index 0000000000000..027ed6a503771 --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_config/Move.locked @@ -0,0 +1,6 @@ +# @generated by Move, please check-in and do not edit manually. + +[move] +version = 3 +manifest_digest = "D0A99B6D19946036C7A76C46B5B13EC9E47ED5FB3B8940036A0B93D627225A8A" +deps_digest = "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855" diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_resolver_config/Move.resolved b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_config/Move.resolved new file mode 100644 index 0000000000000..7bcee822c4b33 --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_config/Move.resolved @@ -0,0 +1,64 @@ +ResolvedGraph { + graph: DependencyGraph { + root_path: "tests/test_sources/external_resolver_config", + root_package_id: "Root", + root_package_name: "Root", + package_graph: { + "Root": [], + }, + package_table: {}, + always_deps: { + "Root", + }, + manifest_digest: "D0A99B6D19946036C7A76C46B5B13EC9E47ED5FB3B8940036A0B93D627225A8A", + deps_digest: "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855", + }, + build_options: BuildConfig { + dev_mode: true, + test_mode: false, + generate_docs: false, + install_dir: Some( + "ELIDED_FOR_TEST", + ), + force_recompilation: false, + lock_file: Some( + "ELIDED_FOR_TEST", + ), + fetch_deps_only: false, + skip_fetch_latest_git_deps: false, + default_flavor: None, + default_edition: None, + deps_as_root: false, + silence_warnings: false, + warnings_are_errors: false, + json_errors: false, + additional_named_addresses: {}, + lint_flag: LintFlag { + no_lint: false, + lint: false, + }, + }, + package_table: { + "Root": Package { + source_package: SourceManifest { + package: PackageInfo { + name: "Root", + authors: [], + license: None, + edition: None, + flavor: None, + custom_properties: {}, + }, + addresses: None, + dev_address_assignments: None, + build: None, + dependencies: {}, + dev_dependencies: {}, + }, + package_path: "ELIDED_FOR_TEST", + renaming: {}, + resolved_table: {}, + source_digest: "ELIDED_FOR_TEST", + }, + }, +} diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_resolver_config/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_config/Move.toml new file mode 100644 index 0000000000000..0a64e2e10e7e9 --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_config/Move.toml @@ -0,0 +1,6 @@ +[package] +name = "Root" + +[r.foo] +network = "something" +other_thing = "hmmm" diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_resolver_config/sources/Root.move b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_config/sources/Root.move new file mode 100644 index 0000000000000..b3d738219c95a --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_config/sources/Root.move @@ -0,0 +1,11 @@ +module AA::Root { + use AA::A; + use BA::B; + use BA::C; + + public fun foo() { + A::foo(); + B::foo(); + C::foo(); + } +} diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver/Move.resolved b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver/Move.resolved new file mode 100644 index 0000000000000..6866147064b03 --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver/Move.resolved @@ -0,0 +1,3 @@ +Error parsing '[dependencies]' section of manifest: Malformed external resolver declaration for dependency r.baz = "quz" +foo = "bar" + diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver/Move.toml new file mode 100644 index 0000000000000..188d524d443e8 --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver/Move.toml @@ -0,0 +1,5 @@ +[package] +name = "Root" + +[dependencies] +A = { r = { foo = "bar" , baz = "quz" } } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver/sources/Root.move b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver/sources/Root.move new file mode 100644 index 0000000000000..b3d738219c95a --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver/sources/Root.move @@ -0,0 +1,11 @@ +module AA::Root { + use AA::A; + use BA::B; + use BA::C; + + public fun foo() { + A::foo(); + B::foo(); + C::foo(); + } +} diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver_invalid_target/Move.resolved b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver_invalid_target/Move.resolved new file mode 100644 index 0000000000000..cf2f91775d1f8 --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver_invalid_target/Move.resolved @@ -0,0 +1,3 @@ +Error parsing '[dependencies]' section of manifest: Malformed external resolver declaration for dependency r.[foo] +bar = "baz" + diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver_invalid_target/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver_invalid_target/Move.toml new file mode 100644 index 0000000000000..97b6185809972 --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver_invalid_target/Move.toml @@ -0,0 +1,5 @@ +[package] +name = "Root" + +[dependencies] +B = { r.foo.bar = "baz" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver_invalid_target/sources/Root.move b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver_invalid_target/sources/Root.move new file mode 100644 index 0000000000000..b3d738219c95a --- /dev/null +++ b/external-crates/move/crates/move-package/tests/test_sources/external_resolver_invalid_resolver_invalid_target/sources/Root.move @@ -0,0 +1,11 @@ +module AA::Root { + use AA::A; + use BA::B; + use BA::C; + + public fun foo() { + A::foo(); + B::foo(); + C::foo(); + } +} diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_silent/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/external_silent/Move.locked index 22d98b6190511..357f6c5ce6bf4 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external_silent/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/external_silent/Move.locked @@ -1,21 +1,21 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 -manifest_digest = "C5119AF40DAACF1DF25E8E9DE830EC0EA839A29E62D7081BF217B49B10BD6980" +version = 3 +manifest_digest = "33DC15844925BAA2A89C51EC6604D5C5756C75124306422A18CA5D33E507DC6F" deps_digest = "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082" dependencies = [ - { name = "A" }, + { id = "A", name = "A" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } dependencies = [ - { name = "ADep" }, + { id = "ADep", name = "ADep" }, ] [[move.package]] -name = "ADep" +id = "ADep" source = { local = "deps_only/ADep" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/external_silent/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/external_silent/Move.toml index 4ba3e12e91ddc..13dbb0609c98f 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/external_silent/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/external_silent/Move.toml @@ -1,5 +1,5 @@ [package] name = "Root" -[dependencies.A] -resolver = "../resolvers/silent.sh" +[dependencies] +A = { r."../resolvers/silent.sh" = "a" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/multiple_deps_no_rename/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/multiple_deps_no_rename/Move.locked index cdcc66f3955b9..6223fbab28b6c 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/multiple_deps_no_rename/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/multiple_deps_no_rename/Move.locked @@ -1,18 +1,18 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "846084292F5A72B3CFE28402E76C1F3F172C3001C90FAAC19221D4B178A84EDB" deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" dependencies = [ - { name = "C" }, - { name = "D" }, + { id = "C", name = "C" }, + { id = "D", name = "D" }, ] [[move.package]] -name = "C" +id = "C" source = { local = "deps_only/C" } [[move.package]] -name = "D" +id = "D" source = { local = "deps_only/D" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/nested_deps_git_local/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/nested_deps_git_local/Move.locked index 76f59370ce000..ab8df643e9c29 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/nested_deps_git_local/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/nested_deps_git_local/Move.locked @@ -1,21 +1,21 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "598DCC919F7378E59F328E1B448D1AAC70B8F34894146860B3ABF46600F9F79B" deps_digest = "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082" dependencies = [ - { name = "MoveNursery" }, + { id = "MoveNursery", name = "MoveNursery" }, ] [[move.package]] -name = "MoveNursery" +id = "MoveNursery" source = { git = "https://github.com/MystenLabs/sui", rev = "5d8a867", subdir = "external-crates/move/crates/move-stdlib/nursery" } dependencies = [ - { name = "MoveStdlib" }, + { id = "MoveStdlib", name = "MoveStdlib" }, ] [[move.package]] -name = "MoveStdlib" +id = "MoveStdlib" source = { git = "https://github.com/MystenLabs/sui", rev = "5d8a867", subdir = "external-crates/move/crates/move-stdlib" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/nested_deps_local_local/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/nested_deps_local_local/Move.locked index 2690dab64732b..c3513005b31ff 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/nested_deps_local_local/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/nested_deps_local_local/Move.locked @@ -1,21 +1,21 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "CD8925BBAAE2A64C347B3D48466097B55E04876C226546516D83EDC58C0BBB3A" deps_digest = "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082" dependencies = [ - { name = "Nested" }, + { id = "Nested", name = "Nested" }, ] [[move.package]] -name = "More" +id = "More" source = { local = "deps_only/nested/more" } [[move.package]] -name = "Nested" +id = "Nested" source = { local = "deps_only/nested" } dependencies = [ - { name = "More" }, + { id = "More", name = "More" }, ] diff --git a/external-crates/move/crates/move-package/tests/test_sources/nested_deps_override/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/nested_deps_override/Move.locked index 2690dab64732b..c3513005b31ff 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/nested_deps_override/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/nested_deps_override/Move.locked @@ -1,21 +1,21 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "CD8925BBAAE2A64C347B3D48466097B55E04876C226546516D83EDC58C0BBB3A" deps_digest = "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082" dependencies = [ - { name = "Nested" }, + { id = "Nested", name = "Nested" }, ] [[move.package]] -name = "More" +id = "More" source = { local = "deps_only/nested/more" } [[move.package]] -name = "Nested" +id = "Nested" source = { local = "deps_only/nested" } dependencies = [ - { name = "More" }, + { id = "More", name = "More" }, ] diff --git a/external-crates/move/crates/move-package/tests/test_sources/nested_deps_shared_override/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/nested_deps_shared_override/Move.locked index e9a3c5e449a44..f6e88c848912d 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/nested_deps_shared_override/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/nested_deps_shared_override/Move.locked @@ -1,32 +1,32 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "5005BF461ECC5281FE314371B38D8C163C5395D7455B91BEA0F63C1BCFD57551" deps_digest = "060AD7E57DFB13104F21BE5F5C3759D03F0553FC3229247D9A7A6B45F50D03A3" dependencies = [ - { name = "More" }, - { name = "Nested" }, - { name = "Shared" }, + { id = "More", name = "More" }, + { id = "Nested", name = "Nested" }, + { id = "Shared", name = "Shared" }, ] [[move.package]] -name = "More" +id = "More" source = { local = "deps_only/more" } dependencies = [ - { name = "Shared" }, + { id = "Shared", name = "Shared" }, ] [[move.package]] -name = "Nested" +id = "Nested" source = { local = "deps_only/nested" } dependencies = [ - { name = "More" }, - { name = "Shared" }, + { id = "More", name = "More" }, + { id = "Shared", name = "Shared" }, ] [[move.package]] -name = "Shared" +id = "Shared" source = { local = "deps_only/shared" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/one_dep/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/one_dep/Move.locked index 8bc86b54e5795..ba3039f5d0028 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/one_dep/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/one_dep/Move.locked @@ -1,13 +1,13 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "0C66C2C067539518C3189E86B5A09D478C872C0F97ACE6D707AE9753319E56AA" deps_digest = "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082" dependencies = [ - { name = "OtherDep", addr_subst = { "A" = "B" } }, + { id = "OtherDep", name = "OtherDep", addr_subst = { "A" = "B" } }, ] [[move.package]] -name = "OtherDep" +id = "OtherDep" source = { local = "deps_only/other_dep" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/one_dep_bad_digest/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/one_dep_bad_digest/Move.locked index 37b2fbd46b2db..c1ac6f7f30305 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/one_dep_bad_digest/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/one_dep_bad_digest/Move.locked @@ -1,13 +1,13 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "9DE3FDE63E3A9E1827A3D28769FE454A274BEF7DC0514AEEA9076DE876F2C361" deps_digest = "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082" dependencies = [ - { name = "OtherDep", digest = "BAD_DIGEST", addr_subst = { "A" = "B" } }, + { id = "OtherDep", name = "OtherDep", digest = "BAD_DIGEST", addr_subst = { "A" = "B" } }, ] [[move.package]] -name = "OtherDep" +id = "OtherDep" source = { local = "deps_only/other_dep" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/one_dep_override/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/one_dep_override/Move.locked index 650bc797bd2b4..e329435a00d5f 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/one_dep_override/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/one_dep_override/Move.locked @@ -1,13 +1,13 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "340302CAC58F9844D483E9F19F08D9578B90043E621D953A62A4B679F8D98896" deps_digest = "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082" dependencies = [ - { name = "OtherDep", addr_subst = { "A" = "B" } }, + { id = "OtherDep", name = "OtherDep", addr_subst = { "A" = "B" } }, ] [[move.package]] -name = "OtherDep" +id = "OtherDep" source = { local = "deps_only/other_dep" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/package_hooks/Move.resolved b/external-crates/move/crates/move-package/tests/test_sources/package_hooks/Move.resolved index c942874984d2b..0e0c36951c599 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/package_hooks/Move.resolved +++ b/external-crates/move/crates/move-package/tests/test_sources/package_hooks/Move.resolved @@ -1 +1 @@ -Failed to resolve dependencies for package 'test': Fetching 'Pkg': TestHooks resolve dep "Pkg" = "localhost:8080" "Pkg" "0x1" "" +Failed to resolve dependencies for package 'test': Fetching 'Pkg': TestHooks resolve dep "Pkg" = "0x1" diff --git a/external-crates/move/crates/move-package/tests/test_sources/package_hooks/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/package_hooks/Move.toml index a8d6d6d3fb00f..f666cd756f791 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/package_hooks/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/package_hooks/Move.toml @@ -2,4 +2,4 @@ name = "test" [dependencies] -Pkg = { custom = "localhost:8080", address = "0x1" } +Pkg = { id = "0x1" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/package_hooks_subdir/Move.resolved b/external-crates/move/crates/move-package/tests/test_sources/package_hooks_subdir/Move.resolved deleted file mode 100644 index f855ef689ac6e..0000000000000 --- a/external-crates/move/crates/move-package/tests/test_sources/package_hooks_subdir/Move.resolved +++ /dev/null @@ -1 +0,0 @@ -Failed to resolve dependencies for package 'test': Fetching 'Pkg': TestHooks resolve dep "Pkg" = "localhost:8080" "Pkg" "0x1" "foo/bar" diff --git a/external-crates/move/crates/move-package/tests/test_sources/package_hooks_subdir/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/package_hooks_subdir/Move.toml deleted file mode 100644 index 540b6f9b0f614..0000000000000 --- a/external-crates/move/crates/move-package/tests/test_sources/package_hooks_subdir/Move.toml +++ /dev/null @@ -1,5 +0,0 @@ -[package] -name = "test" - -[dependencies] -Pkg = { custom = "localhost:8080", address = "0x1", subdir = "foo/bar" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/parsing_no_path_set_for_dependency/Move.resolved b/external-crates/move/crates/move-package/tests/test_sources/parsing_no_path_set_for_dependency/Move.resolved index 173327fe8624b..c11f6354da52b 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/parsing_no_path_set_for_dependency/Move.resolved +++ b/external-crates/move/crates/move-package/tests/test_sources/parsing_no_path_set_for_dependency/Move.resolved @@ -1 +1 @@ -Error parsing '[dependencies]' section of manifest: must provide exactly one of 'local' or 'git' or 'resolver' or 'custom' for dependency. +Error parsing '[dependencies]' section of manifest: must provide exactly one of 'local' or 'git' or 'r.' for dependency. diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name/Move.locked index f8ea0a78c8aec..27cec68c7eb98 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name/Move.locked @@ -1,30 +1,30 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "6F18190939664D7ECC8F2DC327E079037A743E0CFF3FA5F72DABD2B6B5C3D200" deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" dependencies = [ - { name = "A-resolved", addr_subst = { "AA" = "0000000000000000000000000000000000000000000000000000000000000001" } }, - { name = "B-resolved", addr_subst = { "BA" = "0000000000000000000000000000000000000000000000000000000000000001" } }, + { id = "A-resolved", name = "A-rename", addr_subst = { "AA" = "0000000000000000000000000000000000000000000000000000000000000001" } }, + { id = "B-resolved", name = "B-rename", addr_subst = { "BA" = "0000000000000000000000000000000000000000000000000000000000000001" } }, ] [[move.package]] -name = "A-resolved" +id = "A-resolved" source = { local = "deps_only/A-rename" } dependencies = [ - { name = "C-resolved", addr_subst = { "AA" = "A" } }, + { id = "C-resolved", name = "C-rename", addr_subst = { "AA" = "A" } }, ] [[move.package]] -name = "B-resolved" +id = "B-resolved" source = { local = "deps_only/B-rename" } dependencies = [ - { name = "C-resolved", addr_subst = { "BA" = "A" } }, + { id = "C-resolved", name = "C-rename", addr_subst = { "BA" = "A" } }, ] [[move.package]] -name = "C-resolved" +id = "C-resolved" source = { local = "deps_only/C-rename" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/Move.resolved b/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/Move.resolved index 220e2bdd225d8..186e1fe117832 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/Move.resolved +++ b/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/Move.resolved @@ -1,5 +1,5 @@ -When resolving dependencies for package Root-rename, conflicting versions of package C-resolved found: -At A-rename -> C-resolved - C-resolved = { local = "deps_only/C-rename-v2" } -At B -> C-resolved - C-resolved = { local = "deps_only/C-rename-v1" } +When resolving dependencies for package Root-rename, conflicting versions of package C-rename found: +At A-rename -> C-rename + C-rename = { local = "deps_only/C-rename-v2" } +At B -> C-rename + C-rename = { local = "deps_only/C-rename-v1" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/deps_only/A-rename/Move.lock b/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/deps_only/A-rename/Move.lock index 5a92bc29440cf..b6825dd9ea91a 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/deps_only/A-rename/Move.lock +++ b/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/deps_only/A-rename/Move.lock @@ -1,14 +1,14 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 0 +version = 3 manifest_digest = "EB5AB2DFC86E6D3D2FE98D626C2162A4C7344F2A3C485FE58DD6C339CD5E73CF" deps_digest = "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082" dependencies = [ - { name = "C-resolved" }, + { id = "C-resolved", name = "C-rename" }, ] [[move.package]] -name = "C-resolved" +id = "C-resolved" source = { local = "../C-rename-v2" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/deps_only/B/Move.lock b/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/deps_only/B/Move.lock index f7ebf2752eb21..538dda6f5aaf0 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/deps_only/B/Move.lock +++ b/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/deps_only/B/Move.lock @@ -1,14 +1,14 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 0 +version = 3 manifest_digest = "9C1FF13CFBAECFDB6402A2E0952FDA85BE3719E6D1FD0682D63A6CD7A8E92ED6" deps_digest = "F8BBB0CCB2491CA29A3DF03D6F92277A4F3574266507ACD77214D37ECA3F3082" dependencies = [ - { name = "C-resolved" }, + { id = "C-resolved", name = "C-rename" }, ] [[move.package]] -name = "C-resolved" +id = "C-resolved" source = { local = "../C-rename-v1" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/deps_only/C-rename-v1/Move.lock b/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/deps_only/C-rename-v1/Move.lock index d255a73a57e76..00fbe255cd9d8 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/deps_only/C-rename-v1/Move.lock +++ b/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/deps_only/C-rename-v1/Move.lock @@ -1,6 +1,6 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 0 +version = 3 manifest_digest = "60A5B7F88A83F5697F7B9AC059CB9680BD351F68B8EB80AFA13294BBB97A47C3" deps_digest = "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855" diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/deps_only/C-rename-v2/Move.lock b/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/deps_only/C-rename-v2/Move.lock index d255a73a57e76..00fbe255cd9d8 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/deps_only/C-rename-v2/Move.lock +++ b/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_conflict_lockfile/deps_only/C-rename-v2/Move.lock @@ -1,6 +1,6 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 0 +version = 3 manifest_digest = "60A5B7F88A83F5697F7B9AC059CB9680BD351F68B8EB80AFA13294BBB97A47C3" deps_digest = "E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855" diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_nested_dep_conflict/Move.resolved b/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_nested_dep_conflict/Move.resolved index 1c17de6f22aa6..b8a0ddf42ba03 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_nested_dep_conflict/Move.resolved +++ b/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_nested_dep_conflict/Move.resolved @@ -2,4 +2,4 @@ When resolving dependencies for package Root-rename, conflicting dependencies fo At B -> A ADep-rename = { local = "deps_only/ADep", addr_subst = { "A" = "0000000000000000000000000000000000000000000000000000000000000007" } } At C -> A - ADep-resolved = { local = "deps_only/ADep", addr_subst = { "A" = "0000000000000000000000000000000000000000000000000000000000000042" } } + ADep-rename = { local = "deps_only/ADep", addr_subst = { "A" = "0000000000000000000000000000000000000000000000000000000000000042" } } diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_nested_dep_conflict/deps_only/C/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_nested_dep_conflict/deps_only/C/Move.toml index e1529c9b74de2..9331862cbbfc0 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_nested_dep_conflict/deps_only/C/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/resolve_pkg_name_nested_dep_conflict/deps_only/C/Move.toml @@ -1,9 +1,5 @@ [package] name = "C" -[dependencies.A] -resolver = "../../../resolvers/successful_subst_name_resolution.sh" - -[dependencies.A.packages] -Contains = "Anything" -Has = { No = "Schema" } +[dependencies] +A = { r."../../../resolvers/successful_subst_name_resolution.sh" = "A" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolve_version/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/resolve_version/Move.locked index 28ba915ae54bd..ab2fe9145134c 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/resolve_version/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/resolve_version/Move.locked @@ -1,20 +1,20 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "A152DBB11C386226B7A6435D66090103E0CC19330A38251784E4D2D0C0EF57A5" deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" dependencies = [ - { name = "A" }, - { name = "B" }, + { id = "A", name = "A" }, + { id = "B", name = "B" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } version = "3" [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B" } version = "2" diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond/Move.locked index ae74581c8ef7e..e61df4416c488 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond/Move.locked @@ -1,33 +1,33 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "88C81CA573217383E355274A37F0C6171DDE43ED835A166B0D6446C294788F65" deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" dependencies = [ - { name = "A" }, - { name = "B" }, + { id = "A", name = "A" }, + { id = "B", name = "B" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } version = "3" dependencies = [ - { name = "C" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B" } version = "1" dependencies = [ - { name = "C" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "C" +id = "C" source = { local = "deps_only/C-loc-1" } version = "2" diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_deep/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_deep/Move.locked index e77832cd7c792..73515ac3ee7ce 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_deep/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_deep/Move.locked @@ -1,41 +1,41 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "66CAE9439801A11D189DACA1182DFEF8BF0CA1C5F043F966D466CD7E7C486FB3" deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" dependencies = [ - { name = "A" }, - { name = "B" }, + { id = "A", name = "A" }, + { id = "B", name = "B" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } version = "3" dependencies = [ - { name = "C" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B" } version = "1" dependencies = [ - { name = "C" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "C" +id = "C" source = { local = "deps_only/C-loc-1" } version = "2" dependencies = [ - { name = "D" }, + { id = "D", name = "D" }, ] [[move.package]] -name = "D" +id = "D" source = { local = "deps_only/D" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_deep_success/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_deep_success/Move.locked index 04acf0e2f1e7e..e77112522509b 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_deep_success/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_deep_success/Move.locked @@ -1,42 +1,42 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "E301B66162FDBFF21CE0504F64D66F3F9CFE1FC85B6675A3D91C417A41284EEA" deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" dependencies = [ - { name = "A" }, - { name = "B" }, + { id = "A", name = "A" }, + { id = "B", name = "B" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } version = "3" dependencies = [ - { name = "C" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B" } version = "1" dependencies = [ - { name = "C" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "C" +id = "C" source = { local = "deps_only/C-loc-1" } version = "2" dependencies = [ - { name = "D" }, + { id = "D", name = "D" }, ] [[move.package]] -name = "D" +id = "D" source = { local = "deps_only/D-loc-1" } version = "1" diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_external/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_external/Move.locked index 18aab8973149a..1e55205387815 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_external/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_external/Move.locked @@ -1,31 +1,31 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 -manifest_digest = "99C31161E1ED00C7621A2403D3513580715CA556C77C3C26FB1233B0778291AF" +version = 3 +manifest_digest = "B66C7D8081E174C49097FE3DE00A7C0D093D6C0998D61A70C43498B1E822C2C5" deps_digest = "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600" dependencies = [ - { name = "A" }, - { name = "B" }, + { id = "A", name = "A" }, + { id = "B", name = "B" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } dependencies = [ - { name = "ADep" }, + { id = "ADep", name = "ADep" }, ] [[move.package]] -name = "ADep" +id = "ADep" source = { local = "deps_only/ADep" } version = "4" [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B" } dependencies = [ - { name = "ADep" }, + { id = "ADep", name = "ADep" }, ] diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_external/Move.resolved b/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_external/Move.resolved index 1b1ae7a55fa84..2aeb9fda97556 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_external/Move.resolved +++ b/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_external/Move.resolved @@ -80,7 +80,7 @@ ResolvedGraph { "B", "Root", }, - manifest_digest: "99C31161E1ED00C7621A2403D3513580715CA556C77C3C26FB1233B0778291AF", + manifest_digest: "B66C7D8081E174C49097FE3DE00A7C0D093D6C0998D61A70C43498B1E822C2C5", deps_digest: "3C4103934B1E040BB6B23F1D610B4EF9F2F1166A50A104EADCF77467C004C600", }, build_options: BuildConfig { diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_external/Move.toml b/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_external/Move.toml index 6c39ce9fa688e..a2afc7d24bb31 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_external/Move.toml +++ b/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_external/Move.toml @@ -19,5 +19,5 @@ name = "Root" version = "1" [dependencies] -A = { resolver = "../resolvers/successful_version.sh", packages = { Contains = "Anything", Has = { No = "Schema" } } } +A = { r."../resolvers/successful_version.sh" = "A" } B = { local = "./deps_only/B" } diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_override/Move.locked b/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_override/Move.locked index daf35021ea5a1..8bc3a232b1c63 100644 --- a/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_override/Move.locked +++ b/external-crates/move/crates/move-package/tests/test_sources/resolve_version_diamond_override/Move.locked @@ -1,34 +1,34 @@ # @generated by Move, please check-in and do not edit manually. [move] -version = 2 +version = 3 manifest_digest = "84867C472017C20EFC5FF536C440568833A0F592C104B9F9EF5CD5A27D109CDB" deps_digest = "060AD7E57DFB13104F21BE5F5C3759D03F0553FC3229247D9A7A6B45F50D03A3" dependencies = [ - { name = "A" }, - { name = "B" }, - { name = "C" }, + { id = "A", name = "A" }, + { id = "B", name = "B" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "A" +id = "A" source = { local = "deps_only/A" } version = "3" dependencies = [ - { name = "C" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "B" +id = "B" source = { local = "deps_only/B" } version = "1" dependencies = [ - { name = "C" }, + { id = "C", name = "C" }, ] [[move.package]] -name = "C" +id = "C" source = { local = "deps_only/C-loc-2" } version = "2" diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolvers/silent.sh b/external-crates/move/crates/move-package/tests/test_sources/resolvers/silent.sh index 69616a2cbe024..9ea4d24446d12 100755 --- a/external-crates/move/crates/move-package/tests/test_sources/resolvers/silent.sh +++ b/external-crates/move/crates/move-package/tests/test_sources/resolvers/silent.sh @@ -9,21 +9,21 @@ PACKAGE="$2" # Print lock file cat <&2 +Successful External Resolver +PWD: $(pwd | sed "s,^$ROOT,\$ROOT,") +Type: $TYPE +Package: $PACKAGE +EOF + +foo=$(cat <<'EOF' +[move] +version = 3 +manifest_digest = "42" +deps_digest = "7" +dependencies = [ + { id = "foo", name = "foo" }, +] + +[[move.package]] +id = "foo" +source = { local = "./deps_only/foo" } +EOF +) + +bar=$(cat <<'EOF' +[move] +version = 3 +manifest_digest = "42" +deps_digest = "7" +dependencies = [ + { id = "bar", name = "bar" }, +] + +[[move.package]] +id = "bar" +version = "5" +source = { local = "./deps_only/bar" } +EOF +) + +# Echo the two separate graph contents twice with a null separator in between +printf "$foo\0$bar" diff --git a/external-crates/move/crates/move-package/tests/test_sources/resolvers/successful_subst.sh b/external-crates/move/crates/move-package/tests/test_sources/resolvers/successful_subst.sh index 20b38cbbec844..5f57c0469420c 100755 --- a/external-crates/move/crates/move-package/tests/test_sources/resolvers/successful_subst.sh +++ b/external-crates/move/crates/move-package/tests/test_sources/resolvers/successful_subst.sh @@ -17,21 +17,21 @@ EOF # Print lock file cat < Functi } if options.prover.dump_bytecode { let dump_file = output_dir.join(format!("{}.mv.disas", output_prefix)); - fs::write(&dump_file, &module_env.disassemble()).expect("dumping disassembled module"); + fs::write(&dump_file, module_env.disassemble()).expect("dumping disassembled module"); } for func_env in module_env.get_functions() { targets.add_target(&func_env) diff --git a/external-crates/move/crates/move-stackless-bytecode/src/function_target_pipeline.rs b/external-crates/move/crates/move-stackless-bytecode/src/function_target_pipeline.rs index 6486f4afb40c0..ebc192b106c69 100644 --- a/external-crates/move/crates/move-stackless-bytecode/src/function_target_pipeline.rs +++ b/external-crates/move/crates/move-stackless-bytecode/src/function_target_pipeline.rs @@ -4,7 +4,6 @@ use core::fmt; use std::{ - cmp::Ordering, collections::{btree_map::Entry as MapEntry, BTreeMap, BTreeSet}, fmt::Formatter, fs, @@ -12,10 +11,7 @@ use std::{ use itertools::{Either, Itertools}; use log::{debug, info}; -use petgraph::{ - algo::has_path_connecting, - graph::{DiGraph, NodeIndex}, -}; +use petgraph::graph::{DiGraph, NodeIndex}; use move_model::model::{FunId, FunctionEnv, GlobalEnv, QualifiedId}; @@ -338,12 +334,19 @@ impl FunctionTargetPipeline { } /// Collect strongly connected components (SCCs) from the call graph. + /// Returns a list of node SCCs in reverse topological order, and a map from function id + /// to other functions in the same SCC. fn derive_call_graph_sccs( env: &GlobalEnv, graph: &DiGraph, ()>, - ) -> BTreeMap, Option>>> { + ) -> ( + Vec>, + BTreeMap, Option>>>, + ) { let mut sccs = BTreeMap::new(); - for scc in petgraph::algo::tarjan_scc(graph) { + // Returned SCCs are in reverse topological order. + let scc_nodes = petgraph::algo::tarjan_scc(graph); + for scc in scc_nodes.clone() { let mut part = BTreeSet::new(); let mut is_cyclic = scc.len() > 1; for node_idx in scc { @@ -367,7 +370,7 @@ impl FunctionTargetPipeline { assert!(existing.is_none()); } } - sccs + (scc_nodes, sccs) } /// Sort the call graph in topological order with strongly connected components (SCCs) @@ -377,11 +380,11 @@ impl FunctionTargetPipeline { targets: &FunctionTargetsHolder, ) -> Vec, Vec>>> { // collect sccs - let (graph, nodes) = Self::build_call_graph(env, targets); - let sccs = Self::derive_call_graph_sccs(env, &graph); + let (graph, _nodes) = Self::build_call_graph(env, targets); + let (scc_nodes, scc_map) = Self::derive_call_graph_sccs(env, &graph); let mut scc_staging = BTreeMap::new(); - for scc_opt in sccs.values() { + for scc_opt in scc_map.values() { match scc_opt.as_ref() { None => (), Some(scc) => { @@ -390,48 +393,28 @@ impl FunctionTargetPipeline { } } - // construct the work list (with a deterministic ordering) + // Construct the work list from a deterministic topological ordering. let mut worklist = vec![]; - for fun in targets.get_funs() { - let fun_env = env.get_function(fun); - worklist.push(( - fun, - fun_env.get_called_functions().into_iter().collect_vec(), - )); + for scc in scc_nodes.into_iter().rev() { + for node_idx in scc { + let fun_id = *graph.node_weight(node_idx).unwrap(); + let fun_env = env.get_function(fun_id); + worklist.push(( + fun_id, + fun_env.get_called_functions().into_iter().collect_vec(), + )); + } } // analyze bottom-up from the leaves of the call graph // NOTE: this algorithm produces a deterministic ordering of functions to be analyzed let mut dep_ordered = vec![]; - while !worklist.is_empty() { - worklist.sort_by(|(caller1, callees1), (caller2, callees2)| { - // rules of ordering: - // - if function A depends on B (i.e., calls B), put B towards the end of the worklist - // - if there are no dependencies among A and B, rank them by callee size - - let node1 = *nodes.get(caller1).unwrap(); - let node2 = *nodes.get(caller2).unwrap(); - match ( - has_path_connecting(&graph, node1, node2, None), - has_path_connecting(&graph, node2, node1, None), - ) { - (true, true) => Ordering::Equal, - (true, false) => Ordering::Less, - (false, true) => Ordering::Greater, - (false, false) => { - // Put functions with 0 calls first in line, at the end of the vector - callees2.len().cmp(&callees1.len()) - } - } - }); - - let (call_id, callees) = worklist.pop().unwrap(); - + while let Some((call_id, callees)) = worklist.pop() { // At this point, one of two things is true: // 1. callees is empty (common case) // 2. callees is nonempty and call_id is part of a recursive or mutually recursive function group - match sccs.get(&call_id).unwrap().as_ref() { + match scc_map.get(&call_id).unwrap().as_ref() { None => { // case 1: non-recursive call assert!(callees.is_empty()); diff --git a/external-crates/move/crates/move-trace-format/Cargo.toml b/external-crates/move/crates/move-trace-format/Cargo.toml new file mode 100644 index 0000000000000..8b0aad362d282 --- /dev/null +++ b/external-crates/move/crates/move-trace-format/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "move-trace-format" +version = "0.0.1" +authors = ["Move Core Contributors"] +description = "Move Trace Format" +license = "Apache-2.0" +edition = "2021" + +[dependencies] +anyhow.workspace = true +proptest = { workspace = true, optional = true } +proptest-derive = { workspace = true, optional = true } +ref-cast.workspace = true +variant_count.workspace = true +move-core-types.workspace = true +serde.workspace = true +arbitrary = { workspace = true, optional = true, features = ["derive"] } +enum-compat-util.workspace = true +move-proc-macros.workspace = true +move-binary-format.workspace = true + +# wasm support (requires js feature of getrandom) +getrandom = { workspace = true, features = ["js"], optional = true } +serde_json = { workspace = true, features = ["arbitrary_precision"] } + +[dev-dependencies] +proptest.workspace = true +proptest-derive.workspace = true +move-core-types = { workspace = true, features = ["fuzzing" ] } + +[features] +default = [] +fuzzing = ["proptest", "proptest-derive", "arbitrary", "move-core-types/fuzzing"] +wasm = ["getrandom"] diff --git a/external-crates/move/crates/move-trace-format/src/format.rs b/external-crates/move/crates/move-trace-format/src/format.rs new file mode 100644 index 0000000000000..4d4f9698cbd89 --- /dev/null +++ b/external-crates/move/crates/move-trace-format/src/format.rs @@ -0,0 +1,388 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +// IDEA: Post trace analysis -- report when values are dropped. + +use crate::interface::{NopTracer, Tracer, Writer}; +use move_binary_format::{ + file_format::{Bytecode, FunctionDefinitionIndex as BinaryFunctionDefinitionIndex}, + file_format_common::instruction_opcode, +}; +use move_core_types::{ + annotated_value::MoveValue, + language_storage::{ModuleId, TypeTag}, +}; +use serde::Serialize; +use std::fmt::Display; + +/// An index into the trace. This should be used when referring to locations in the trace. +/// Otherwise, a `usize` should be used when referring to indices that are not in the trace. +pub type TraceIndex = usize; +pub type TraceVersion = u64; + +/// The current version of the trace format. +const TRACE_VERSION: TraceVersion = 1; + +/// A Location is a valid root for a reference. This can either be a local in a frame, a stack +/// value, or a reference into another location (e.g., vec[0][2]). +/// +/// Note that we track aliasing through the locations so you can always trace back to the root +/// value for the reference. +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] +pub enum Location { + // Local index in a frame. The frame is identified by the index in the trace where it was created. + // The `usize` is the index into the locals of the frame. + Local(TraceIndex, usize), + // An indexed location. This is a reference into another location (e.g., due to a borrow field, + // or a reference into a vector). + Indexed(Box, usize), + // A global reference. + // Identified by the location in the trace where it was introduced. + Global(TraceIndex), +} + +/// A Read event. This represents a read from a location, with the value read and whether the value +/// was moved or not. +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct Read { + pub location: Location, + pub root_value_read: TraceValue, + pub moved: bool, +} + +/// A Write event. This represents a write to a location with the value written and a snapshot of +/// the value that was written. Note that the `root_value_after_write` is a snapshot of the +/// _entire_ (root) value that was written after the write. +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct Write { + pub location: Location, + pub root_value_after_write: TraceValue, +} + +/// A TraceValue is a value in the standard MoveValue domain + references. +/// References hold their own snapshot of the root value they point to, along with the rooted path to +/// the value that they reference within that snapshot. +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum TraceValue { + RuntimeValue { + value: MoveValue, + }, + ImmRef { + location: Location, + // Snapshot of the root value. + snapshot: Box, + }, + MutRef { + location: Location, + // Snapshot of the root value. + snapshot: Box, + }, +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum RefType { + Imm, + Mut, +} + +/// Type tag with references. This is a type tag that also supports references. +/// if ref_type is None, this is a value type. If ref_type is Some, this is a reference type of the +/// given reference type. +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct TypeTagWithRefs { + pub type_: TypeTag, + pub ref_type: Option, +} + +/// A `Frame` represents a stack frame in the Move VM and a given instantiation of a function. +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct Frame { + // The frame id is the offset in the trace where this frame was opened. + pub frame_id: TraceIndex, + pub function_name: String, + pub module: ModuleId, + // External pointer out into the module -- the `FunctionDefinitionIndex` in the module. + pub binary_member_index: u16, + pub type_instantiation: Vec, + pub parameters: Vec, + pub return_types: Vec, + pub locals_types: Vec, + pub is_native: bool, +} + +/// An instruction effect is a single effect of an instruction. This can be a push/pop of a value +/// or a reference to a value, or a read/write of a value. +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub enum Effect { + // Pop a value off the stack (pre-effect only) + Pop(TraceValue), + // Read a value from a location (pre-effect only) + Read(Read), + + // Push a value on the stack (post-effect only) + Push(TraceValue), + // Write a value to a location (post-effect only) + Write(Write), + + // A data load Effect + DataLoad(DataLoad), + + // An execution error occured + ExecutionError(String), +} + +/// Represent a data load event. This is a load of a value from storage. We only record loads by +/// reference in the trace, and we snapshot the value at the reference location at the time of load +/// and record its global reference ID (i.e., the location in the trace at which it was loaded). +#[derive(Clone, Debug, Eq, PartialEq, Serialize)] +pub struct DataLoad { + pub ref_type: RefType, + pub location: Location, + pub snapshot: MoveValue, +} + +/// A TraceEvent is a single event in the Move VM, external events can also be interleaved in the +/// trace. MoveVM events, are well structured, and can be a frame event or an instruction event. +#[derive(Debug, Clone, Eq, PartialEq, Serialize)] +pub enum TraceEvent { + OpenFrame { + frame: Box, + gas_left: u64, + }, + CloseFrame { + frame_id: TraceIndex, + return_: Vec, + gas_left: u64, + }, + Instruction { + type_parameters: Vec, + pc: u16, + gas_left: u64, + instruction: Box, + }, + Effect(Box), + External(Box), +} + +#[derive(Serialize, Debug, Clone, Eq, PartialEq)] +pub struct MoveTrace { + pub version: TraceVersion, + pub events: Vec, +} + +/// The Move trace format. The custom tracer is not serialized, but the events are. +/// This is the format that the Move VM will output traces in, and the `tracer` can output +/// additional events to the trace. +pub struct MoveTraceBuilder { + pub tracer: Box, + + pub trace: MoveTrace, +} + +impl TraceValue { + pub fn snapshot(&self) -> &MoveValue { + match self { + TraceValue::ImmRef { snapshot, .. } | TraceValue::MutRef { snapshot, .. } => snapshot, + TraceValue::RuntimeValue { value } => value, + } + } + + pub fn value_mut(&mut self) -> Option<&mut MoveValue> { + match self { + TraceValue::RuntimeValue { value, .. } => Some(value), + _ => None, + } + } + + pub fn location(&self) -> Option<&Location> { + match self { + TraceValue::ImmRef { location, .. } | TraceValue::MutRef { location, .. } => { + Some(location) + } + _ => None, + } + } +} + +impl MoveTrace { + pub fn new() -> Self { + Self { + version: TRACE_VERSION, + events: vec![], + } + } + + pub fn to_json(&self) -> serde_json::Value { + serde_json::to_value(self).unwrap() + } +} + +impl MoveTraceBuilder { + /// Create a new `MoveTraceBuilder` with no additional tracing. + pub fn new() -> Self { + Self { + tracer: Box::new(NopTracer), + trace: MoveTrace::new(), + } + } + + /// Create a new `MoveTraceBuilder` with a custom `tracer`. + pub fn new_with_tracer(tracer: Box) -> Self { + Self { + tracer, + trace: MoveTrace::new(), + } + } + + /// Consume the `MoveTraceBuilder` and return the `MoveTrace` that has been built by it. + pub fn into_trace(self) -> MoveTrace { + self.trace + } + + /// Get the current offset in the `MoveTrace` that is being built. + pub fn current_trace_offset(&self) -> TraceIndex { + self.trace.events.len() + } + + /// Record an `OpenFrame` event in the trace. + pub fn open_frame( + &mut self, + frame_id: TraceIndex, + binary_member_index: BinaryFunctionDefinitionIndex, + name: String, + module: ModuleId, + parameters: Vec, + type_instantiation: Vec, + return_types: Vec, + locals_types: Vec, + is_native: bool, + gas_left: u64, + ) { + let frame = Box::new(Frame { + frame_id, + function_name: name, + module, + binary_member_index: binary_member_index.0, + type_instantiation, + parameters, + return_types, + locals_types, + is_native, + }); + self.push_event(TraceEvent::OpenFrame { frame, gas_left }); + } + + /// Record a `CloseFrame` event in the trace. + pub fn close_frame(&mut self, frame_id: TraceIndex, return_: Vec, gas_left: u64) { + self.push_event(TraceEvent::CloseFrame { + frame_id, + return_, + gas_left, + }); + } + + /// Record an `Instruction` event in the trace along with the effects of the instruction. + pub fn instruction( + &mut self, + instruction: &Bytecode, + type_parameters: Vec, + effects: Vec, + gas_left: u64, + pc: u16, + ) { + self.push_event(TraceEvent::Instruction { + type_parameters, + pc, + gas_left, + instruction: Box::new(format!("{:?}", instruction_opcode(instruction))), + }); + for effect in effects { + self.push_event(TraceEvent::Effect(Box::new(effect))); + } + } + + /// Push an `Effect` event to the trace. + pub fn effect(&mut self, effect: Effect) { + self.push_event(TraceEvent::Effect(Box::new(effect))); + } + + // All events pushed to the trace are first pushed, and then the tracer is notified of the + // event. + fn push_event(&mut self, event: TraceEvent) { + self.trace.events.push(event.clone()); + self.tracer.notify(&event, Writer(&mut self.trace)); + } +} + +impl Display for TraceValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TraceValue::RuntimeValue { value } => { + write!(f, "{:#}", value) + } + TraceValue::ImmRef { location, snapshot } => { + write!(f, "&{} {:#}", location, snapshot) + } + TraceValue::MutRef { location, snapshot } => { + write!(f, "&mut {} {:#}", location, snapshot) + } + } + } +} + +impl Display for Location { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Location::Local(frame_idx, idx) => { + write!(f, "l{idx}@{frame_idx}") + } + Location::Indexed(loc, offset) => { + write!(f, "{loc}[{offset}]") + } + Location::Global(id) => { + write!(f, "g{}", id) + } + } + } +} + +impl Display for Effect { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Effect::Pop(value) => { + write!(f, "Pop {}", value) + } + Effect::Push(value) => { + write!(f, "Push {}", value) + } + Effect::Read(Read { + location, + root_value_read: value_read, + moved, + }) => { + let arrow = if *moved { "==>" } else { "-->" }; + write!(f, "{location} {arrow} {value_read}") + } + Effect::Write(Write { + location, + root_value_after_write: value_written, + }) => { + write!(f, "{location} <-- {value_written}") + } + Effect::ExecutionError(error_string) => { + write!(f, "ExecutionError: {error_string}") + } + Effect::DataLoad(DataLoad { + ref_type, + location, + snapshot, + }) => { + let ref_type = match ref_type { + RefType::Imm => "&", + RefType::Mut => "&mut", + }; + write!(f, "g{ref_type}{location} ~~> {snapshot}") + } + } + } +} diff --git a/external-crates/move/crates/move-trace-format/src/interface.rs b/external-crates/move/crates/move-trace-format/src/interface.rs new file mode 100644 index 0000000000000..c48791c0ce645 --- /dev/null +++ b/external-crates/move/crates/move-trace-format/src/interface.rs @@ -0,0 +1,39 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::format::{MoveTrace, TraceEvent, TraceVersion}; +use serde::Serialize; + +/// This is meant to be an internal tracing interface for the VM, and should only be implemented +/// and used if you are _sure_ that you want/need to use it. Generally you should use the output +/// trace format for any analysis or debugging purposes. This should only be used if you want to +/// add custom tracing data to the VM's traces that cannot be added using other means or +/// post-processing. +pub trait Tracer { + /// Notify the tracer of a new event in the VM. This is called for every event that is emitted, + /// and immediatlye _after_ the `event` has been added to the trace held inside of the `writer`. + fn notify(&mut self, event: &TraceEvent, writer: Writer<'_>); +} + +pub struct NopTracer; +impl Tracer for NopTracer { + fn notify(&mut self, _event: &TraceEvent, _writer: Writer<'_>) {} +} + +/// A writer that allows you to push custom events to the trace but encapsulates the trace so that +/// non-external events cannot be accidentally added. +pub struct Writer<'a>(pub(crate) &'a mut MoveTrace); + +impl<'a> Writer<'a> { + /// Emit an external event into the trace. + pub fn push(&mut self, e: T) { + self.0.events.push(TraceEvent::External(Box::new( + serde_json::to_value(e).unwrap(), + ))); + } + + /// Get the current version of the trace. + pub fn trace_version(&mut self) -> TraceVersion { + self.0.version + } +} diff --git a/external-crates/move/crates/move-trace-format/src/lib.rs b/external-crates/move/crates/move-trace-format/src/lib.rs new file mode 100644 index 0000000000000..5f307d24f1aed --- /dev/null +++ b/external-crates/move/crates/move-trace-format/src/lib.rs @@ -0,0 +1,6 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +pub mod format; +pub mod interface; +pub mod memory_tracer; diff --git a/external-crates/move/crates/move-trace-format/src/memory_tracer.rs b/external-crates/move/crates/move-trace-format/src/memory_tracer.rs new file mode 100644 index 0000000000000..910d6910cb7bb --- /dev/null +++ b/external-crates/move/crates/move-trace-format/src/memory_tracer.rs @@ -0,0 +1,196 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +//! This module contains the implementation of the memory tracer. The memory tracer is a tracer +//! that takes a stream of trace events, and uses these events to create a snapshot of the memory +//! state (operand stack, locals, and globals) at each point in time during execution. +//! +//! The memory tracer then emits `External` events with the current VM state for every instruction, +//! and open/close frame event that is has built up. +//! +//! The memory tracer is useful for debugging, and as an example of how to build up this +//! state for more advanced analysis and also using the custom tracing trait. + +use crate::{ + format::{DataLoad, Effect, Location, Read, TraceEvent, TraceIndex, TraceValue, Write}, + interface::{Tracer, Writer}, +}; +use core::fmt; +use move_core_types::annotated_value::MoveValue; +use std::collections::BTreeMap; + +#[derive(Debug, Clone)] +pub struct TraceState { + // Tracks "global memory" state (i.e., references out in to global memory/references returned + // from native functions). + pub loaded_state: BTreeMap, + // The current state (i.e., values) of the VM's operand stack. + pub operand_stack: Vec, + // The current call stack indexed by frame id. Maps from the frame id to the current state of + // the frame's locals. The bool indicates if the frame is native or not. + pub call_stack: BTreeMap, bool)>, +} + +impl TraceState { + pub fn new() -> Self { + Self { + loaded_state: BTreeMap::new(), + operand_stack: vec![], + call_stack: BTreeMap::new(), + } + } + + /// Apply an event to the state machine and update the locals state accordingly. + fn apply_event(&mut self, event: &TraceEvent) { + match event { + TraceEvent::OpenFrame { frame, .. } => { + let mut locals = BTreeMap::new(); + for (i, p) in frame.parameters.iter().enumerate() { + // NB: parameters are passed directly, so we just pop to make sure they aren't also + // left on the operand stack. For the initial call, these pops may (should) fail, but that + // is fine as we already have the values in the parameter list. + self.operand_stack.pop(); + locals.insert(i, p.clone()); + } + + self.call_stack + .insert(frame.frame_id, (locals, frame.is_native)); + } + TraceEvent::CloseFrame { .. } => { + self.call_stack + .pop_last() + .expect("Unbalanced call stack in memory tracer -- this should never happen"); + } + TraceEvent::Effect(ef) => match &**ef { + Effect::ExecutionError(_) => (), + Effect::Push(value) => { + self.operand_stack.push(value.clone()); + } + Effect::Pop(_) => { + self.operand_stack.pop().expect( + "Tried to pop off the empty operand stack -- this should never happen", + ); + } + Effect::Read(Read { + location, + root_value_read: _, + moved, + }) => { + if *moved { + match location { + Location::Local(frame_idx, idx) => { + let frame = self.call_stack.get_mut(frame_idx).unwrap(); + frame.0.remove(idx); + } + Location::Indexed(..) => { + panic!("Cannot move from indexed location"); + } + Location::Global(..) => { + panic!("Cannot move from global location"); + } + } + } + } + Effect::Write(Write { + location, + root_value_after_write: value_written, + }) => match location { + Location::Local(frame_idx, idx) => { + let frame = self.call_stack.get_mut(frame_idx).unwrap(); + frame.0.insert(*idx, value_written.clone()); + } + Location::Indexed(location, _idx) => { + let val = self.get_mut_location(location); + *val = value_written.clone().snapshot().clone(); + } + Location::Global(id) => { + let val = self.loaded_state.get_mut(id).unwrap(); + *val = value_written.snapshot().clone(); + } + }, + Effect::DataLoad(DataLoad { + location, snapshot, .. + }) => { + let Location::Global(id) = location else { + unreachable!("Dataload by reference must have a global location"); + }; + self.loaded_state.insert(*id, snapshot.clone()); + } + }, + // External events are treated opaqeuly + TraceEvent::External(_) => (), + // Instructions + TraceEvent::Instruction { .. } => (), + } + } + + /// Given a reference "location" return a mutable reference to the value it points to so that + /// it can be updated. + fn get_mut_location(&mut self, location: &Location) -> &mut MoveValue { + match location { + Location::Local(frame_idx, idx) => { + let frame = self.call_stack.get_mut(frame_idx).unwrap(); + frame.0.get_mut(idx).unwrap().value_mut().unwrap() + } + Location::Indexed(loc, _offset) => self.get_mut_location(loc), + Location::Global(id) => self.loaded_state.get_mut(id).unwrap(), + } + } +} + +impl Tracer for TraceState { + fn notify(&mut self, event: &TraceEvent, mut write: Writer<'_>) { + self.apply_event(event); + // We only emit the state when we hit a non-effect internal event. This coincides with + // emitting the current state of the VM before each instruction/function call. + match event { + TraceEvent::Instruction { .. } + | TraceEvent::OpenFrame { .. } + | TraceEvent::CloseFrame { .. } => { + write.push(self.to_string()); + } + _ => (), + } + } +} + +impl fmt::Display for TraceState { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if !self.loaded_state.is_empty() { + writeln!(f, "Loaded state:")?; + for (id, v) in &self.loaded_state { + writeln!( + f, + "\t{}: {}", + id, + format!("{:#}", v).replace('\n', "\n\t ") + )?; + } + } + + if !self.operand_stack.is_empty() { + writeln!(f, "Operand stack:")?; + for (i, v) in self.operand_stack.iter().enumerate() { + writeln!(f, "\t{}: {}", i, format!("{:#}", v).replace('\n', "\n\t "))?; + } + } + + if !self.call_stack.is_empty() { + writeln!(f, "Call stack:")?; + for (i, (frame, _)) in self.call_stack.iter() { + if !frame.is_empty() { + writeln!(f, "\tFrame {}:", i)?; + for (j, v) in frame.iter() { + writeln!( + f, + "\t\t{}: {}", + j, + format!("{:#}", v).replace('\n', "\n\t\t") + )?; + } + } + } + } + Ok(()) + } +} diff --git a/external-crates/move/crates/move-unit-test/Cargo.toml b/external-crates/move/crates/move-unit-test/Cargo.toml index 2d60a4cf9a0cf..d6a3d543ffcf5 100644 --- a/external-crates/move/crates/move-unit-test/Cargo.toml +++ b/external-crates/move/crates/move-unit-test/Cargo.toml @@ -34,6 +34,7 @@ move-vm-test-utils.workspace = true move-binary-format.workspace = true move-model.workspace = true move-bytecode-utils.workspace = true +move-trace-format.workspace = true bcs.workspace = true rand.workspace = true diff --git a/external-crates/move/crates/move-unit-test/src/lib.rs b/external-crates/move/crates/move-unit-test/src/lib.rs index 4b40c47cbb208..00d42c0219885 100644 --- a/external-crates/move/crates/move-unit-test/src/lib.rs +++ b/external-crates/move/crates/move-unit-test/src/lib.rs @@ -33,6 +33,7 @@ const DEFAULT_RAND_ITERS: u64 = 10; const RAND_NUM_ITERS_FLAG: &str = "rand-num-iters"; const SEED_FLAG: &str = "seed"; +const TRACE_FLAG: &str = "trace-execution"; #[derive(Debug, Parser, Clone)] #[clap(author, version, about)] @@ -121,6 +122,10 @@ pub struct UnitTestingConfig { // WARNING: You should only use this flag for debugging and meta-testing purposes! #[clap(skip)] pub deterministic_generation: bool, + + // Enable tracing for tests + #[clap(long = TRACE_FLAG, value_name = "PATH")] + pub trace_execution: Option>, } fn format_module_id( @@ -152,6 +157,7 @@ impl UnitTestingConfig { rand_num_iters: Some(DEFAULT_RAND_ITERS), seed: None, deterministic_generation: false, + trace_execution: None, } } @@ -263,6 +269,11 @@ impl UnitTestingConfig { } writeln!(shared_writer.lock().unwrap(), "Running Move unit tests")?; + let trace_location = match &self.trace_execution { + Some(None) => Some("traces".to_string()), + Some(Some(path)) => Some(path.clone()), + None => None, + }; let mut test_runner = TestRunner::new( self.gas_limit.unwrap_or(DEFAULT_EXECUTION_BOUND), self.num_threads, @@ -270,6 +281,7 @@ impl UnitTestingConfig { self.seed, rand_num_iters, self.deterministic_generation, + trace_location, test_plan, native_function_table, cost_table, diff --git a/external-crates/move/crates/move-unit-test/src/test_reporter.rs b/external-crates/move/crates/move-unit-test/src/test_reporter.rs index 71ab7ae300d27..221b2953f2bd4 100644 --- a/external-crates/move/crates/move-unit-test/src/test_reporter.rs +++ b/external-crates/move/crates/move-unit-test/src/test_reporter.rs @@ -15,9 +15,11 @@ use move_core_types::{ vm_status::{StatusCode, StatusType}, }; use move_ir_types::location::Loc; +use move_trace_format::format::MoveTrace; use std::{ collections::{BTreeMap, BTreeSet}, io::{Result, Write}, + path::Path, sync::Mutex, time::Duration, }; @@ -40,7 +42,7 @@ pub enum FailureReason { Property(String), } -#[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct TestFailure { pub test_run_info: TestRunInfo, pub vm_error: Option, @@ -48,10 +50,11 @@ pub struct TestFailure { pub prng_seed: Option, } -#[derive(Debug, Clone, Ord, PartialOrd, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct TestRunInfo { pub elapsed_time: Duration, pub instructions_executed: u64, + pub trace: Option, } type TestRuns = BTreeMap>; @@ -68,11 +71,34 @@ pub struct TestResults { test_plan: TestPlan, } +fn write_string_to_file(filepath: &str, content: &str) -> std::io::Result<()> { + let path = Path::new(filepath); + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent)?; + } + let mut file = std::fs::File::create(path)?; + file.write_all(content.as_bytes())?; + Ok(()) +} + impl TestRunInfo { - pub fn new(elapsed_time: Duration, instructions_executed: u64) -> Self { + pub fn new( + elapsed_time: Duration, + instructions_executed: u64, + trace: Option, + ) -> Self { Self { elapsed_time, instructions_executed, + trace, + } + } + + pub fn save_trace(&self, path: &str) -> Result<()> { + if let Some(trace) = &self.trace { + write_string_to_file(path, &format!("{}", trace.to_json())) + } else { + Ok(()) } } } diff --git a/external-crates/move/crates/move-unit-test/src/test_runner.rs b/external-crates/move/crates/move-unit-test/src/test_runner.rs index dd386ac52bf2f..42310d40cece8 100644 --- a/external-crates/move/crates/move-unit-test/src/test_runner.rs +++ b/external-crates/move/crates/move-unit-test/src/test_runner.rs @@ -30,6 +30,7 @@ use move_core_types::{ u256::U256, vm_status::StatusCode, }; +use move_trace_format::format::MoveTraceBuilder; use move_vm_runtime::{move_vm::MoveVM, native_functions::NativeFunctionTable}; use move_vm_test_utils::{ gas_schedule::{unit_cost_schedule, CostTable, Gas, GasStatus}, @@ -51,6 +52,7 @@ pub struct SharedTestingConfig { prng_seed: Option, num_iters: u64, deterministic_generation: bool, + trace_location: Option, } pub struct TestRunner { @@ -112,6 +114,7 @@ impl TestRunner { prng_seed: Option, num_iters: u64, deterministic_generation: bool, + trace_location: Option, tests: TestPlan, // TODO: maybe we should require the clients to always pass in a list of native functions so // we don't have to make assumptions about their gas parameters. @@ -143,6 +146,7 @@ impl TestRunner { prng_seed, num_iters, deterministic_generation, + trace_location, }, num_threads, tests, @@ -247,6 +251,14 @@ impl SharedTestingConfig { ) { let move_vm = MoveVM::new(self.native_function_table.clone()).unwrap(); let extensions = extensions::new_extensions(); + + let mut move_tracer = MoveTraceBuilder::new(); + let tracer = if self.trace_location.is_some() { + Some(&mut move_tracer) + } else { + None + }; + let mut session = move_vm.new_session_with_extensions(&self.starting_storage_state, extensions); let mut gas_meter = GasStatus::new(&self.cost_table, Gas::new(self.execution_bound)); @@ -260,15 +272,16 @@ impl SharedTestingConfig { } // TODO: collect VM logs if the verbose flag (i.e, `self.verbose`) is set - let now = Instant::now(); - let serialized_return_values_result = session.execute_function_bypass_visibility( - &test_plan.module_id, - IdentStr::new(function_name).unwrap(), - vec![], // no ty args, at least for now - serialize_values(arguments.iter()), - &mut gas_meter, - ); + let serialized_return_values_result = session + .execute_function_bypass_visibility_with_tracer_if_enabled( + &test_plan.module_id, + IdentStr::new(function_name).unwrap(), + vec![], // no ty args, at least for now + serialize_values(arguments.iter()), + &mut gas_meter, + tracer, + ); let mut return_result = serialized_return_values_result.map(|res| { res.return_values .into_iter() @@ -280,6 +293,11 @@ impl SharedTestingConfig { err.remove_exec_state(); } } + let trace = if self.trace_location.is_some() { + Some(move_tracer.into_trace()) + } else { + None + }; let test_run_info = TestRunInfo::new( now.elapsed(), // TODO(Gas): This doesn't look quite right... @@ -288,6 +306,7 @@ impl SharedTestingConfig { .checked_sub(gas_meter.remaining_gas()) .unwrap() .into(), + trace, ); match session.finish_with_extensions().0 { Ok((cs, extensions)) => (Ok(cs), Ok(extensions), return_result, test_run_info), @@ -407,6 +426,25 @@ impl SharedTestingConfig { let (_cs_result, _ext_result, exec_result, test_run_info) = self.execute_via_move_vm(test_plan, function_name, arguments); + // Save the trace -- one per test -- for each test that we have traced (and if tracing is + // enabled). + if let Some(location) = &self.trace_location { + let trace_file_location = format!( + "{}/{}__{}{}.json", + location, + format_module_id(output.test_info, &output.test_plan.module_id).replace("::", "__"), + function_name, + if let Some(seed) = prng_seed { + format!("_seed_{}", seed) + } else { + "".to_string() + } + ); + if let Err(e) = test_run_info.save_trace(&trace_file_location) { + eprintln!("Unable to save trace to {trace_file_location} -- {:?}", e); + } + } + match exec_result { Err(err) => { let sub_status = err.sub_status().and_then(|status| { diff --git a/external-crates/move/crates/move-vm-integration-tests/Cargo.toml b/external-crates/move/crates/move-vm-integration-tests/Cargo.toml index 1bad20330a8c8..2a74255a839f7 100644 --- a/external-crates/move/crates/move-vm-integration-tests/Cargo.toml +++ b/external-crates/move/crates/move-vm-integration-tests/Cargo.toml @@ -37,7 +37,6 @@ gas-profiler = [ "move-vm-config/gas-profiler", "move-vm-runtime/gas-profiler", "move-vm-profiler/gas-profiler", - "move-vm-types/gas-profiler", "move-vm-test-utils/gas-profiler", ] diff --git a/external-crates/move/crates/move-vm-integration-tests/src/tests/compatibility_tests.rs b/external-crates/move/crates/move-vm-integration-tests/src/tests/compatibility_tests.rs index 9462ae21ad370..edfecbe94a62b 100644 --- a/external-crates/move/crates/move-vm-integration-tests/src/tests/compatibility_tests.rs +++ b/external-crates/move/crates/move-vm-integration-tests/src/tests/compatibility_tests.rs @@ -199,10 +199,7 @@ fn test_enum_upgrade_add_variant_at_front() { } ", ); - let mut compat = Compatibility::default(); - assert!(compat.disallow_new_variants); - assert!(compat.check(&old, &new).is_err()); - compat.disallow_new_variants = false; + let compat = Compatibility::default(); assert!(compat.check(&old, &new).is_err()); assert!(InclusionCheck::Equal.check(&old, &new).is_err()); assert!(InclusionCheck::Subset.check(&old, &new).is_err()); @@ -226,12 +223,9 @@ fn test_enum_upgrade_add_variant_at_end() { } ", ); - let mut compat = Compatibility::default(); - assert!(compat.disallow_new_variants); + let compat = Compatibility::default(); assert!(compat.check(&old, &new).is_err()); // Allow adding new variants at the end of the enum - compat.disallow_new_variants = false; - assert!(compat.check(&old, &new).is_ok()); assert!(InclusionCheck::Equal.check(&old, &new).is_err()); // NOTE: We currently restrict all enums (even in subset mode) so that new enum variants are not // allowed. This assertion will fail when we allow new enum variants in subset mode and should diff --git a/external-crates/move/crates/move-vm-profiler/src/lib.rs b/external-crates/move/crates/move-vm-profiler/src/lib.rs index 9f21f99cd66e5..3312383e1b052 100644 --- a/external-crates/move/crates/move-vm-profiler/src/lib.rs +++ b/external-crates/move/crates/move-vm-profiler/src/lib.rs @@ -261,7 +261,7 @@ macro_rules! profile_open_frame_impl { if let Some(profiler) = $profiler { if let Some(config) = &profiler.config { let name = if !config.use_long_function_name { - GasProfiler::short_name(&$frame_name) + $crate::GasProfiler::short_name(&$frame_name) } else { $frame_name }; @@ -295,7 +295,7 @@ macro_rules! profile_close_frame_impl { if let Some(profiler) = $profiler { if let Some(config) = &profiler.config { let name = if !config.use_long_function_name { - GasProfiler::short_name(&$frame_name) + $crate::GasProfiler::short_name(&$frame_name) } else { $frame_name }; diff --git a/external-crates/move/crates/move-vm-runtime/Cargo.toml b/external-crates/move/crates/move-vm-runtime/Cargo.toml index aa26f0f895111..17e49de4c0a46 100644 --- a/external-crates/move/crates/move-vm-runtime/Cargo.toml +++ b/external-crates/move/crates/move-vm-runtime/Cargo.toml @@ -26,6 +26,7 @@ move-vm-config.workspace = true move-vm-types.workspace = true move-binary-format.workspace = true move-vm-profiler.workspace = true +move-trace-format.workspace = true [dev-dependencies] anyhow.workspace = true @@ -44,6 +45,5 @@ testing = [] lazy_natives = [] gas-profiler = [ "move-vm-config/gas-profiler", - "move-vm-types/gas-profiler", "move-vm-profiler/gas-profiler", ] diff --git a/external-crates/move/crates/move-vm-runtime/src/interpreter.rs b/external-crates/move/crates/move-vm-runtime/src/interpreter.rs index 7eb672c08de21..eb4d600faaa95 100644 --- a/external-crates/move/crates/move-vm-runtime/src/interpreter.rs +++ b/external-crates/move/crates/move-vm-runtime/src/interpreter.rs @@ -3,9 +3,11 @@ // SPDX-License-Identifier: Apache-2.0 use crate::{ + close_frame, close_initial_frame, close_instruction, loader::{Function, Loader, Resolver}, native_functions::NativeContext, - trace, + open_frame, open_initial_frame, open_instruction, trace, + tracing2::tracer::VMTracer, }; use fail::fail_point; use move_binary_format::{ @@ -20,11 +22,6 @@ use move_core_types::{ vm_status::{StatusCode, StatusType}, }; use move_vm_config::runtime::VMRuntimeLimitsConfig; -#[cfg(feature = "gas-profiler")] -use move_vm_profiler::GasProfiler; -use move_vm_profiler::{ - profile_close_frame, profile_close_instr, profile_open_frame, profile_open_instr, -}; use move_vm_types::{ data_store::DataStore, gas::{GasMeter, SimpleInstruction}, @@ -84,9 +81,9 @@ enum InstrRet { /// It mimics execution on a single thread, with an call stack and an operand stack. pub(crate) struct Interpreter { /// Operand stack, where Move `Value`s are stored for stack operations. - operand_stack: Stack, + pub(crate) operand_stack: Stack, /// The stack of active functions. - call_stack: CallStack, + pub(crate) call_stack: CallStack, /// Limits imposed at runtime runtime_limits_config: VMRuntimeLimitsConfig, /// List of captured call traces @@ -125,6 +122,7 @@ impl Interpreter { gas_meter: &mut impl GasMeter, extensions: &mut NativeContextExtensions, loader: &Loader, + tracer: &mut Option>, ) -> VMResult { let mut interpreter = Interpreter { operand_stack: Stack::new(), @@ -133,7 +131,16 @@ impl Interpreter { call_traces: Vec::new(), values_cache: HashMap::new(), }; - profile_open_frame!(gas_meter, function.pretty_string()); + + open_initial_frame!( + tracer, + &args, + &ty_args, + &function, + loader, + gas_meter, + data_store.link_context() + ); let values = if function.is_native() { for arg in args { @@ -157,12 +164,13 @@ impl Interpreter { e.at_code_offset(function.index(), 0) .finish(Location::Module(function.module_id().clone())) })?; - profile_close_frame!(gas_meter, function.pretty_string()); + + close_initial_frame!(tracer, &function, &return_values, gas_meter); return_values.into_iter().collect() } else { interpreter.execute_main( - loader, data_store, gas_meter, extensions, function, ty_args, args, + loader, data_store, gas_meter, extensions, function, ty_args, args, tracer, )? }; @@ -187,6 +195,7 @@ impl Interpreter { function: Arc, ty_args: Vec, args: Vec, + tracer: &mut Option>, ) -> VMResult> { let mut locals = Locals::new(function.local_count()); for (i, value) in args.into_iter().enumerate() { @@ -210,9 +219,8 @@ impl Interpreter { loop { let resolver = current_frame.resolver(link_context, loader); - - let execution_result = current_frame - .execute_code(&resolver, self, gas_meter) + let execution_result = current_frame //self + .execute_code(&resolver, self, gas_meter, tracer) .map_err(|err| self.maybe_core_dump(err, ¤t_frame)); let gas_used_after_call = gas_meter.charged_already_total().unwrap(); @@ -259,7 +267,15 @@ impl Interpreter { err })?; - profile_close_frame!(gas_meter, current_frame.function.pretty_string()); + close_frame!( + tracer, + ¤t_frame, + ¤t_frame.function, + &self, + &loader, + gas_meter, + link_context + ); if let Some(frame) = self.call_stack.pop() { // Note: the caller will find the callee's return values at the top of the shared operand stack @@ -272,9 +288,16 @@ impl Interpreter { } ExitCode::Call(fh_idx) => { let func = resolver.function_from_handle(fh_idx); - #[cfg(feature = "gas-profiler")] - let func_name = func.pretty_string(); - profile_open_frame!(gas_meter, func_name.clone()); + open_frame!( + tracer, + &[], + &func, + ¤t_frame, + &self, + &loader, + gas_meter, + link_context + ); // Charge gas let module_id = func.module_id(); @@ -284,17 +307,26 @@ impl Interpreter { func.name(), self.operand_stack .last_n(func.arg_count()) - .map_err(|e| set_err_info!(current_frame, e))?, + .map_err(|e| set_err_info!(current_frame, e.clone()))?, (func.local_count() as u64).into(), ) - .map_err(|e| set_err_info!(current_frame, e))?; + .map_err(|e| set_err_info!(current_frame, e.clone()))?; if func.is_native() { + let func_clone = func.clone(); self.call_native(&resolver, gas_meter, extensions, func, vec![])?; current_frame.pc += 1; // advance past the Call instruction in the caller - profile_close_frame!(gas_meter, func_name.clone()); + close_frame!( + tracer, + ¤t_frame, + &func_clone, + &self, + &loader, + gas_meter, + link_context + ); continue; } let frame = self @@ -313,11 +345,18 @@ impl Interpreter { // TODO(Gas): We should charge gas as we do type substitution... let ty_args = resolver .instantiate_generic_function(idx, current_frame.ty_args()) - .map_err(|e| set_err_info!(current_frame, e))?; + .map_err(|e| set_err_info!(current_frame, e.clone()))?; let func = resolver.function_from_instantiation(idx); - #[cfg(feature = "gas-profiler")] - let func_name = func.pretty_string(); - profile_open_frame!(gas_meter, func_name.clone()); + open_frame!( + tracer, + &ty_args, + &func, + ¤t_frame, + &self, + &loader, + gas_meter, + link_context + ); // Charge gas let module_id = func.module_id(); @@ -328,15 +367,24 @@ impl Interpreter { ty_args.iter().map(|ty| TypeWithLoader { ty, loader }), self.operand_stack .last_n(func.arg_count()) - .map_err(|e| set_err_info!(current_frame, e))?, + .map_err(|e| set_err_info!(current_frame, e.clone()))?, (func.local_count() as u64).into(), ) - .map_err(|e| set_err_info!(current_frame, e))?; + .map_err(|e| set_err_info!(current_frame, e.clone()))?; if func.is_native() { + let func_clone = func.clone(); self.call_native(&resolver, gas_meter, extensions, func, ty_args)?; current_frame.pc += 1; // advance past the Call instruction in the caller - profile_close_frame!(gas_meter, func_name.clone()); + close_frame!( + tracer, + ¤t_frame, + &func_clone, + &self, + &loader, + gas_meter, + link_context + ); continue; } @@ -766,8 +814,8 @@ const OPERAND_STACK_SIZE_LIMIT: usize = 1024; const CALL_STACK_SIZE_LIMIT: usize = 1024; /// The operand stack. -struct Stack { - value: Vec, +pub(crate) struct Stack { + pub(crate) value: Vec, } impl Stack { @@ -825,7 +873,7 @@ impl Stack { /// A call stack. // #[derive(Debug)] -struct CallStack(Vec); +pub(crate) struct CallStack(pub(crate) Vec); impl CallStack { /// Create a new empty call stack. @@ -861,12 +909,12 @@ impl CallStack { /// A `Frame` is the execution context for a function. It holds the locals of the function and /// the function itself. // #[derive(Debug)] -struct Frame { - pc: u16, - locals: Locals, - function: Arc, - ty_args: Vec, - call_trace_idx: usize, +pub(crate) struct Frame { + pub(crate) pc: u16, + pub(crate) locals: Locals, + pub(crate) function: Arc, + pub(crate) ty_args: Vec, + pub(crate) call_trace_idx: usize, } fn new_call_trace( @@ -1009,8 +1057,9 @@ impl Frame { resolver: &Resolver, interpreter: &mut Interpreter, gas_meter: &mut impl GasMeter, + tracer: &mut Option>, ) -> VMResult { - self.execute_code_impl(resolver, interpreter, gas_meter) + self.execute_code_impl(resolver, interpreter, gas_meter, tracer) .map_err(|e| { let e = if resolver.loader().vm_config().error_execution_state { e.with_exec_state(interpreter.get_internal_state()) @@ -1566,11 +1615,13 @@ impl Frame { Ok(InstrRet::Ok) } + #[allow(unused_variables)] fn execute_code_impl( &mut self, resolver: &Resolver, interpreter: &mut Interpreter, gas_meter: &mut impl GasMeter, + tracer: &mut Option>, ) -> PartialVMResult { let code = self.function.code(); loop { @@ -1592,7 +1643,14 @@ impl Frame { ) }); - profile_open_instr!(gas_meter, format!("{:?}", instruction)); + open_instruction!( + tracer, + instruction, + self, + interpreter, + resolver.loader(), + gas_meter + ); let r = Self::execute_instruction( &mut self.pc, @@ -1603,11 +1661,19 @@ impl Frame { interpreter, gas_meter, instruction, - )?; + ); - profile_close_instr!(gas_meter, format!("{:?}", instruction)); + close_instruction!( + tracer, + instruction, + self, + interpreter, + resolver.loader(), + gas_meter, + r.as_ref().err() + ); - match r { + match r? { InstrRet::Ok => (), InstrRet::ExitCode(exit_code) => { return Ok(exit_code); diff --git a/external-crates/move/crates/move-vm-runtime/src/lib.rs b/external-crates/move/crates/move-vm-runtime/src/lib.rs index 5aadd80fb1899..c69f0d150582d 100644 --- a/external-crates/move/crates/move-vm-runtime/src/lib.rs +++ b/external-crates/move/crates/move-vm-runtime/src/lib.rs @@ -21,6 +21,7 @@ pub mod runtime; pub mod session; #[macro_use] mod tracing; +mod tracing2; // Only include debugging functionality in debug builds #[cfg(any(debug_assertions, feature = "debugging"))] diff --git a/external-crates/move/crates/move-vm-runtime/src/loader.rs b/external-crates/move/crates/move-vm-runtime/src/loader.rs index 4634f9730d488..bffc555dbbaba 100644 --- a/external-crates/move/crates/move-vm-runtime/src/loader.rs +++ b/external-crates/move/crates/move-vm-runtime/src/loader.rs @@ -1339,7 +1339,7 @@ impl Loader { // Return an instantiated type given a generic and an instantiation. // Stopgap to avoid a recursion that is either taking too long or using too // much memory - fn subst(&self, ty: &Type, ty_args: &[Type]) -> PartialVMResult { + pub(crate) fn subst(&self, ty: &Type, ty_args: &[Type]) -> PartialVMResult { // Before instantiating the type, count the # of nodes of all type arguments plus // existing type instantiation. // If that number is larger than MAX_TYPE_INSTANTIATION_NODES, refuse to construct this type. @@ -1357,6 +1357,14 @@ impl Loader { ty.subst(ty_args) } + pub(crate) fn make_type( + &self, + module: &CompiledModule, + tok: &SignatureToken, + ) -> PartialVMResult { + self.module_cache.read().make_type(module, tok) + } + // Verify the kind (constraints) of an instantiation. // Function invocations call this function to verify correctness of type arguments provided fn verify_ty_args<'a, I>(&self, constraints: I, ty_args: &[Type]) -> PartialVMResult<()> @@ -1386,7 +1394,7 @@ impl Loader { self.module_cache.read().function_at(idx) } - fn get_module( + pub(crate) fn get_module( &self, link_context: AccountAddress, runtime_id: &ModuleId, diff --git a/external-crates/move/crates/move-vm-runtime/src/runtime.rs b/external-crates/move/crates/move-vm-runtime/src/runtime.rs index fedf0925679b5..b3656f597a3f2 100644 --- a/external-crates/move/crates/move-vm-runtime/src/runtime.rs +++ b/external-crates/move/crates/move-vm-runtime/src/runtime.rs @@ -9,6 +9,7 @@ use crate::{ native_extensions::NativeContextExtensions, native_functions::{NativeFunction, NativeFunctions}, session::{LoadedFunctionInstantiation, SerializedReturnValues, Session}, + tracing2::tracer::VMTracer, }; use move_binary_format::{ errors::{verification_error, Location, PartialVMError, PartialVMResult, VMResult}, @@ -25,6 +26,7 @@ use move_core_types::{ runtime_value::MoveTypeLayout, vm_status::StatusCode, }; +use move_trace_format::format::MoveTraceBuilder; use move_vm_config::runtime::VMConfig; use move_vm_types::{ data_store::DataStore, @@ -320,6 +322,7 @@ impl VMRuntime { data_store: &mut impl DataStore, gas_meter: &mut impl GasMeter, extensions: &mut NativeContextExtensions, + tracer: &mut Option>, ) -> VMResult { let arg_types = param_types .into_iter() @@ -351,6 +354,7 @@ impl VMRuntime { gas_meter, extensions, &self.loader, + tracer, )?; let serialized_return_values = self @@ -392,6 +396,7 @@ impl VMRuntime { gas_meter: &mut impl GasMeter, extensions: &mut NativeContextExtensions, bypass_declared_entry_check: bool, + tracer: Option<&mut MoveTraceBuilder>, ) -> VMResult { use move_binary_format::file_format::SignatureIndex; fn check_is_entry( @@ -443,6 +448,7 @@ impl VMRuntime { data_store, gas_meter, extensions, + &mut tracer.map(VMTracer::new), ) } @@ -512,6 +518,7 @@ impl VMRuntime { gas_meter, extensions, bypass_declared_entry_check, + None, ) } diff --git a/external-crates/move/crates/move-vm-runtime/src/session.rs b/external-crates/move/crates/move-vm-runtime/src/session.rs index 7f2d539ed5874..b20694120a5b2 100644 --- a/external-crates/move/crates/move-vm-runtime/src/session.rs +++ b/external-crates/move/crates/move-vm-runtime/src/session.rs @@ -20,6 +20,7 @@ use move_core_types::{ runtime_value::MoveTypeLayout, trace::CallTrace, }; +use move_trace_format::format::MoveTraceBuilder; use move_vm_types::{ data_store::DataStore, gas::GasMeter, @@ -90,6 +91,7 @@ impl<'r, 'l, S: MoveResolver> Session<'r, 'l, S> { gas_meter, &mut self.native_extensions, bypass_declared_entry_check, + None, ) } @@ -122,6 +124,46 @@ impl<'r, 'l, S: MoveResolver> Session<'r, 'l, S> { gas_meter, &mut self.native_extensions, bypass_declared_entry_check, + None, + ) + } + + pub fn execute_function_bypass_visibility_with_tracer_if_enabled( + &mut self, + module: &ModuleId, + function_name: &IdentStr, + ty_args: Vec, + args: Vec>, + gas_meter: &mut impl GasMeter, + tracer: Option<&mut MoveTraceBuilder>, + ) -> VMResult { + move_vm_profiler::gas_profiler_feature_enabled! { + use move_vm_profiler::GasProfiler; + if gas_meter.get_profiler_mut().is_none() { + gas_meter.set_profiler(GasProfiler::init_default_cfg( + function_name.to_string(), + gas_meter.remaining_gas().into(), + )); + } + } + + let tracer = if cfg!(feature = "gas-profiler") { + tracer + } else { + None + }; + + let bypass_declared_entry_check = true; + self.runtime.execute_function( + module, + function_name, + ty_args, + args, + &mut self.data_cache, + gas_meter, + &mut self.native_extensions, + bypass_declared_entry_check, + tracer, ) } diff --git a/external-crates/move/crates/move-vm-runtime/src/tracing2/mod.rs b/external-crates/move/crates/move-vm-runtime/src/tracing2/mod.rs new file mode 100644 index 0000000000000..5a29145c8ba4f --- /dev/null +++ b/external-crates/move/crates/move-vm-runtime/src/tracing2/mod.rs @@ -0,0 +1,107 @@ +pub(crate) mod tracer; + +#[cfg(feature = "gas-profiler")] +pub(crate) const TRACING_ENABLED: bool = true; + +#[cfg(not(feature = "gas-profiler"))] +pub(crate) const TRACING_ENABLED: bool = false; + +#[macro_export] +macro_rules! open_initial_frame { + ($tracer: expr, $args: expr, $ty_args: expr, $function: expr, $loader: expr, $gas_meter: expr, $link_context: expr) => { + if $crate::tracing2::TRACING_ENABLED { + $tracer.as_mut().map(|tracer| { + tracer.open_initial_frame( + $args, + $ty_args, + $function, + $loader, + $gas_meter.remaining_gas().into(), + $link_context, + ) + }); + move_vm_profiler::profile_open_frame!($gas_meter, $function.pretty_string()); + } + }; +} + +#[macro_export] +macro_rules! close_initial_frame { + ($tracer: expr, $function: expr, $return_values: expr, $gas_meter: expr) => { + if $crate::tracing2::TRACING_ENABLED { + $tracer.as_mut().map(|tracer| { + tracer.close_initial_frame($return_values, $gas_meter.remaining_gas().into()) + }); + move_vm_profiler::profile_close_frame!($gas_meter, $function.pretty_string()); + } + }; +} + +#[macro_export] +macro_rules! close_frame { + ($tracer: expr, $frame: expr, $function: expr, $interp: expr, $loader: expr, $gas_meter: expr, $link_context: expr) => { + if $crate::tracing2::TRACING_ENABLED { + $tracer.as_mut().map(|tracer| { + tracer.close_frame( + $frame, + $function, + $interp, + $loader, + $gas_meter.remaining_gas().into(), + $link_context, + ) + }); + move_vm_profiler::profile_close_frame!($gas_meter, $function.pretty_string()); + } + }; +} + +#[macro_export] +macro_rules! open_frame { + ($tracer: expr, $ty_args: expr, $function: expr, $calling_frame: expr, $interp: expr, $loader: expr, $gas_meter: expr, $link_context: expr) => { + if $crate::tracing2::TRACING_ENABLED { + $tracer.as_mut().map(|tracer| { + tracer.open_frame( + $ty_args, + $function, + $calling_frame, + $interp, + $loader, + $gas_meter.remaining_gas().into(), + $link_context, + ) + }); + move_vm_profiler::profile_open_frame!($gas_meter, $function.pretty_string()); + } + }; +} + +#[macro_export] +macro_rules! open_instruction { + ($tracer: expr, $instruction: expr, $frame: expr, $interp: expr, $loader: expr, $gas_meter: expr) => { + if $crate::tracing2::TRACING_ENABLED { + $tracer.as_mut().map(|tracer| { + tracer.open_instruction($frame, $interp, $loader, $gas_meter.remaining_gas().into()) + }); + move_vm_profiler::profile_open_instr!($gas_meter, format!("{:?}", $instruction)); + } + }; +} + +#[macro_export] +macro_rules! close_instruction { + ($tracer: expr, $instruction: expr, $frame: expr, $interp: expr, $loader: expr, $gas_meter: expr, $result: expr) => { + if $crate::tracing2::TRACING_ENABLED { + $tracer.as_mut().map(|tracer| { + tracer.close_instruction( + $frame, + $interp, + $loader, + $gas_meter.remaining_gas().into(), + $result, + ) + }); + move_vm_profiler::profile_close_instr!($gas_meter, format!("{:?}", $instruction)); + } + }; +} diff --git a/external-crates/move/crates/move-vm-runtime/src/tracing2/tracer.rs b/external-crates/move/crates/move-vm-runtime/src/tracing2/tracer.rs new file mode 100644 index 0000000000000..f58baf63b0d68 --- /dev/null +++ b/external-crates/move/crates/move-vm-runtime/src/tracing2/tracer.rs @@ -0,0 +1,1738 @@ +// Copyright (c) The Move Contributors +// SPDX-License-Identifier: Apache-2.0 + +use crate::{ + interpreter::{Frame, Interpreter}, + loader::{Function, Loader}, +}; +use move_binary_format::{ + errors::PartialVMError, + file_format::{ConstantPoolIndex, SignatureIndex}, +}; +use move_core_types::{ + account_address::AccountAddress, + annotated_value::{MoveTypeLayout, MoveValue}, + language_storage::TypeTag, +}; +use move_trace_format::format::{ + DataLoad, Effect as EF, Location, MoveTraceBuilder, Read, RefType, TraceIndex, TraceValue, + TypeTagWithRefs, Write, +}; +use move_vm_types::{loaded_data::runtime_types::Type, values::Value}; +use std::collections::BTreeMap; + +/// Internal state for the tracer. This is where the actual tracing logic is implemented. +pub(crate) struct VMTracer<'a> { + trace: &'a mut MoveTraceBuilder, + link_context: Option, + pc: Option, + active_frames: BTreeMap, + type_stack: Vec, + loaded_data: BTreeMap, + effects: Vec, +} + +/// Information about a frame that we keep during trace building +#[derive(Debug, Clone)] +struct FrameInfo { + frame_identifier: TraceIndex, + is_native: bool, + locals_types: Vec, + return_types: Vec, +} + +/// A type tag, and the move type layout and reference information for that type if it is +/// computable without error. Due to runtime value depth restrictions you can have a valid type +/// whose type layout is not computable at runtime without error. +#[derive(Debug, Clone)] +struct TagWithLayoutInfoOpt { + tag: TypeTag, + layout: (Option, Option), +} + +// Information about a function that we use for trace building +// All types are fully substituted +#[derive(Debug, Clone)] +struct FunctionTypeInfo { + ty_args: Vec, + local_types: Vec, + return_types: Vec, +} + +/// A runtime location can refer to the stack to make it easier to refer to values on the stack and +/// resolving them. However, the stack is not a valid location for a reference and all references +/// are rooted in a local or global so the Trace `Location` does not include the stack, and +/// only `Local`, `Global`, and `Indexed` locations. +#[derive(Debug, Clone)] +enum RuntimeLocation { + Stack(usize), + Local(TraceIndex, usize), + Indexed(Box, usize), + Global(TraceIndex), +} + +/// The reference information for a local. This is used to track the state of a local in a frame. +/// * It can be a value, in which case the reference type is `Value`. +/// * It can be a local that does not currently hold a value (is "empty"), in which case +/// we track the reference type and the type of the local, but we don't have a `RuntimeLocation` +/// for the reference. This is e.g., the case when we open a frame and the local is not +/// initialized yet. +/// * It can be a local that holds a value (is "filled"), in which case we track the reference type and the +/// location the reference resolves to. +#[derive(Debug, Clone)] +enum ReferenceType { + Value, + Empty { + ref_type: RefType, + }, + Filled { + ref_type: RefType, + location: RuntimeLocation, + }, +} + +/// A `RootedType` is a a type layout with reference information, where any reference type is +/// fully rooted back to a specific location. +#[derive(Debug, Clone)] +struct RootedType { + layout: MoveTypeLayout, + ref_type: Option<(RefType, RuntimeLocation)>, +} + +/// A `LocalType` layout where a reference type may not be rooted to a +/// specific location (or it may be rooted to a specific location if the location is filled with a +/// value at the time). Note the type layout may be `None` in the case where the type is not +/// calculable at runtime without error. +#[derive(Debug, Clone)] +struct LocalType { + layout: Option, + ref_type: ReferenceType, +} + +impl TagWithLayoutInfoOpt { + pub fn as_tag_with_refs(&self) -> TypeTagWithRefs { + TypeTagWithRefs { + type_: self.tag.clone(), + ref_type: self.layout.1.clone(), + } + } +} + +impl RuntimeLocation { + fn as_trace_location(&self) -> Location { + match self { + RuntimeLocation::Stack(_) => { + panic!("Cannot convert stack location to trace location") + } + RuntimeLocation::Local(fidx, lidx) => Location::Local(*fidx, *lidx), + RuntimeLocation::Indexed(loc, idx) => { + Location::Indexed(Box::new(loc.as_trace_location()), *idx) + } + RuntimeLocation::Global(id) => Location::Global(*id), + } + } + + fn as_runtime_location(loc: Location) -> Self { + match loc { + Location::Local(fidx, lidx) => RuntimeLocation::Local(fidx, lidx), + Location::Indexed(loc, idx) => { + RuntimeLocation::Indexed(Box::new(RuntimeLocation::as_runtime_location(*loc)), idx) + } + Location::Global(id) => RuntimeLocation::Global(id), + } + } +} + +impl LocalType { + fn into_rooted_type(self) -> Option { + let ref_type = match self.ref_type { + ReferenceType::Value => None, + ReferenceType::Empty { .. } => panic!("Empty reference type"), + ReferenceType::Filled { ref_type, location } => Some((ref_type, location)), + }; + Some(RootedType { + layout: self.layout?, + ref_type, + }) + } +} + +impl RootedType { + fn into_local_type(self) -> LocalType { + let ref_type = match self.ref_type { + None => ReferenceType::Value, + Some((ref_type, location)) => ReferenceType::Filled { ref_type, location }, + }; + LocalType { + layout: Some(self.layout), + ref_type, + } + } +} + +impl<'a> VMTracer<'a> { + /// Emit an error event to the trace if `true` + fn emit_trace_error_if_err(&mut self, is_err: bool) { + if is_err { + self.trace.effect(EF::ExecutionError( + "!! TRACING ERROR !! Events below this may be incorrect.".to_string(), + )); + } + } + + fn current_frame(&self) -> Option<&FrameInfo> { + self.active_frames.last_key_value().map(|(_, v)| v) + } + + fn current_frame_mut(&mut self) -> Option<&mut FrameInfo> { + self.active_frames.last_entry().map(|e| e.into_mut()) + } + + /// Get the current locals type and reference state(s) + fn current_frame_locals(&self) -> Option<&[LocalType]> { + Some(self.current_frame()?.locals_types.as_slice()) + } + + /// Return the current frame identifier. This is trace index of the frame and is used to + /// identify reference locations rooted higher up the call stack. + fn current_frame_identifier(&self) -> Option { + Some(self.current_frame()?.frame_identifier) + } + + /// Given the trace index for a frame, return the index of the frame in the call stack. + fn trace_index_to_frame_index(&self, idx: TraceIndex) -> Option { + self.active_frames + .range(..=idx) + .enumerate() + .last() + .map(|(i, _)| i) + } + + /// Register the pre-effects for the instruction (i.e., reads, pops.) + fn register_pre_effects(&mut self, effects: Vec) { + assert!(self.effects.is_empty()); + self.effects = effects; + } + + /// Register the post-effects for the instruction (i.e., pushes, writes) and return the total + /// effects for the instruction. + fn register_post_effects(&mut self, effects: Vec) -> Vec { + self.effects.extend(effects); + std::mem::take(&mut self.effects) + } + + /// Insert a local with a specifice runtime location into the current frame. + fn insert_local(&mut self, local_index: usize, local: RootedType) -> Option<()> { + *self + .current_frame_mut()? + .locals_types + .get_mut(local_index)? = local.into_local_type(); + Some(()) + } + + /// Invalidate a local in the current frame. This is used to mark a local as uninitialized and + /// remove its reference information. + fn invalidate_local(&mut self, local_index: usize) -> Option<()> { + let local = self + .current_frame_mut()? + .locals_types + .get_mut(local_index)?; + match &local.ref_type { + ReferenceType::Filled { ref_type, .. } => { + local.ref_type = ReferenceType::Empty { + ref_type: ref_type.clone(), + } + } + ReferenceType::Empty { .. } => (), + ReferenceType::Value => (), + }; + Some(()) + } + + /// Resolve a value on the stack to a TraceValue. References are fully rooted all the way back + /// to their location in a local. + fn resolve_stack_value( + &self, + frame: Option<&Frame>, + interpreter: &Interpreter, + stack_idx: usize, + ) -> Option { + if stack_idx >= interpreter.operand_stack.value.len() { + return None; + } + let offset = self.type_stack.len() - 1; + self.resolve_location( + &RuntimeLocation::Stack(offset - stack_idx), + frame, + interpreter, + ) + } + + /// Resolve a value in a local to a TraceValue. References are fully rooted all the way back to + /// their root location in a local. + fn resolve_local( + &self, + frame: &Frame, + interpreter: &Interpreter, + local_index: usize, + ) -> Option { + self.resolve_location( + &RuntimeLocation::Local(self.current_frame_identifier()?, local_index), + Some(frame), + interpreter, + ) + } + + /// Shared utility function that creates a TraceValue from a runtime location along with + /// grabbing the snapshot of the value. + fn make_trace_value( + &self, + location: RuntimeLocation, + ref_info: Option, + frame: Option<&Frame>, + interpreter: &Interpreter, + ) -> Option { + let value = self.root_location_snapshot(&location, frame, interpreter)?; + Some(match ref_info { + Some(RefType::Imm) => TraceValue::ImmRef { + location: location.as_trace_location(), + snapshot: Box::new(value), + }, + Some(RefType::Mut) => TraceValue::MutRef { + location: location.as_trace_location(), + snapshot: Box::new(value), + }, + None => TraceValue::RuntimeValue { value }, + }) + } + + /// Given a location, resolve it to the value it points to or the value itself in the case + /// where it's not a reference. + fn resolve_location( + &self, + loc: &RuntimeLocation, + frame: Option<&Frame>, + interpreter: &Interpreter, + ) -> Option { + Some(match loc { + RuntimeLocation::Stack(sidx) => { + let ty = self.type_stack.get(*sidx)?; + let ref_ty = ty.ref_type.as_ref().map(|(r, _)| r.clone()); + let location = ty + .ref_type + .as_ref() + .map(|(_, l)| l.clone()) + .unwrap_or_else(|| loc.clone()); + self.make_trace_value(location, ref_ty, frame, interpreter)? + } + RuntimeLocation::Local(fidx, lidx) => { + let ty = &self.active_frames.get(fidx)?.locals_types.get(*lidx)?; + let ref_ty = match &ty.ref_type { + ReferenceType::Value => None, + ReferenceType::Empty { ref_type } => Some(ref_type.clone()), + ReferenceType::Filled { ref_type, .. } => Some(ref_type.clone()), + }; + let location = match &ty.ref_type { + ReferenceType::Filled { location, .. } => location.clone(), + ReferenceType::Value => loc.clone(), + _ => panic!( + "We tried to access a local that was not initialized at {:?}", + loc + ), + }; + self.make_trace_value(location, ref_ty, frame, interpreter)? + } + RuntimeLocation::Indexed(location, _) => { + self.resolve_location(location, frame, interpreter)? + } + RuntimeLocation::Global(id) => self.loaded_data.get(id)?.clone(), + }) + } + + /// Snapshot the value at the root of a location. This is used to create the value snapshots + /// for TraceValue references. + fn root_location_snapshot( + &self, + loc: &RuntimeLocation, + frame: Option<&Frame>, + interpreter: &Interpreter, + ) -> Option { + Some(match loc { + RuntimeLocation::Local(fidx, loc_idx) => { + let local_ty = self + .active_frames + .get(fidx)? + .locals_types + .get(*loc_idx)? + .clone(); + let call_stack_index = self.trace_index_to_frame_index(*fidx)?; + match local_ty.ref_type { + ReferenceType::Value => { + let frame = if call_stack_index >= interpreter.call_stack.0.len() { + frame? + } else { + interpreter.call_stack.0.get(call_stack_index)? + }; + frame + .locals + .copy_loc(*loc_idx) + .ok()? + .as_annotated_move_value_for_tracing_only(&local_ty.layout?)? + } + ReferenceType::Empty { .. } => { + panic!("We tried to access a local that was not initialized") + } + ReferenceType::Filled { location, .. } => { + self.root_location_snapshot(&location, frame, interpreter)? + } + } + } + RuntimeLocation::Stack(stack_idx) => { + let ty = self.type_stack.get(*stack_idx)?; + match &ty.ref_type { + Some((_, location)) => { + self.root_location_snapshot(location, frame, interpreter)? + } + None => { + let value = interpreter.operand_stack.value.get(*stack_idx)?; + value.as_annotated_move_value_for_tracing_only(&ty.layout)? + } + } + } + RuntimeLocation::Indexed(loc, _) => { + self.root_location_snapshot(loc, frame, interpreter)? + } + RuntimeLocation::Global(id) => self.loaded_data.get(id)?.snapshot().clone(), + }) + } + + fn link_context(&self) -> AccountAddress { + self.link_context + .expect("Link context always set by this point") + } + + /// Load data returned by a native function into the tracer state. + /// We also emit a data load event for the data loaded from the native function. + fn load_data( + &mut self, + layout: &MoveTypeLayout, + reftype: &Option, + value: &Value, + ) -> Option<(RefType, RuntimeLocation)> { + let value = value.as_annotated_move_value_for_tracing_only(layout)?; + + let Some(ref_type) = reftype else { + return None; + }; + + // We treat any references coming out of a native as global reference. + // This generally works fine as long as you don't have a native function returning a + // mutable reference within a mutable reference passed-in. + let id = self.trace.current_trace_offset(); + + let location = RuntimeLocation::Global(id); + + self.trace.effect(EF::DataLoad(DataLoad { + ref_type: ref_type.clone(), + location: location.as_trace_location(), + snapshot: value.clone(), + })); + let trace_value = match &ref_type { + RefType::Imm => TraceValue::ImmRef { + location: location.as_trace_location(), + snapshot: Box::new(value), + }, + RefType::Mut => TraceValue::MutRef { + location: location.as_trace_location(), + snapshot: Box::new(value), + }, + }; + self.loaded_data.insert(id, trace_value); + Some((ref_type.clone(), location)) + } + + /// Handle (and load) any data returned by a native function. + fn handle_native_return( + &mut self, + function: &Function, + interpreter: &Interpreter, + ) -> Option<()> { + assert!(function.is_native()); + let trace_frame = self.current_frame()?.clone(); + assert!(trace_frame.is_native); + let len = interpreter.operand_stack.value.len(); + for (i, r_ty) in trace_frame.return_types.iter().cloned().enumerate() { + let r_ty = r_ty.layout; + let ref_type = self.load_data( + r_ty.0.as_ref()?, + &r_ty.1, + interpreter.operand_stack.value.get(len - i - 1)?, + ); + self.type_stack.push(RootedType { + layout: r_ty.0?, + ref_type, + }); + } + Some(()) + } + + //--------------------------------------------------------------------------- + // Core entry points for the tracer + //--------------------------------------------------------------------------- + + fn open_initial_frame_( + &mut self, + args: &[Value], + ty_args: &[Type], + function: &Function, + loader: &Loader, + remaining_gas: u64, + link_context: AccountAddress, + ) -> Option<()> { + self.link_context = Some(link_context); + + let function_type_info = FunctionTypeInfo::new(function, loader, ty_args, link_context)?; + + assert!(function_type_info.local_types.len() == function.local_count()); + + let call_args: Vec<_> = args + .iter() + .zip(function_type_info.local_types.iter().cloned()) + .map(|(value, tag_with_layout_info_opt)| { + let (layout, ref_type) = tag_with_layout_info_opt.layout; + let move_value = value.as_annotated_move_value_for_tracing_only(&layout?)?; + assert!(ref_type.is_none()); + Some(TraceValue::RuntimeValue { value: move_value }) + }) + .collect::>()?; + + let locals_types = function_type_info + .local_types + .iter() + .cloned() + .map(|tag_with_layout_info_opt| { + let (layout, ref_type) = tag_with_layout_info_opt.layout; + LocalType { + layout, + ref_type: ref_type + .map(|r_type| match r_type { + RefType::Imm => ReferenceType::Empty { ref_type: r_type }, + RefType::Mut => ReferenceType::Empty { ref_type: r_type }, + }) + .unwrap_or(ReferenceType::Value), + } + }) + .collect(); + + let current_trace_offset = self.trace.current_trace_offset(); + self.active_frames.insert( + current_trace_offset, + FrameInfo { + frame_identifier: current_trace_offset, + is_native: function.is_native(), + locals_types, + return_types: function_type_info.return_types.clone(), + }, + ); + + self.trace.open_frame( + self.current_frame_identifier()?, + function.index(), + function.name().to_string(), + function.module_id().clone(), + call_args, + function_type_info.ty_args, + function_type_info + .return_types + .iter() + .map(|tag_with_layout_info_opt| tag_with_layout_info_opt.as_tag_with_refs()) + .collect(), + function_type_info + .local_types + .into_iter() + .map(|tag_with_layout_info_opt| tag_with_layout_info_opt.as_tag_with_refs()) + .collect(), + function.is_native(), + remaining_gas, + ); + Some(()) + } + + fn close_initial_frame_(&mut self, return_values: &[Value], remaining_gas: u64) -> Option<()> { + let current_frame_return_tys = self.current_frame()?.return_types.clone(); + let return_values: Vec<_> = return_values + .iter() + .zip(current_frame_return_tys.into_iter()) + .map(|(value, tag_with_layout_info_opt)| { + let (layout, ref_type) = tag_with_layout_info_opt.layout; + let move_value = value.as_annotated_move_value_for_tracing_only(&layout?)?; + assert!(ref_type.is_none()); + Some(TraceValue::RuntimeValue { value: move_value }) + }) + .collect::>()?; + self.trace.close_frame( + self.current_frame_identifier()?, + return_values, + remaining_gas, + ); + self.active_frames + .pop_last() + .expect("Unbalanced frame close"); + Some(()) + } + + fn open_frame_( + &mut self, + ty_args: &[Type], + function: &Function, + calling_frame: &Frame, + interpreter: &Interpreter, + loader: &Loader, + remaining_gas: u64, + link_context: AccountAddress, + ) -> Option<()> { + self.link_context = Some(link_context); + + let call_args = (0..function.arg_count()) + .rev() + .map(|i| self.resolve_stack_value(Some(calling_frame), interpreter, i)) + .collect::>>()?; + + let call_args_types = self + .type_stack + .split_off(self.type_stack.len() - function.arg_count()); + let function_type_info = FunctionTypeInfo::new(function, loader, ty_args, link_context)?; + + let locals_types = function_type_info + .local_types + .iter() + .cloned() + .enumerate() + .map(|(i, tag_with_layout_info_opt)| { + // For any arguments, start them out with the correct locations + if let Some(a_layout) = call_args_types.get(i).cloned() { + let ref_type = match a_layout.ref_type { + Some((ref_type, location)) => ReferenceType::Filled { ref_type, location }, + None => ReferenceType::Value, + }; + LocalType { + layout: Some(a_layout.layout), + ref_type, + } + } else { + let (layout, ref_type) = tag_with_layout_info_opt.layout; + let ref_type = ref_type + .map(|ref_type| ReferenceType::Empty { ref_type }) + .unwrap_or(ReferenceType::Value); + LocalType { layout, ref_type } + } + }) + .collect(); + + let current_trace_offset = self.trace.current_trace_offset(); + self.active_frames.insert( + current_trace_offset, + FrameInfo { + frame_identifier: current_trace_offset, + is_native: function.is_native(), + locals_types, + return_types: function_type_info.return_types.clone(), + }, + ); + + self.trace.open_frame( + self.current_frame_identifier()?, + function.index(), + function.name().to_string(), + function.module_id().clone(), + call_args, + function_type_info.ty_args, + function_type_info + .return_types + .iter() + .map(|tag_with_layout_info_opt| tag_with_layout_info_opt.as_tag_with_refs()) + .collect(), + function_type_info + .local_types + .into_iter() + .map(|tag_with_layout_info_opt| tag_with_layout_info_opt.as_tag_with_refs()) + .collect(), + function.is_native(), + remaining_gas, + ); + Some(()) + } + + fn close_frame_( + &mut self, + frame: &Frame, + function: &Function, + interpreter: &Interpreter, + _loader: &Loader, + remaining_gas: u64, + _link_context: AccountAddress, + ) -> Option<()> { + if function.is_native() { + self.handle_native_return(function, interpreter) + .expect("Native function return failed -- this should not happen."); + } + + let return_values = (0..function.return_type_count()) + .rev() + .map(|i| self.resolve_stack_value(Some(frame), interpreter, i)) + .collect::>>()?; + + // Note that when a native function frame closes the values returned by the native function + // are all pushed on the operand stack. + if function.is_native() { + for val in &return_values { + self.trace.effect(EF::Push(val.clone())); + } + } + + self.trace.close_frame( + self.current_frame_identifier()?, + return_values, + remaining_gas, + ); + self.active_frames + .pop_last() + .expect("Unbalanced frame close"); + Some(()) + } + + fn open_instruction_( + &mut self, + frame: &Frame, + interpreter: &Interpreter, + loader: &Loader, + _remaining_gas: u64, + ) -> Option<()> { + use move_binary_format::file_format::Bytecode as B; + + let pc = frame.pc; + self.pc = Some(pc); + + let popn = |n: usize| { + let mut effects = vec![]; + for i in 0..n { + let v = self.resolve_stack_value(Some(frame), interpreter, i)?; + effects.push(EF::Pop(v)); + } + Some(effects) + }; + + assert_eq!( + self.type_stack.len(), + interpreter.operand_stack.value.len(), + "Type stack and operand stack must be the same length {} {}", + frame.function.name(), + pc, + ); + + match &frame.function.code()[pc as usize] { + B::Nop + | B::Branch(_) + | B::Ret + | B::LdU8(_) + | B::LdU16(_) + | B::LdU32(_) + | B::LdU64(_) + | B::LdU128(_) + | B::LdU256(_) + | B::LdFalse + | B::LdTrue + | B::LdConst(_) => { + self.register_pre_effects(vec![]); + } + B::MutBorrowField(_) + | B::ImmBorrowField(_) + | B::MutBorrowFieldGeneric(_) + | B::ImmBorrowFieldGeneric(_) + | B::FreezeRef + | B::Not + | B::Abort + | B::Unpack(_) + | B::UnpackGeneric(_) + | B::CastU8 + | B::CastU16 + | B::CastU32 + | B::CastU64 + | B::CastU128 + | B::CastU256 + | B::Pop + | B::BrTrue(_) + | B::BrFalse(_) + | B::VecUnpack(_, _) + | B::VecLen(_) + | B::VecPopBack(_) + | B::VariantSwitch(_) + | B::UnpackVariantImmRef(_) + | B::UnpackVariantMutRef(_) + | B::UnpackVariantGenericImmRef(_) + | B::UnpackVariantGenericMutRef(_) + | B::UnpackVariant(_) + | B::UnpackVariantGeneric(_) => { + self.register_pre_effects(popn(1)?); + } + B::Add + | B::Sub + | B::Mul + | B::Mod + | B::Div + | B::BitOr + | B::BitAnd + | B::Xor + | B::Shl + | B::Shr + | B::Lt + | B::Gt + | B::Le + | B::Ge + | B::Eq + | B::Neq + | B::Or + | B::And + | B::WriteRef + | B::VecImmBorrow(_) + | B::VecMutBorrow(_) + | B::VecPushBack(_) => self.register_pre_effects(popn(2)?), + B::VecSwap(_) => self.register_pre_effects(popn(3)?), + B::VecPack(_, n) => self.register_pre_effects(popn(*n as usize)?), + i @ (B::MoveLoc(l) | B::CopyLoc(l)) => { + let v = self.resolve_local(frame, interpreter, *l as usize)?; + let effects = vec![EF::Read(Read { + location: Location::Local(self.current_frame_identifier()?, *l as usize), + root_value_read: v.clone(), + moved: matches!(i, B::MoveLoc(_)), + })]; + self.register_pre_effects(effects); + } + B::StLoc(lidx) => { + let ty = self.type_stack.last()?; + let v = self.resolve_stack_value(Some(frame), interpreter, 0)?; + self.insert_local(*lidx as usize, ty.clone())?; + let effects = vec![EF::Pop(v.clone())]; + self.register_pre_effects(effects); + } + B::ImmBorrowLoc(l_idx) | B::MutBorrowLoc(l_idx) => { + let val = self.resolve_local(frame, interpreter, *l_idx as usize)?; + let location = Location::Local(self.current_frame_identifier()?, *l_idx as usize); + self.register_pre_effects(vec![EF::Read(Read { + location, + root_value_read: val, + moved: false, + })]); + } + // Handled by open frame + B::Call(_) | B::CallGeneric(_) => {} + B::Pack(sidx) => { + let resolver = frame.function.get_resolver(self.link_context(), loader); + let field_count = resolver.field_count(*sidx) as usize; + self.register_pre_effects(popn(field_count)?); + } + B::PackGeneric(sidx) => { + let resolver = frame.function.get_resolver(self.link_context(), loader); + let field_count = resolver.field_instantiation_count(*sidx) as usize; + self.register_pre_effects(popn(field_count)?); + } + B::PackVariant(vidx) => { + let resolver = frame.function.get_resolver(self.link_context(), loader); + let (field_count, _variant_tag) = resolver.variant_field_count_and_tag(*vidx); + self.register_pre_effects(popn(field_count as usize)?); + } + B::PackVariantGeneric(vidx) => { + let resolver = frame.function.get_resolver(self.link_context(), loader); + let (field_count, _variant_tag) = + resolver.variant_instantiantiation_field_count_and_tag(*vidx); + self.register_pre_effects(popn(field_count as usize)?); + } + B::ReadRef => { + let ref_value = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let location = ref_value.location()?.clone(); + let runtime_location = RuntimeLocation::as_runtime_location(location.clone()); + let value = self.resolve_location(&runtime_location, Some(frame), interpreter)?; + self.register_pre_effects(vec![ + EF::Pop(ref_value), + EF::Read(Read { + location, + root_value_read: value.clone(), + moved: false, + }), + ]); + } + + B::ExistsDeprecated(_) + | B::ExistsGenericDeprecated(_) + | B::MoveFromDeprecated(_) + | B::MoveFromGenericDeprecated(_) + | B::MoveToDeprecated(_) + | B::MoveToGenericDeprecated(_) + | B::MutBorrowGlobalDeprecated(_) + | B::MutBorrowGlobalGenericDeprecated(_) + | B::ImmBorrowGlobalDeprecated(_) + | B::ImmBorrowGlobalGenericDeprecated(_) => unreachable!(), + } + Some(()) + } + + fn close_instruction_( + &mut self, + frame: &Frame, + interpreter: &Interpreter, + loader: &Loader, + remaining_gas: u64, + ) -> Option<()> { + use move_binary_format::file_format::Bytecode as B; + + // NB: Do _not_ use the frames pc here, as it will be incremented by the interpreter to the + // next instruction already. + let pc = self + .pc + .expect("PC always set by this point by `open_instruction`"); + + // NB: At the start of this function (i.e., at this point) the operand stack in the VM, and + // the type stack in the tracer are _out of sync_. This is because the VM has already + // executed the instruction and we now need to manage the type transition of the + // instruction along with snapshoting the effects of the instruction's execution. + let instruction = &frame.function.code()[pc as usize]; + match instruction { + B::Pop | B::BrTrue(_) | B::BrFalse(_) => { + self.type_stack.pop()?; + let effects = self.register_post_effects(vec![]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::Branch(_) | B::Ret => { + let effects = self.register_post_effects(vec![]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + i @ (B::LdU8(_) + | B::LdU16(_) + | B::LdU32(_) + | B::LdU64(_) + | B::LdU128(_) + | B::LdU256(_) + | B::LdFalse + | B::LdTrue + | B::LdConst(_)) => { + let layout = match i { + B::LdU8(_) => MoveTypeLayout::U8, + B::LdU16(_) => MoveTypeLayout::U16, + B::LdU32(_) => MoveTypeLayout::U32, + B::LdU64(_) => MoveTypeLayout::U64, + B::LdU128(_) => MoveTypeLayout::U128, + B::LdU256(_) => MoveTypeLayout::U256, + B::LdTrue => MoveTypeLayout::Bool, + B::LdFalse => MoveTypeLayout::Bool, + B::LdConst(const_idx) => get_constant_type_layout( + &frame.function, + loader, + self.link_context(), + *const_idx, + )?, + _ => unreachable!(), + }; + let a_layout = RootedType { + layout, + ref_type: None, + }; + self.type_stack.push(a_layout); + + let value = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = vec![EF::Push(value)]; + let effects = self.register_post_effects(effects); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + i @ (B::MoveLoc(l) | B::CopyLoc(l)) => { + let local_annot_type = self + .current_frame_locals()? + .get(*l as usize)? + .clone() + .into_rooted_type()?; + self.type_stack.push(local_annot_type); + if matches!(i, B::MoveLoc(_)) { + self.invalidate_local(*l as usize)?; + } + // This was pushed on the stack during execution so read it off from there. + let v = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(v.clone())]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + i @ (B::CastU8 | B::CastU16 | B::CastU32 | B::CastU64 | B::CastU128 | B::CastU256) => { + let layout = match i { + B::CastU8 => MoveTypeLayout::U8, + B::CastU16 => MoveTypeLayout::U16, + B::CastU32 => MoveTypeLayout::U32, + B::CastU64 => MoveTypeLayout::U64, + B::CastU128 => MoveTypeLayout::U128, + B::CastU256 => MoveTypeLayout::U256, + _ => unreachable!(), + }; + let annot_layout = RootedType { + layout, + ref_type: None, + }; + self.type_stack.pop()?; + self.type_stack.push(annot_layout); + + let value = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = vec![EF::Push(value.clone())]; + let effects = self.register_post_effects(effects); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::StLoc(lidx) => { + let ty = self.type_stack.pop()?; + self.insert_local(*lidx as usize, ty.clone())?; + let v = self.resolve_local(frame, interpreter, *lidx as usize)?; + let effects = self.register_post_effects(vec![EF::Write(Write { + location: Location::Local(self.current_frame_identifier()?, *lidx as usize), + root_value_after_write: v.clone(), + })]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::Add + | B::Sub + | B::Mul + | B::Mod + | B::Div + | B::BitOr + | B::BitAnd + | B::Xor + | B::Shl + | B::Shr => { + self.type_stack.pop()?; + // NB in the case of shift left and shift right the second operand is the resultant + // value type. + let a_ty = self.type_stack.pop()?; + self.type_stack.push(a_ty); + + let result = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(result)]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::Lt | B::Gt | B::Le | B::Ge => { + self.type_stack.pop()?; + self.type_stack.pop()?; + let a_layout = RootedType { + layout: MoveTypeLayout::Bool, + ref_type: None, + }; + self.type_stack.push(a_layout); + + let value = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(value)]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::Call(_) | B::CallGeneric(_) => { + // NB: We don't register effects for calls as they will be handled by + // open_frame. + self.trace + .instruction(instruction, vec![], vec![], remaining_gas, pc); + } + B::Pack(sidx) => { + let resolver = frame.function.get_resolver(self.link_context(), loader); + let field_count = resolver.field_count(*sidx) as usize; + let struct_type = resolver.get_struct_type(*sidx); + let stack_len = self.type_stack.len(); + let _ = self.type_stack.split_off(stack_len - field_count); + let ty = loader.type_to_fully_annotated_layout(&struct_type).ok()?; + let a_layout = RootedType { + layout: ty, + ref_type: None, + }; + self.type_stack.push(a_layout); + + let value = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(value)]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::PackGeneric(sidx) => { + let resolver = frame.function.get_resolver(self.link_context(), loader); + let field_count = resolver.field_instantiation_count(*sidx) as usize; + let struct_type = resolver + .instantiate_struct_type(*sidx, &frame.ty_args) + .ok()?; + let stack_len = self.type_stack.len(); + let _ = self.type_stack.split_off(stack_len - field_count); + let ty = loader.type_to_fully_annotated_layout(&struct_type).ok()?; + let a_layout = RootedType { + layout: ty, + ref_type: None, + }; + self.type_stack.push(a_layout); + + let value = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(value)]); + let TypeTag::Struct(s_type) = loader.type_to_type_tag(&struct_type).ok()? else { + panic!("Expected struct, got {:#?}", struct_type); + }; + self.trace + .instruction(instruction, s_type.type_params, effects, remaining_gas, pc); + } + B::Unpack(_) | B::UnpackGeneric(_) => { + let ty = self.type_stack.pop()?; + let MoveTypeLayout::Struct(s) = ty.layout else { + panic!("Expected struct, got {:#?}", ty.layout); + }; + let field_tys = s.fields.iter().map(|t| t.layout.clone()); + for field_ty in field_tys { + self.type_stack.push(RootedType { + layout: field_ty.clone(), + ref_type: None, + }); + } + + let mut effects = vec![]; + for i in (0..s.fields.len()).rev() { + let value = self.resolve_stack_value(Some(frame), interpreter, i)?; + effects.push(EF::Push(value)); + } + + let effects = self.register_post_effects(effects); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::Eq | B::Neq => { + self.type_stack.pop()?; + self.type_stack.pop()?; + let a_layout = RootedType { + layout: MoveTypeLayout::Bool, + ref_type: None, + }; + self.type_stack.push(a_layout); + let value = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(value)]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::Or | B::And => { + self.type_stack.pop()?; + self.type_stack.pop()?; + let a_layout = RootedType { + layout: MoveTypeLayout::Bool, + ref_type: None, + }; + self.type_stack.push(a_layout); + let value = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(value)]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::Not => { + let a_ty = self.type_stack.pop()?; + self.type_stack.push(a_ty); + let value = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(value)]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::Nop => { + self.trace + .instruction(instruction, vec![], vec![], remaining_gas, pc); + } + B::Abort => { + self.type_stack.pop()?; + let effects = self.register_post_effects(vec![]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::ReadRef => { + let ref_ty = self.type_stack.pop()?; + let a_layout = RootedType { + layout: ref_ty.layout.clone(), + ref_type: None, + }; + self.type_stack.push(a_layout); + + let value = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(value)]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + i @ (B::ImmBorrowLoc(l_idx) | B::MutBorrowLoc(l_idx)) => { + let non_imm_ty = self.current_frame_locals()?.get(*l_idx as usize)?.clone(); + let ref_type = match i { + B::ImmBorrowLoc(_) => RefType::Imm, + B::MutBorrowLoc(_) => RefType::Mut, + _ => unreachable!(), + }; + let a_layout = RootedType { + layout: non_imm_ty.layout?.clone(), + ref_type: Some(( + ref_type, + RuntimeLocation::Local(self.current_frame_identifier()?, *l_idx as usize), + )), + }; + self.type_stack.push(a_layout); + + let val = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(val)]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::WriteRef => { + let reference_ty = self.type_stack.pop()?; + let _value_ty = self.type_stack.pop()?; + let location = reference_ty.ref_type.as_ref()?.1.clone(); + let root_value_after_write = self + .resolve_location(&location, Some(frame), interpreter)? + .clone(); + let effects = self.register_post_effects(vec![EF::Write(Write { + location: location.as_trace_location(), + root_value_after_write, + })]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::FreezeRef => { + let mut reference_ty = self.type_stack.pop()?; + reference_ty.ref_type.as_mut()?.0 = RefType::Imm; + self.type_stack.push(reference_ty); + let reference_val = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(reference_val)]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + i @ (B::MutBorrowField(fhidx) | B::ImmBorrowField(fhidx)) => { + let value_ty = self.type_stack.pop()?; + + let MoveTypeLayout::Struct(slayout) = &value_ty.layout else { + panic!("Expected struct, got {:?}", value_ty.layout) + }; + let resolver = frame.function.get_resolver(self.link_context(), loader); + let field_offset = resolver.field_offset(*fhidx); + let field_layout = slayout.fields.get(field_offset)?.layout.clone(); + + let location = value_ty.ref_type.as_ref()?.1.clone(); + let field_location = + RuntimeLocation::Indexed(Box::new(location.clone()), field_offset); + + let ref_type = match i { + B::MutBorrowField(_) => RefType::Mut, + B::ImmBorrowField(_) => RefType::Imm, + _ => unreachable!(), + }; + let a_layout = RootedType { + layout: field_layout, + ref_type: Some((ref_type, field_location)), + }; + self.type_stack.push(a_layout); + let value = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(value)]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + i @ (B::MutBorrowFieldGeneric(fhidx) | B::ImmBorrowFieldGeneric(fhidx)) => { + let value_ty = self.type_stack.pop()?; + + let MoveTypeLayout::Struct(slayout) = &value_ty.layout else { + panic!("Expected struct, got {:?}", value_ty.layout) + }; + let resolver = frame.function.get_resolver(self.link_context(), loader); + let field_offset = resolver.field_instantiation_offset(*fhidx); + let field_layout = slayout.fields.get(field_offset)?.layout.clone(); + let location = value_ty.ref_type.as_ref()?.1.clone(); + let field_location = + RuntimeLocation::Indexed(Box::new(location.clone()), field_offset); + + let ref_type = match i { + B::MutBorrowFieldGeneric(_) => RefType::Mut, + B::ImmBorrowFieldGeneric(_) => RefType::Imm, + _ => unreachable!(), + }; + let a_layout = RootedType { + layout: field_layout, + ref_type: Some((ref_type, field_location)), + }; + self.type_stack.push(a_layout); + let value = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(value)]); + let ty_args = slayout.type_.type_params.clone(); + self.trace + .instruction(instruction, ty_args, effects, remaining_gas, pc); + } + + B::VecPack(tok, n) => { + let resolver = frame.function.get_resolver(self.link_context(), loader); + let ty = resolver + .instantiate_single_type(*tok, &frame.ty_args) + .ok()?; + let ty = loader.type_to_fully_annotated_layout(&ty).ok()?; + let ty = MoveTypeLayout::Vector(Box::new(ty)); + let stack_len = self.type_stack.len(); + let _ = self.type_stack.split_off(stack_len - *n as usize); + let a_layout = RootedType { + layout: ty, + ref_type: None, + }; + self.type_stack.push(a_layout); + let val = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(val)]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + i @ (B::VecImmBorrow(_) | B::VecMutBorrow(_)) => { + let ref_type = match i { + B::VecImmBorrow(_) => RefType::Imm, + B::VecMutBorrow(_) => RefType::Mut, + _ => unreachable!(), + }; + self.type_stack.pop()?; + let ref_ty = self.type_stack.pop()?; + let MoveTypeLayout::Vector(ty) = ref_ty.layout else { + panic!("Expected vector, got {:?}", ref_ty.layout,); + }; + let EF::Pop(TraceValue::RuntimeValue { + value: MoveValue::U64(i), + }) = &self.effects[0] + else { + unreachable!(); + }; + let location = + RuntimeLocation::Indexed(Box::new(ref_ty.ref_type?.1.clone()), *i as usize); + let a_layout = RootedType { + layout: (*ty).clone(), + ref_type: Some((ref_type, location)), + }; + self.type_stack.push(a_layout); + let val = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(val)]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::VecLen(_) => { + self.type_stack.pop()?; + let a_layout = RootedType { + layout: MoveTypeLayout::U64, + ref_type: None, + }; + self.type_stack.push(a_layout); + let len = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(len)]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::VecPushBack(_) => { + self.type_stack.pop()?; + self.type_stack.pop()?; + let EF::Pop(reference_val) = &self.effects[1] else { + unreachable!(); + }; + let location = reference_val.location()?.clone(); + let runtime_location = RuntimeLocation::as_runtime_location(location.clone()); + let snap = self.resolve_location(&runtime_location, Some(frame), interpreter)?; + let effects = self.register_post_effects(vec![EF::Write(Write { + location, + root_value_after_write: snap, + })]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::VecPopBack(_) => { + let reference_ty = self.type_stack.pop()?; + let MoveTypeLayout::Vector(ty) = reference_ty.layout else { + panic!("Expected vector, got {:?}", reference_ty.layout); + }; + let a_layout = RootedType { + layout: (*ty).clone(), + ref_type: None, + }; + self.type_stack.push(a_layout); + let v = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(v)]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::VecUnpack(_, n) => { + let ty = self.type_stack.pop()?; + let MoveTypeLayout::Vector(ty) = ty.layout else { + panic!("Expected vector, got {:?}", ty.layout); + }; + for _ in 0..*n { + let a_layout = RootedType { + layout: (*ty).clone(), + ref_type: None, + }; + self.type_stack.push(a_layout); + } + let mut effects = vec![]; + for i in (0..*n).rev() { + let value = self.resolve_stack_value(Some(frame), interpreter, i as usize)?; + effects.push(EF::Push(value)); + } + let effects = self.register_post_effects(effects); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::VecSwap(_) => { + self.type_stack.pop()?; + self.type_stack.pop()?; + let v_ref = self.type_stack.pop()?; + let location = v_ref.ref_type.as_ref()?.1.clone(); + let snap = self.resolve_location(&location, Some(frame), interpreter)?; + let effects = self.register_post_effects(vec![EF::Write(Write { + location: location.as_trace_location(), + root_value_after_write: snap, + })]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::PackVariant(vidx) => { + let resolver = frame.function.get_resolver(self.link_context(), loader); + let (field_count, _variant_tag) = resolver.variant_field_count_and_tag(*vidx); + let stack_len = self.type_stack.len(); + let _ = self.type_stack.split_off(stack_len - field_count as usize); + let ty = loader + .type_to_fully_annotated_layout(&resolver.get_enum_type(*vidx)) + .ok()?; + let a_layout = RootedType { + layout: ty, + ref_type: None, + }; + self.type_stack.push(a_layout); + let val = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(val)]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::PackVariantGeneric(vidx) => { + let resolver = frame.function.get_resolver(self.link_context(), loader); + let (field_count, _variant_tag) = + resolver.variant_instantiantiation_field_count_and_tag(*vidx); + let stack_len = self.type_stack.len(); + let _ = self.type_stack.split_off(stack_len - field_count as usize); + let ty = loader + .type_to_fully_annotated_layout( + &resolver.instantiate_enum_type(*vidx, &frame.ty_args).ok()?, + ) + .ok()?; + let a_layout = RootedType { + layout: ty, + ref_type: None, + }; + self.type_stack.push(a_layout); + let val = self.resolve_stack_value(Some(frame), interpreter, 0)?; + let effects = self.register_post_effects(vec![EF::Push(val)]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + i @ (B::UnpackVariant(_) | B::UnpackVariantGeneric(_)) => { + let ty = self.type_stack.pop()?; + let resolver = frame.function.get_resolver(self.link_context(), loader); + let (field_count, tag) = match i { + B::UnpackVariant(vidx) => resolver.variant_field_count_and_tag(*vidx), + B::UnpackVariantGeneric(vidx) => { + resolver.variant_instantiantiation_field_count_and_tag(*vidx) + } + _ => unreachable!(), + }; + let MoveTypeLayout::Enum(e) = ty.layout else { + panic!("Expected enum, got {:#?}", ty.layout); + }; + let variant_layout = e.variants.iter().find(|v| v.0 .1 == tag)?; + let mut effects = vec![]; + for f_layout in variant_layout.1.iter() { + let a_layout = RootedType { + layout: f_layout.layout.clone(), + ref_type: None, + }; + self.type_stack.push(a_layout); + } + for i in 0..field_count { + let value = self.resolve_stack_value(Some(frame), interpreter, i as usize)?; + effects.push(EF::Push(value)); + } + let effects = self.register_post_effects(effects); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + i @ (B::UnpackVariantImmRef(_) + | B::UnpackVariantMutRef(_) + | B::UnpackVariantGenericImmRef(_) + | B::UnpackVariantGenericMutRef(_)) => { + let ty = self.type_stack.pop()?; + let resolver = frame.function.get_resolver(self.link_context(), loader); + let ((field_count, tag), ref_type) = match i { + B::UnpackVariantImmRef(vidx) => { + (resolver.variant_field_count_and_tag(*vidx), RefType::Imm) + } + B::UnpackVariantMutRef(vidx) => { + (resolver.variant_field_count_and_tag(*vidx), RefType::Mut) + } + B::UnpackVariantGenericImmRef(vidx) => ( + resolver.variant_instantiantiation_field_count_and_tag(*vidx), + RefType::Imm, + ), + B::UnpackVariantGenericMutRef(vidx) => ( + resolver.variant_instantiantiation_field_count_and_tag(*vidx), + RefType::Mut, + ), + _ => unreachable!(), + }; + let MoveTypeLayout::Enum(e) = ty.layout else { + panic!("Expected enum, got {:#?}", ty.layout); + }; + let variant_layout = e.variants.iter().find(|v| v.0 .1 == tag)?; + let location = ty.ref_type.as_ref()?.1.clone(); + + let mut effects = vec![]; + for (i, f_layout) in variant_layout.1.iter().enumerate() { + let location = RuntimeLocation::Indexed(Box::new(location.clone()), i); + let a_layout = RootedType { + layout: f_layout.layout.clone(), + ref_type: Some((ref_type.clone(), location)), + }; + self.type_stack.push(a_layout); + } + for i in 0..field_count { + let value = self.resolve_stack_value(Some(frame), interpreter, i as usize)?; + effects.push(EF::Push(value)); + } + let effects = self.register_post_effects(effects); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::VariantSwitch(_) => { + self.type_stack.pop()?; + let effects = self.register_post_effects(vec![]); + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } + B::ExistsDeprecated(_) + | B::ExistsGenericDeprecated(_) + | B::MoveFromDeprecated(_) + | B::MoveFromGenericDeprecated(_) + | B::MoveToDeprecated(_) + | B::MoveToGenericDeprecated(_) + | B::MutBorrowGlobalDeprecated(_) + | B::MutBorrowGlobalGenericDeprecated(_) + | B::ImmBorrowGlobalDeprecated(_) + | B::ImmBorrowGlobalGenericDeprecated(_) => unreachable!(), + } + + // At this point the type stack and the operand stack should be in sync. + assert_eq!(self.type_stack.len(), interpreter.operand_stack.value.len()); + Some(()) + } +} + +/// The (public crate) API for the VM tracer. +impl<'a> VMTracer<'a> { + pub(crate) fn new(trace: &'a mut MoveTraceBuilder) -> Self { + Self { + trace, + link_context: None, + pc: None, + active_frames: BTreeMap::new(), + type_stack: vec![], + loaded_data: BTreeMap::new(), + effects: vec![], + } + } + + pub(crate) fn open_initial_frame( + &mut self, + args: &[Value], + ty_args: &[Type], + function: &Function, + loader: &Loader, + remaining_gas: u64, + link_context: AccountAddress, + ) { + let opt = + self.open_initial_frame_(args, ty_args, function, loader, remaining_gas, link_context); + self.emit_trace_error_if_err(opt.is_none()); + } + + pub(crate) fn close_initial_frame(&mut self, return_values: &[Value], remaining_gas: u64) { + let opt = self.close_initial_frame_(return_values, remaining_gas); + self.emit_trace_error_if_err(opt.is_none()); + } + + pub(crate) fn open_frame( + &mut self, + ty_args: &[Type], + function: &Function, + calling_frame: &Frame, + interpreter: &Interpreter, + loader: &Loader, + remaining_gas: u64, + link_context: AccountAddress, + ) { + let opt = self.open_frame_( + ty_args, + function, + calling_frame, + interpreter, + loader, + remaining_gas, + link_context, + ); + self.emit_trace_error_if_err(opt.is_none()) + } + + pub(crate) fn close_frame( + &mut self, + frame: &Frame, + function: &Function, + interpreter: &Interpreter, + loader: &Loader, + remaining_gas: u64, + link_context: AccountAddress, + ) { + let opt = self.close_frame_( + frame, + function, + interpreter, + loader, + remaining_gas, + link_context, + ); + self.emit_trace_error_if_err(opt.is_none()) + } + + pub(crate) fn open_instruction( + &mut self, + frame: &Frame, + interpreter: &Interpreter, + loader: &Loader, + remaining_gas: u64, + ) { + let opt = self.open_instruction_(frame, interpreter, loader, remaining_gas); + self.emit_trace_error_if_err(opt.is_none()); + } + + pub(crate) fn close_instruction( + &mut self, + frame: &Frame, + interpreter: &Interpreter, + loader: &Loader, + remaining_gas: u64, + err: Option<&PartialVMError>, + ) { + if self + .close_instruction_(frame, interpreter, loader, remaining_gas) + .is_none() + { + // If we fail to close the instruction, we need to emit an error event. + // This can be the case where the instruction itself failed -- e.g. with a division by + // zero, invalid cast, etc. + let error_string = match err { + Some(err) => format!("{:?}", err.major_status()), + None => "VM tracer failed to close instruction but interpreter was OK -- this is most likely a bug in the tracer".to_string(), + }; + let pc = self + .pc + .expect("PC always set by this point by `open_instruction`"); + let instruction = &frame.function.code()[pc as usize]; + let effects = self.register_post_effects(vec![EF::ExecutionError(error_string)]); + // TODO: type params here? + self.trace + .instruction(instruction, vec![], effects, remaining_gas, pc); + } else if let Some(err) = err { + self.trace + .effect(EF::ExecutionError(format!("{:?}", err.major_status()))); + } + } +} + +impl FunctionTypeInfo { + /// Resolve a function to all of its type information (type arguments, local types, and return + /// types). + fn new( + function: &Function, + loader: &Loader, + ty_args: &[Type], + link_context: AccountAddress, + ) -> Option { + // Split a `Type` into its inner type and reference type. + let deref_ty = |ty: Type| -> (Type, Option) { + match ty { + Type::Reference(r) => (*r, Some(RefType::Imm)), + Type::MutableReference(t) => (*t, Some(RefType::Mut)), + Type::TyParam(_) => unreachable!("Type parameters should be fully substituted"), + _ => (ty, None), + } + }; + + let (module, _) = loader.get_module(link_context, function.module_id()); + let fdef = module.function_def_at(function.index()); + let f_handle = module.function_handle_at(fdef.function); + let get_types_for_sig = |si: SignatureIndex| -> Option> { + let signatures = &module.signature_at(si).0; + signatures + .iter() + .map(|tok| { + let ty = loader.make_type(&module, tok).ok()?; + let subst_ty = loader.subst(&ty, ty_args).ok()?; + let (ty, ref_type) = deref_ty(subst_ty); + let tag = loader.type_to_type_tag(&ty).ok()?; + // NB: This may fail if the type represents a value greater than the max + // value depth. + let type_layout = loader.type_to_fully_annotated_layout(&ty).ok(); + let layout = (type_layout, ref_type); + Some(TagWithLayoutInfoOpt { tag, layout }) + }) + .collect::>>() + }; + let mut local_types = get_types_for_sig(f_handle.parameters)?; + + if let Some(code) = fdef.code.as_ref() { + local_types.extend(get_types_for_sig(code.locals)?); + } + + let return_types = { + let signatures = &module.signature_at(f_handle.return_).0; + signatures + .iter() + .map(|tok| { + let ty = loader.make_type(&module, tok).ok()?; + let subst_ty = loader.subst(&ty, ty_args).ok()?; + let (ty, ref_type) = deref_ty(subst_ty); + let tag = loader.type_to_type_tag(&ty).ok()?; + let type_layout = loader.type_to_fully_annotated_layout(&ty).ok(); + let layout = (type_layout, ref_type); + Some(TagWithLayoutInfoOpt { tag, layout }) + }) + .collect::>>()? + }; + + let ty_args = ty_args + .iter() + .cloned() + .map(|ty| { + let (ty, ref_type) = deref_ty(ty); + assert!(ref_type.is_none()); + loader.type_to_type_tag(&ty).ok() + }) + .collect::>()?; + + Some(FunctionTypeInfo { + ty_args, + local_types, + return_types, + }) + } +} + +/// Get the type layout of a constant. +fn get_constant_type_layout( + function: &Function, + loader: &Loader, + link_context: AccountAddress, + const_idx: ConstantPoolIndex, +) -> Option { + let (module, _loaded_module) = loader.get_module(link_context, function.module_id()); + let constant = module.constant_at(const_idx); + let ty = loader.make_type(&module, &constant.type_).ok()?; + loader.type_to_fully_annotated_layout(&ty).ok() +} diff --git a/external-crates/move/crates/move-vm-types/Cargo.toml b/external-crates/move/crates/move-vm-types/Cargo.toml index 9a3b95c811795..998ed8311c278 100644 --- a/external-crates/move/crates/move-vm-types/Cargo.toml +++ b/external-crates/move/crates/move-vm-types/Cargo.toml @@ -26,7 +26,4 @@ proptest.workspace = true [features] default = [] fuzzing = ["proptest", "move-binary-format/fuzzing"] -gas-profiler = [ - "move-vm-profiler/gas-profiler" -] diff --git a/external-crates/move/crates/move-vm-types/src/values/values_impl.rs b/external-crates/move/crates/move-vm-types/src/values/values_impl.rs index 658653010289f..f8b928cdba564 100644 --- a/external-crates/move/crates/move-vm-types/src/values/values_impl.rs +++ b/external-crates/move/crates/move-vm-types/src/values/values_impl.rs @@ -10,6 +10,7 @@ use move_binary_format::{ errors::*, file_format::{Constant, SignatureToken, VariantTag}, }; +use move_core_types::annotated_value as A; use move_core_types::{ account_address::AccountAddress, effects::Op, @@ -4465,4 +4466,141 @@ impl Value { ) -> PartialVMResult { self.0.try_as_move_value(layout) } + + pub fn as_annotated_move_value_for_tracing_only( + &self, + layout: &A::MoveTypeLayout, + ) -> Option { + self.0.as_annotated_move_value(layout) + } +} + +impl ValueImpl { + /// Converts the value to an annotated move value. This is only needed for tracing and care + /// should be taken when using this function as it can possibly inflate the size of the value. + fn as_annotated_move_value(&self, layout: &A::MoveTypeLayout) -> Option { + use move_core_types::annotated_value::MoveTypeLayout as L; + use move_core_types::annotated_value::MoveValue; + Some(match (layout, self) { + (L::U8, ValueImpl::U8(x)) => MoveValue::U8(*x), + (L::U16, ValueImpl::U16(x)) => MoveValue::U16(*x), + (L::U32, ValueImpl::U32(x)) => MoveValue::U32(*x), + (L::U64, ValueImpl::U64(x)) => MoveValue::U64(*x), + (L::U128, ValueImpl::U128(x)) => MoveValue::U128(*x), + (L::U256, ValueImpl::U256(x)) => MoveValue::U256(*x), + (L::Bool, ValueImpl::Bool(x)) => MoveValue::Bool(*x), + (L::Address, ValueImpl::Address(x)) => MoveValue::Address(*x), + (l, ValueImpl::Container(c)) => return c.as_annotated_move_value(l), + ( + _, + ValueImpl::ContainerRef( + ContainerRef::Local(c) | ContainerRef::Global { container: c, .. }, + ), + ) => return c.as_annotated_move_value(layout), + ( + _, + ValueImpl::IndexedRef(IndexedRef { + container_ref: + ContainerRef::Local(c) | ContainerRef::Global { container: c, .. }, + idx, + }), + ) => { + use Container::*; + let idx = *idx; + let res = match c { + Locals(r) | Vec(r) | Struct(r) => r.borrow()[idx].copy_value().unwrap(), + Variant(r) => r.borrow().1[idx].copy_value().unwrap(), + VecU8(r) => ValueImpl::U8(r.borrow()[idx]), + VecU16(r) => ValueImpl::U16(r.borrow()[idx]), + VecU32(r) => ValueImpl::U32(r.borrow()[idx]), + VecU64(r) => ValueImpl::U64(r.borrow()[idx]), + VecU128(r) => ValueImpl::U128(r.borrow()[idx]), + VecU256(r) => ValueImpl::U256(r.borrow()[idx]), + VecBool(r) => ValueImpl::Bool(r.borrow()[idx]), + VecAddress(r) => ValueImpl::Address(r.borrow()[idx]), + }; + return res.as_annotated_move_value(layout); + } + (_layout, _val) => return None, + }) + } } + +impl Container { + fn as_annotated_move_value(&self, layout: &A::MoveTypeLayout) -> Option { + use move_core_types::annotated_value::MoveTypeLayout as L; + Some(match (layout, self) { + (L::Enum(e_layout), Container::Variant(r)) => { + let A::MoveEnumLayout { type_, variants } = e_layout.as_ref(); + let (tag, values) = &*r.borrow(); + let tag = *tag; + let ((name, _), field_layouts) = + variants.iter().find(|((_, t), _)| *t == tag).unwrap(); + let mut fields = vec![]; + for (v, field_layout) in values.iter().zip(field_layouts) { + fields.push(( + field_layout.name.clone(), + v.as_annotated_move_value(&field_layout.layout)?, + )); + } + A::MoveValue::Variant(A::MoveVariant { + tag, + fields, + type_: type_.clone(), + variant_name: name.clone(), + }) + } + (L::Struct(struct_layout), Container::Struct(r)) => { + let mut fields = vec![]; + for (v, field_layout) in r.borrow().iter().zip(struct_layout.fields.iter()) { + fields.push(( + field_layout.name.clone(), + v.as_annotated_move_value(&field_layout.layout)?, + )); + } + A::MoveValue::Struct(A::MoveStruct::new(struct_layout.type_.clone(), fields)) + } + + (L::Vector(inner_layout), c) => A::MoveValue::Vector(match c { + Container::VecU8(r) => r.borrow().iter().map(|u| A::MoveValue::U8(*u)).collect(), + Container::VecU16(r) => r.borrow().iter().map(|u| A::MoveValue::U16(*u)).collect(), + Container::VecU32(r) => r.borrow().iter().map(|u| A::MoveValue::U32(*u)).collect(), + Container::VecU64(r) => r.borrow().iter().map(|u| A::MoveValue::U64(*u)).collect(), + Container::VecU128(r) => { + r.borrow().iter().map(|u| A::MoveValue::U128(*u)).collect() + } + Container::VecU256(r) => { + r.borrow().iter().map(|u| A::MoveValue::U256(*u)).collect() + } + Container::VecBool(r) => { + r.borrow().iter().map(|u| A::MoveValue::Bool(*u)).collect() + } + Container::VecAddress(r) => r + .borrow() + .iter() + .map(|u| A::MoveValue::Address(*u)) + .collect(), + Container::Vec(r) => r + .borrow() + .iter() + .map(|v| v.as_annotated_move_value(inner_layout)) + .collect::>()?, + Container::Struct(_) | Container::Variant { .. } | Container::Locals(_) => { + return None + } + }), + + (L::Signer, Container::Struct(r)) => { + let v = r.borrow(); + if v.len() != 1 { + return None; + } + match &v[0] { + ValueImpl::Address(a) => A::MoveValue::Signer(*a), + _ => return None, + } + } + (_layout, _val) => return None, + }) + } +} \ No newline at end of file diff --git a/external-crates/move/move-execution/v0/crates/move-vm-runtime/Cargo.toml b/external-crates/move/move-execution/v0/crates/move-vm-runtime/Cargo.toml index a01547fa798a6..4ef1818e16839 100644 --- a/external-crates/move/move-execution/v0/crates/move-vm-runtime/Cargo.toml +++ b/external-crates/move/move-execution/v0/crates/move-vm-runtime/Cargo.toml @@ -44,5 +44,4 @@ lazy_natives = [] gas-profiler = [ "move-vm-config/gas-profiler", "move-vm-profiler/gas-profiler", - "move-vm-types/gas-profiler", ] diff --git a/external-crates/move/move-execution/v1/crates/move-vm-runtime/Cargo.toml b/external-crates/move/move-execution/v1/crates/move-vm-runtime/Cargo.toml index 4a8f2a5c0fff4..5fa8584679fb3 100644 --- a/external-crates/move/move-execution/v1/crates/move-vm-runtime/Cargo.toml +++ b/external-crates/move/move-execution/v1/crates/move-vm-runtime/Cargo.toml @@ -43,6 +43,5 @@ testing = [] lazy_natives = [] gas-profiler = [ "move-vm-config/gas-profiler", - "move-vm-types/gas-profiler", "move-vm-profiler/gas-profiler", ] diff --git a/external-crates/move/move-execution/v2/crates/move-vm-runtime/Cargo.toml b/external-crates/move/move-execution/v2/crates/move-vm-runtime/Cargo.toml index 6fd0a85cbea32..b7c15e1bba673 100644 --- a/external-crates/move/move-execution/v2/crates/move-vm-runtime/Cargo.toml +++ b/external-crates/move/move-execution/v2/crates/move-vm-runtime/Cargo.toml @@ -43,6 +43,5 @@ testing = [] lazy_natives = [] gas-profiler = [ "move-vm-config/gas-profiler", - "move-vm-types/gas-profiler", "move-vm-profiler/gas-profiler", ] diff --git a/external-crates/tests.sh b/external-crates/tests.sh index efa26a5bc4695..7fa5a0531ff9a 100755 --- a/external-crates/tests.sh +++ b/external-crates/tests.sh @@ -3,3 +3,5 @@ echo "Running Move tests in external-crates" cd move echo "Excluding prover Move tests" cargo nextest run -E '!package(move-prover) and !test(prove) and !test(run_all::simple_build_with_docs/args.txt) and !test(run_test::nested_deps_bad_parent/Move.toml)' --workspace --no-fail-fast +echo "Running tracing-specific tests" +cargo nextest run -p move-cli --features gas-profiler diff --git a/narwhal/network/src/connectivity.rs b/narwhal/network/src/connectivity.rs deleted file mode 100644 index dbd9e9526f8bb..0000000000000 --- a/narwhal/network/src/connectivity.rs +++ /dev/null @@ -1,381 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -use crate::metrics::NetworkConnectionMetrics; -use anemo::types::PeerEvent; -use anemo::PeerId; -use dashmap::DashMap; -use futures::future; -use mysten_metrics::spawn_logged_monitored_task; -use quinn_proto::ConnectionStats; -use std::collections::HashMap; -use std::sync::Arc; -use std::time::Duration; -use tokio::task::JoinHandle; -use tokio::time; -use types::ConditionalBroadcastReceiver; - -const CONNECTION_STAT_COLLECTION_INTERVAL: Duration = Duration::from_secs(60); - -#[derive(Eq, PartialEq, Clone, Debug)] -pub enum ConnectionStatus { - Connected, - Disconnected, -} - -pub struct ConnectionMonitor { - network: anemo::NetworkRef, - connection_metrics: NetworkConnectionMetrics, - peer_id_types: HashMap, - connection_statuses: Arc>, - rx_shutdown: Option, -} - -impl ConnectionMonitor { - #[must_use] - pub fn spawn( - network: anemo::NetworkRef, - connection_metrics: NetworkConnectionMetrics, - peer_id_types: HashMap, - rx_shutdown: Option, - ) -> (JoinHandle<()>, Arc>) { - let connection_statuses_outer = Arc::new(DashMap::new()); - let connection_statuses = connection_statuses_outer.clone(); - ( - spawn_logged_monitored_task!( - Self { - network, - connection_metrics, - peer_id_types, - connection_statuses, - rx_shutdown - } - .run(), - "ConnectionMonitor" - ), - connection_statuses_outer, - ) - } - - async fn run(mut self) { - let (mut subscriber, connected_peers) = { - if let Some(network) = self.network.upgrade() { - let Ok((subscriber, active_peers)) = network.subscribe() else { - return; - }; - (subscriber, active_peers) - } else { - return; - } - }; - - // we report first all the known peers as disconnected - so we can see - // their labels in the metrics reporting tool - let mut known_peers = Vec::new(); - for (peer_id, ty) in &self.peer_id_types { - known_peers.push(*peer_id); - self.connection_metrics - .network_peer_connected - .with_label_values(&[&format!("{peer_id}"), ty]) - .set(0) - } - - // now report the connected peers - for peer_id in connected_peers.iter() { - self.handle_peer_event(PeerEvent::NewPeer(*peer_id)).await; - } - - let mut connection_stat_collection_interval = - time::interval(CONNECTION_STAT_COLLECTION_INTERVAL); - - async fn wait_for_shutdown( - rx_shutdown: &mut Option, - ) -> Result<(), tokio::sync::broadcast::error::RecvError> { - if let Some(rx) = rx_shutdown.as_mut() { - rx.receiver.recv().await - } else { - // If no shutdown receiver is provided, wait forever. - let future = future::pending(); - #[allow(clippy::let_unit_value)] - let () = future.await; - Ok(()) - } - } - - loop { - tokio::select! { - _ = connection_stat_collection_interval.tick() => { - if let Some(network) = self.network.upgrade() { - self.connection_metrics.socket_receive_buffer_size.set( - network.socket_receive_buf_size() as i64 - ); - self.connection_metrics.socket_send_buffer_size.set( - network.socket_send_buf_size() as i64 - ); - for peer_id in known_peers.iter() { - if let Some(connection) = network.peer(*peer_id) { - let stats = connection.connection_stats(); - self.update_quinn_metrics_for_peer(&format!("{peer_id}"), &stats); - } - } - } else { - continue; - } - } - Ok(event) = subscriber.recv() => { - self.handle_peer_event(event).await; - } - _ = wait_for_shutdown(&mut self.rx_shutdown) => { - return; - } - } - } - } - - async fn handle_peer_event(&self, peer_event: PeerEvent) { - if let Some(network) = self.network.upgrade() { - self.connection_metrics - .network_peers - .set(network.peers().len() as i64); - } else { - return; - } - - let (peer_id, status, int_status) = match peer_event { - PeerEvent::NewPeer(peer_id) => (peer_id, ConnectionStatus::Connected, 1), - PeerEvent::LostPeer(peer_id, _) => (peer_id, ConnectionStatus::Disconnected, 0), - }; - self.connection_statuses.insert(peer_id, status); - - // Only report peer IDs for known peers to prevent unlimited cardinality. - let peer_id_str = if self.peer_id_types.contains_key(&peer_id) { - format!("{peer_id}") - } else { - "other_peer".to_string() - }; - - if let Some(ty) = self.peer_id_types.get(&peer_id) { - self.connection_metrics - .network_peer_connected - .with_label_values(&[&peer_id_str, ty]) - .set(int_status); - } - - if let PeerEvent::LostPeer(_, reason) = peer_event { - self.connection_metrics - .network_peer_disconnects - .with_label_values(&[&peer_id_str, &format!("{reason:?}")]) - .inc(); - } - } - - // TODO: Replace this with ClosureMetric - fn update_quinn_metrics_for_peer(&self, peer_id: &str, stats: &ConnectionStats) { - // Update PathStats - self.connection_metrics - .network_peer_rtt - .with_label_values(&[peer_id]) - .set(stats.path.rtt.as_millis() as i64); - self.connection_metrics - .network_peer_lost_packets - .with_label_values(&[peer_id]) - .set(stats.path.lost_packets as i64); - self.connection_metrics - .network_peer_lost_bytes - .with_label_values(&[peer_id]) - .set(stats.path.lost_bytes as i64); - self.connection_metrics - .network_peer_sent_packets - .with_label_values(&[peer_id]) - .set(stats.path.sent_packets as i64); - self.connection_metrics - .network_peer_congestion_events - .with_label_values(&[peer_id]) - .set(stats.path.congestion_events as i64); - self.connection_metrics - .network_peer_congestion_window - .with_label_values(&[peer_id]) - .set(stats.path.cwnd as i64); - - // Update FrameStats - self.connection_metrics - .network_peer_max_data - .with_label_values(&[peer_id, "transmitted"]) - .set(stats.frame_tx.max_data as i64); - self.connection_metrics - .network_peer_max_data - .with_label_values(&[peer_id, "received"]) - .set(stats.frame_rx.max_data as i64); - self.connection_metrics - .network_peer_closed_connections - .with_label_values(&[peer_id, "transmitted"]) - .set(stats.frame_tx.connection_close as i64); - self.connection_metrics - .network_peer_closed_connections - .with_label_values(&[peer_id, "received"]) - .set(stats.frame_rx.connection_close as i64); - self.connection_metrics - .network_peer_data_blocked - .with_label_values(&[peer_id, "transmitted"]) - .set(stats.frame_tx.data_blocked as i64); - self.connection_metrics - .network_peer_data_blocked - .with_label_values(&[peer_id, "received"]) - .set(stats.frame_rx.data_blocked as i64); - - // Update UDPStats - self.connection_metrics - .network_peer_udp_datagrams - .with_label_values(&[peer_id, "transmitted"]) - .set(stats.udp_tx.datagrams as i64); - self.connection_metrics - .network_peer_udp_datagrams - .with_label_values(&[peer_id, "received"]) - .set(stats.udp_rx.datagrams as i64); - self.connection_metrics - .network_peer_udp_bytes - .with_label_values(&[peer_id, "transmitted"]) - .set(stats.udp_tx.bytes as i64); - self.connection_metrics - .network_peer_udp_bytes - .with_label_values(&[peer_id, "received"]) - .set(stats.udp_rx.bytes as i64); - self.connection_metrics - .network_peer_udp_transmits - .with_label_values(&[peer_id, "transmitted"]) - .set(stats.udp_tx.ios as i64); - self.connection_metrics - .network_peer_udp_transmits - .with_label_values(&[peer_id, "received"]) - .set(stats.udp_rx.ios as i64); - } -} - -#[cfg(test)] -mod tests { - use crate::connectivity::{ConnectionMonitor, ConnectionStatus}; - use crate::metrics::NetworkConnectionMetrics; - use anemo::{Network, Request, Response}; - use bytes::Bytes; - use prometheus::Registry; - use std::collections::HashMap; - use std::convert::Infallible; - use std::time::Duration; - use tokio::time::{sleep, timeout}; - use tower::util::BoxCloneService; - - #[tokio::test] - async fn test_connectivity() { - // GIVEN - let network_1 = build_network().unwrap(); - let network_2 = build_network().unwrap(); - let network_3 = build_network().unwrap(); - - let registry = Registry::new(); - let metrics = NetworkConnectionMetrics::new("primary", ®istry); - - // AND we connect to peer 2 - let peer_2 = network_1.connect(network_2.local_addr()).await.unwrap(); - - let mut peer_types = HashMap::new(); - peer_types.insert(network_2.peer_id(), "other_network".to_string()); - peer_types.insert(network_3.peer_id(), "other_network".to_string()); - - // WHEN bring up the monitor - let (_h, statuses) = - ConnectionMonitor::spawn(network_1.downgrade(), metrics.clone(), peer_types, None); - - // THEN peer 2 should be already connected - assert_network_peers(metrics.clone(), 1).await; - - // AND we should have collected connection stats - let mut labels = HashMap::new(); - let peer_2_str = format!("{peer_2}"); - labels.insert("peer_id", peer_2_str.as_str()); - assert_ne!( - metrics - .network_peer_rtt - .get_metric_with(&labels) - .unwrap() - .get(), - 0 - ); - assert_eq!( - *statuses.get(&peer_2).unwrap().value(), - ConnectionStatus::Connected - ); - - // WHEN connect to peer 3 - let peer_3 = network_1.connect(network_3.local_addr()).await.unwrap(); - - // THEN - assert_network_peers(metrics.clone(), 2).await; - assert_eq!( - *statuses.get(&peer_3).unwrap().value(), - ConnectionStatus::Connected - ); - - // AND disconnect peer 2 - network_1.disconnect(peer_2).unwrap(); - - // THEN - assert_network_peers(metrics.clone(), 1).await; - assert_eq!( - *statuses.get(&peer_2).unwrap().value(), - ConnectionStatus::Disconnected - ); - - // AND disconnect peer 3 - network_1.disconnect(peer_3).unwrap(); - - // THEN - assert_network_peers(metrics.clone(), 0).await; - assert_eq!( - *statuses.get(&peer_3).unwrap().value(), - ConnectionStatus::Disconnected - ); - } - - async fn assert_network_peers(metrics: NetworkConnectionMetrics, value: i64) { - let m = metrics.clone(); - timeout(Duration::from_secs(5), async move { - while m.network_peers.get() != value { - sleep(Duration::from_millis(500)).await; - } - }) - .await - .unwrap_or_else(|_| { - panic!( - "Timeout while waiting for connectivity results for value {}", - value - ) - }); - - assert_eq!(metrics.network_peers.get(), value); - } - - fn build_network() -> anyhow::Result { - let network = Network::bind("localhost:0") - .private_key(random_private_key()) - .server_name("test") - .start(echo_service())?; - Ok(network) - } - - fn echo_service() -> BoxCloneService, Response, Infallible> { - let handle = move |request: Request| async move { - let response = Response::new(request.into_body()); - Result::, Infallible>::Ok(response) - }; - - tower::ServiceExt::boxed_clone(tower::service_fn(handle)) - } - - fn random_private_key() -> [u8; 32] { - let mut rng = rand::thread_rng(); - let mut bytes = [0u8; 32]; - rand::RngCore::fill_bytes(&mut rng, &mut bytes[..]); - - bytes - } -} diff --git a/narwhal/network/src/lib.rs b/narwhal/network/src/lib.rs index 5e9add7e20b76..f781bf851a66a 100644 --- a/narwhal/network/src/lib.rs +++ b/narwhal/network/src/lib.rs @@ -11,7 +11,6 @@ pub mod admin; pub mod client; -pub mod connectivity; pub mod epoch_filter; pub mod failpoints; pub mod metrics; diff --git a/narwhal/primary/src/metrics.rs b/narwhal/primary/src/metrics.rs index d6340253e8f92..c32f0f0bea5fa 100644 --- a/narwhal/primary/src/metrics.rs +++ b/narwhal/primary/src/metrics.rs @@ -1,6 +1,6 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -use network::metrics::{NetworkConnectionMetrics, NetworkMetrics}; +use network::metrics::NetworkMetrics; use prometheus::{ core::{AtomicI64, GenericGauge}, default_registry, linear_buckets, register_histogram_vec_with_registry, @@ -22,7 +22,6 @@ pub(crate) struct Metrics { pub(crate) outbound_network_metrics: Option, pub(crate) primary_channel_metrics: Option, pub(crate) node_metrics: Option, - pub(crate) network_connection_metrics: Option, } /// Initialises the metrics @@ -37,15 +36,11 @@ pub(crate) fn initialise_metrics(metrics_registry: &Registry) -> Metrics { // Essential/core metrics across the primary node let node_metrics = PrimaryMetrics::new(metrics_registry); - // Network metrics for the primary connection - let network_connection_metrics = NetworkConnectionMetrics::new("primary", metrics_registry); - Metrics { node_metrics: Some(node_metrics), primary_channel_metrics: Some(primary_channel_metrics), inbound_network_metrics: Some(inbound_network_metrics), outbound_network_metrics: Some(outbound_network_metrics), - network_connection_metrics: Some(network_connection_metrics), } } diff --git a/narwhal/primary/src/primary.rs b/narwhal/primary/src/primary.rs index 6b2a7f84cac1a..aa6145dafdc9b 100644 --- a/narwhal/primary/src/primary.rs +++ b/narwhal/primary/src/primary.rs @@ -126,7 +126,6 @@ impl Primary { let inbound_network_metrics = Arc::new(metrics.inbound_network_metrics.unwrap()); let outbound_network_metrics = Arc::new(metrics.outbound_network_metrics.unwrap()); let node_metrics = Arc::new(metrics.node_metrics.unwrap()); - let network_connection_metrics = metrics.network_connection_metrics.unwrap(); let (tx_our_digests, rx_our_digests) = channel_with_total( CHANNEL_CAPACITY, @@ -404,13 +403,6 @@ impl Primary { ); } - let (connection_monitor_handle, _) = network::connectivity::ConnectionMonitor::spawn( - network.downgrade(), - network_connection_metrics, - peer_types, - Some(tx_shutdown.subscribe()), - ); - info!( "Primary {} listening to network admin messages on 127.0.0.1:{}", authority.id(), @@ -477,12 +469,7 @@ impl Primary { leader_schedule.clone(), ); - let mut handles = vec![ - core_handle, - certificate_fetcher_handle, - proposer_handle, - connection_monitor_handle, - ]; + let mut handles = vec![core_handle, certificate_fetcher_handle, proposer_handle]; handles.extend(admin_handles); // Keeps track of the latest consensus round and allows other tasks to clean up their their internal state diff --git a/narwhal/worker/src/tests/worker_tests.rs b/narwhal/worker/src/tests/worker_tests.rs index 9d031756bf4d7..99421109124da 100644 --- a/narwhal/worker/src/tests/worker_tests.rs +++ b/narwhal/worker/src/tests/worker_tests.rs @@ -6,24 +6,17 @@ use crate::LocalNarwhalClient; use crate::{metrics::initialise_metrics, TrivialTransactionValidator}; use async_trait::async_trait; use bytes::Bytes; -use fastcrypto::{ - encoding::{Encoding, Hex}, - hash::Hash, -}; +use fastcrypto::hash::Hash; use futures::stream::FuturesOrdered; use futures::StreamExt; -use primary::consensus::{ConsensusRound, LeaderSchedule, LeaderSwapTable}; -use primary::{Primary, CHANNEL_CAPACITY, NUM_SHUTDOWN_RECEIVERS}; +use primary::{CHANNEL_CAPACITY, NUM_SHUTDOWN_RECEIVERS}; use prometheus::Registry; -use std::time::Duration; -use storage::NodeStorage; use store::rocks; use store::rocks::MetricConf; use store::rocks::ReadWriteOptions; use test_utils::{ batch, latest_protocol_version, temp_dir, test_network, transaction, CommitteeFixture, }; -use tokio::sync::watch; use types::{ BatchAPI, MockWorkerToPrimary, MockWorkerToWorker, PreSubscribedBroadcastSender, TransactionProto, TransactionsClient, WorkerBatchMessage, WorkerToWorkerClient, @@ -376,288 +369,3 @@ async fn handle_local_clients_transactions() { // Ensure sending ended. assert!(join_handle.await.is_ok()); } - -#[tokio::test] -async fn get_network_peers_from_admin_server() { - // telemetry_subscribers::init_for_testing(); - let primary_1_parameters = Parameters { - batch_size: 200, // Two transactions. - ..Parameters::default() - }; - let fixture = CommitteeFixture::builder().randomize_ports(true).build(); - let committee = fixture.committee(); - let worker_cache = fixture.worker_cache(); - let authority_1 = fixture.authorities().next().unwrap(); - let signer_1 = authority_1.keypair().copy(); - let client_1 = NetworkClient::new_from_keypair(&authority_1.network_keypair()); - - let worker_id = 0; - let worker_1_keypair = authority_1.worker(worker_id).keypair().copy(); - - // Make the data store. - let store = NodeStorage::reopen(temp_dir(), None); - - let (tx_new_certificates, _rx_new_certificates) = - test_utils::test_new_certificates_channel!(CHANNEL_CAPACITY); - let (tx_feedback, rx_feedback) = test_utils::test_channel!(CHANNEL_CAPACITY); - let (_tx_consensus_round_updates, rx_consensus_round_updates) = - watch::channel(ConsensusRound::default()); - let mut tx_shutdown = PreSubscribedBroadcastSender::new(NUM_SHUTDOWN_RECEIVERS); - - // Spawn Primary 1 - Primary::spawn( - authority_1.authority().clone(), - signer_1, - authority_1.network_keypair().copy(), - committee.clone(), - worker_cache.clone(), - latest_protocol_version(), - primary_1_parameters.clone(), - client_1.clone(), - store.certificate_store.clone(), - store.proposer_store.clone(), - store.payload_store.clone(), - store.vote_digest_store.clone(), - tx_new_certificates, - rx_feedback, - rx_consensus_round_updates, - &mut tx_shutdown, - tx_feedback, - &Registry::new(), - LeaderSchedule::new(committee.clone(), LeaderSwapTable::default()), - ); - - // Wait for tasks to start - tokio::time::sleep(Duration::from_secs(1)).await; - - let registry_1 = Registry::new(); - let metrics_1 = initialise_metrics(®istry_1); - let mut tx_shutdown = PreSubscribedBroadcastSender::new(NUM_SHUTDOWN_RECEIVERS); - - let worker_1_parameters = Parameters { - batch_size: 200, // Two transactions. - ..Parameters::default() - }; - - // Spawn a `Worker` instance for primary 1. - Worker::spawn( - authority_1.authority().clone(), - worker_1_keypair.copy(), - worker_id, - committee.clone(), - worker_cache.clone(), - latest_protocol_version(), - worker_1_parameters.clone(), - TrivialTransactionValidator, - client_1.clone(), - store.batch_store.clone(), - metrics_1.clone(), - &mut tx_shutdown, - ); - - let primary_1_peer_id = Hex::encode(authority_1.network_keypair().copy().public().0.as_bytes()); - let worker_1_peer_id = Hex::encode(worker_1_keypair.copy().public().0.as_bytes()); - - // Wait for tasks to start - tokio::time::sleep(Duration::from_secs(1)).await; - - // Test getting all known peers for worker 1 - let resp = reqwest::get(format!( - "http://127.0.0.1:{}/known_peers", - worker_1_parameters - .network_admin_server - .worker_network_admin_server_base_port - + worker_id as u16 - )) - .await - .unwrap() - .json::>() - .await - .unwrap(); - - // Assert we returned 3 peers (1 primary + 3 other workers) - assert_eq!(4, resp.len()); - - // Test getting all connected peers for worker 1 (worker at index 0 for primary 1) - let resp = reqwest::get(format!( - "http://127.0.0.1:{}/peers", - worker_1_parameters - .network_admin_server - .worker_network_admin_server_base_port - + worker_id as u16 - )) - .await - .unwrap() - .json::>() - .await - .unwrap(); - - // Assert we returned 1 peer (only worker's primary spawned) - assert_eq!(1, resp.len()); - - // Assert peer ids are correct - let expected_peer_ids = [&primary_1_peer_id]; - assert!(expected_peer_ids.iter().all(|e| resp.contains(e))); - - let authority_2 = fixture.authorities().nth(1).unwrap(); - let signer_2 = authority_2.keypair().copy(); - let client_2 = NetworkClient::new_from_keypair(&authority_2.network_keypair()); - - let worker_2_keypair = authority_2.worker(worker_id).keypair().copy(); - - let primary_2_parameters = Parameters { - batch_size: 200, // Two transactions. - ..Parameters::default() - }; - - let (tx_new_certificates_2, _rx_new_certificates_2) = - test_utils::test_new_certificates_channel!(CHANNEL_CAPACITY); - let (tx_feedback_2, rx_feedback_2) = test_utils::test_channel!(CHANNEL_CAPACITY); - let (_tx_consensus_round_updates, rx_consensus_round_updates) = - watch::channel(ConsensusRound::default()); - - let mut tx_shutdown_2 = PreSubscribedBroadcastSender::new(NUM_SHUTDOWN_RECEIVERS); - - // Spawn Primary 2 - Primary::spawn( - authority_2.authority().clone(), - signer_2, - authority_2.network_keypair().copy(), - committee.clone(), - worker_cache.clone(), - latest_protocol_version(), - primary_2_parameters.clone(), - client_2.clone(), - store.certificate_store.clone(), - store.proposer_store.clone(), - store.payload_store.clone(), - store.vote_digest_store.clone(), - tx_new_certificates_2, - rx_feedback_2, - rx_consensus_round_updates, - &mut tx_shutdown_2, - tx_feedback_2, - &Registry::new(), - LeaderSchedule::new(committee.clone(), LeaderSwapTable::default()), - ); - - // Wait for tasks to start - tokio::time::sleep(Duration::from_secs(1)).await; - - let registry_2 = Registry::new(); - let metrics_2 = initialise_metrics(®istry_2); - - let worker_2_parameters = Parameters { - batch_size: 200, // Two transactions. - ..Parameters::default() - }; - - let mut tx_shutdown_worker = PreSubscribedBroadcastSender::new(NUM_SHUTDOWN_RECEIVERS); - - // Spawn a `Worker` instance for primary 2. - Worker::spawn( - authority_2.authority().clone(), - worker_2_keypair.copy(), - worker_id, - committee.clone(), - worker_cache.clone(), - latest_protocol_version(), - worker_2_parameters.clone(), - TrivialTransactionValidator, - client_2, - store.batch_store, - metrics_2.clone(), - &mut tx_shutdown_worker, - ); - - // Wait for tasks to start. Sleeping longer here to ensure all primaries and workers - // have a chance to connect to each other. - tokio::time::sleep(Duration::from_secs(5)).await; - - let primary_2_peer_id = Hex::encode(authority_2.network_keypair().copy().public().0.as_bytes()); - let worker_2_peer_id = Hex::encode(worker_2_keypair.copy().public().0.as_bytes()); - - // Test getting all known peers for worker 2 (worker at index 0 for primary 2) - let resp = reqwest::get(format!( - "http://127.0.0.1:{}/known_peers", - worker_2_parameters - .network_admin_server - .worker_network_admin_server_base_port - + worker_id as u16 - )) - .await - .unwrap() - .json::>() - .await - .unwrap(); - - // Assert we returned 4 peers (1 primary + 3 other workers) - assert_eq!(4, resp.len()); - - // Test getting all connected peers for worker 1 (worker at index 0 for primary 1) - let resp = reqwest::get(format!( - "http://127.0.0.1:{}/peers", - worker_1_parameters - .network_admin_server - .worker_network_admin_server_base_port - + worker_id as u16 - )) - .await - .unwrap() - .json::>() - .await - .unwrap(); - - // Assert we returned 3 peers (2 primaries spawned + 1 other worker spawned) - assert_eq!(3, resp.len()); - - // Assert peer ids are correct - let expected_peer_ids = [&primary_1_peer_id, &primary_2_peer_id, &worker_2_peer_id]; - assert!(expected_peer_ids.iter().all(|e| resp.contains(e))); - - // Test getting all connected peers for worker 2 (worker at index 0 for primary 2) - let resp = reqwest::get(format!( - "http://127.0.0.1:{}/peers", - worker_2_parameters - .network_admin_server - .worker_network_admin_server_base_port - + worker_id as u16 - )) - .await - .unwrap() - .json::>() - .await - .unwrap(); - - // Assert we returned 3 peers (2 primaries spawned + 1 other worker spawned) - assert_eq!(3, resp.len()); - - // Assert peer ids are correct - let expected_peer_ids = [&primary_1_peer_id, &primary_2_peer_id, &worker_1_peer_id]; - assert!(expected_peer_ids.iter().all(|e| resp.contains(e))); - - // Assert network connectivity metrics are also set as expected - let filters = vec![ - (primary_2_peer_id.as_str(), "our_primary"), - (primary_1_peer_id.as_str(), "other_primary"), - (worker_1_peer_id.as_str(), "other_worker"), - ]; - - for f in filters { - let mut m = HashMap::new(); - m.insert("peer_id", f.0); - m.insert("type", f.1); - - assert_eq!( - 1, - metrics_2 - .clone() - .network_connection_metrics - .unwrap() - .network_peer_connected - .get_metric_with(&m) - .unwrap() - .get() - ); - } -} diff --git a/narwhal/worker/src/worker.rs b/narwhal/worker/src/worker.rs index 9bdb55e8b4e91..63064a1b6c925 100644 --- a/narwhal/worker/src/worker.rs +++ b/narwhal/worker/src/worker.rs @@ -106,7 +106,6 @@ impl Worker { let channel_metrics: Arc = Arc::new(metrics.channel_metrics.unwrap()); let inbound_network_metrics = Arc::new(metrics.inbound_network_metrics.unwrap()); let outbound_network_metrics = Arc::new(metrics.outbound_network_metrics.unwrap()); - let network_connection_metrics = metrics.network_connection_metrics.unwrap(); let mut shutdown_receivers = tx_shutdown.subscribe_n(NUM_SHUTDOWN_RECEIVERS); @@ -351,13 +350,6 @@ impl Worker { ); } - let (connection_monitor_handle, _) = network::connectivity::ConnectionMonitor::spawn( - network.downgrade(), - network_connection_metrics, - peer_types, - Some(shutdown_receivers.pop().unwrap()), - ); - let network_admin_server_base_port = parameters .network_admin_server .worker_network_admin_server_base_port @@ -402,7 +394,7 @@ impl Worker { .transactions ); - let mut handles = vec![connection_monitor_handle, network_shutdown_handle]; + let mut handles = vec![network_shutdown_handle]; handles.extend(admin_handles); handles.extend(client_flow_handles); handles diff --git a/nre/sui_for_node_operators.md b/nre/sui_for_node_operators.md index 280cb29a53e8c..b734fea654004 100644 --- a/nre/sui_for_node_operators.md +++ b/nre/sui_for_node_operators.md @@ -125,6 +125,18 @@ The following keys are used by Sui Node: These are configured in the [Sui Node configuration file](#configuration). +You can generate each of these via the [sui cli](https://docs.sui.io/guides/developer/getting-started/sui-install). + +``` +$ sui keytool generate bls12381 +$ sui keytool generate ed25519 +$ sui keytool generate ed25519 +$ sui keytool generate ed25519 +``` + +This will create files like `0x0061b30cdda02b6f55f575f1485a2890ec5c95b753deabbf823b6de7c936eb26.key` & `bls-0x1b7a4038f207d6c65cc106dd5be7270b3031e671fc8f9c1318b19e94a3bf3ed5.key` +which you can copy to your validator and rename to `protocol.key` or `account.key`, etc. + ## Monitoring ### Metrics diff --git a/package.json b/package.json index c4b9465373d83..d2aa83a36f06a 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "nth-check": "2.0.1", "yaml@<2.2.2": ">=2.2.2", "semver@<7.5.2": ">=7.5.2", - "postcss@<8.4.31": ">=8.4.31" + "postcss@<8.4.31": ">=8.4.31", + "dompurify@>=3.0.0 <3.1.3": ">=3.1.3" } }, "engines": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3dae324991f38..04a14ddddf926 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,7 @@ overrides: yaml@<2.2.2: '>=2.2.2' semver@<7.5.2: '>=7.5.2' postcss@<8.4.31: '>=8.4.31' + dompurify@>=3.0.0 <3.1.3: '>=3.1.3' importers: @@ -45,7 +46,7 @@ importers: version: 8.8.0(eslint@8.45.0) eslint-config-react-app: specifier: ^7.0.1 - version: 7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.1.0(eslint@8.45.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.45.0))(eslint@8.45.0)(typescript@5.5.3) + version: 7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.24.7))(@babel/plugin-transform-react-jsx@7.24.7(@babel/core@7.24.7))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.1.0(eslint@8.45.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.45.0))(eslint@8.45.0)(typescript@5.5.3) eslint-import-resolver-typescript: specifier: ^3.6.1 version: 3.6.1(@typescript-eslint/parser@6.1.0(eslint@8.45.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.45.0) @@ -57,7 +58,7 @@ importers: version: 2.29.1(@typescript-eslint/parser@6.1.0(eslint@8.45.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.45.0) eslint-plugin-prettier: specifier: ^5.1.3 - version: 5.1.3(@types/eslint@9.6.1)(eslint-config-prettier@8.8.0(eslint@8.45.0))(eslint@8.45.0)(prettier@3.3.2) + version: 5.1.3(@types/eslint@8.56.10)(eslint-config-prettier@8.8.0(eslint@8.45.0))(eslint@8.45.0)(prettier@3.3.2) eslint-plugin-require-extensions: specifier: ^0.1.3 version: 0.1.3(eslint@8.45.0) @@ -139,34 +140,34 @@ importers: devDependencies: '@headlessui/tailwindcss': specifier: ^0.1.3 - version: 0.1.3(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3))) + version: 0.1.3(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))) '@tailwindcss/aspect-ratio': specifier: ^0.4.2 - version: 0.4.2(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3))) + version: 0.4.2(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))) '@tailwindcss/forms': specifier: ^0.5.7 - version: 0.5.7(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3))) + version: 0.5.7(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))) '@types/react': specifier: ^18.3.3 version: 18.3.3 '@vanilla-extract/vite-plugin': specifier: ^4.0.13 - version: 4.0.13(@types/node@22.5.4)(babel-plugin-macros@3.1.0)(sass@1.78.0)(terser@5.32.0)(vite@5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0)) + version: 4.0.13(@types/node@20.14.10)(babel-plugin-macros@3.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)) postcss: specifier: ^8.4.39 version: 8.4.39 tailwindcss: specifier: ^3.4.4 - version: 3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3)) + version: 3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)) typescript: specifier: ^5.5.3 version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) + version: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) vitest: specifier: ^2.0.1 - version: 2.0.1(@types/node@22.5.4)(happy-dom@15.7.3)(jsdom@25.0.0)(sass@1.78.0)(terser@5.32.0) + version: 2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) apps/icons: devDependencies: @@ -274,7 +275,7 @@ importers: version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@reduxjs/toolkit': specifier: ^1.9.5 - version: 1.9.5(react-redux@8.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@5.0.1))(react@18.3.1) + version: 1.9.5(react-redux@8.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1))(react@18.3.1) '@scure/bip32': specifier: ^1.4.0 version: 1.4.0 @@ -352,7 +353,7 @@ importers: version: 5.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-redux: specifier: ^8.1.1 - version: 8.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@5.0.1) + version: 8.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1) react-router-dom: specifier: ^6.24.1 version: 6.24.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -388,7 +389,7 @@ importers: version: 3.23.8 zustand: specifier: ^4.5.4 - version: 4.5.4(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1) + version: 4.5.4(@types/react@18.3.3)(immer@9.0.21)(react@18.3.1) zxcvbn: specifier: ^4.4.2 version: 4.4.2 @@ -443,7 +444,7 @@ importers: version: 7.6.20(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) '@storybook/react-webpack5': specifier: ^7.1.0 - version: 7.6.20(@babel/core@7.24.7)(@swc/core@1.7.24(@swc/helpers@0.5.13))(@swc/helpers@0.5.13)(@types/webpack@5.28.5(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4(webpack@5.92.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@4.26.1)(typescript@5.5.3)(webpack-cli@5.1.4(webpack@5.92.1))(webpack-hot-middleware@2.26.1) + version: 7.6.20(@babel/core@7.24.7)(@swc/core@1.6.13(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)(@types/webpack@5.28.5(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@4.21.0)(typescript@5.5.3)(webpack-cli@5.1.4(webpack@5.92.1))(webpack-hot-middleware@2.26.1) '@storybook/theming': specifier: ^7.1.0 version: 7.6.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -452,7 +453,7 @@ importers: version: 7.0.0(typescript@5.5.3) '@types/dotenv-webpack': specifier: ^7.0.4 - version: 7.0.7(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4(webpack@5.92.1)) + version: 7.0.7(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) '@types/git-rev-sync': specifier: ^2.0.0 version: 2.0.2 @@ -479,31 +480,31 @@ importers: version: 0.10.7 '@types/webpack': specifier: ^5.28.1 - version: 5.28.5(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4(webpack@5.92.1)) + version: 5.28.5(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) '@types/zxcvbn': specifier: ^4.4.1 version: 4.4.4 '@vitejs/plugin-react': specifier: ^4.3.1 - version: 4.3.1(vite@5.3.3(@types/node@20.14.10)(sass@1.77.6)(terser@5.32.0)) + version: 4.3.1(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)) copy-webpack-plugin: specifier: ^11.0.0 - version: 11.0.0(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + version: 11.0.0(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) cross-env: specifier: ^7.0.3 version: 7.0.3 css-loader: specifier: ^6.7.3 - version: 6.11.0(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + version: 6.11.0(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) dotenv-webpack: specifier: ^8.0.0 - version: 8.1.0(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + version: 8.1.0(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) eslint: specifier: ^8.45.0 version: 8.45.0 eslint-webpack-plugin: specifier: ^4.0.1 - version: 4.2.0(eslint@8.45.0)(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + version: 4.2.0(eslint@8.45.0)(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) git-rev-sync: specifier: ^3.0.2 version: 3.0.2 @@ -512,10 +513,10 @@ importers: version: 14.12.3 html-webpack-plugin: specifier: ^5.5.3 - version: 5.6.0(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + version: 5.6.0(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) mini-css-extract-plugin: specifier: ^2.7.6 - version: 2.9.0(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + version: 2.9.0(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) onchange: specifier: ^7.1.0 version: 7.1.0 @@ -524,7 +525,7 @@ importers: version: 8.4.39 postcss-loader: specifier: ^7.3.3 - version: 7.3.3(postcss@8.4.39)(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + version: 7.3.3(postcss@8.4.39)(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) postcss-preset-env: specifier: ^9.0.0 version: 9.6.0(postcss@8.4.39) @@ -533,22 +534,22 @@ importers: version: 1.77.6 sass-loader: specifier: ^13.3.2 - version: 13.3.2(sass@1.77.6)(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + version: 13.3.2(sass@1.77.6)(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) storybook: specifier: ^7.1.0 version: 7.1.0 tailwindcss: specifier: ^3.4.4 - version: 3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@20.14.10)(typescript@5.5.3)) + version: 3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)) tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@20.14.10)(typescript@5.5.3))) + version: 1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))) ts-loader: specifier: ^9.4.4 - version: 9.5.1(typescript@5.5.3)(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + version: 9.5.1(typescript@5.5.3)(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@20.14.10)(typescript@5.5.3) + version: 10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3) tsconfig-paths: specifier: ^4.2.0 version: 4.2.0 @@ -557,19 +558,19 @@ importers: version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@20.14.10)(sass@1.77.6)(terser@5.32.0) + version: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.6)(terser@5.32.0)) + version: 4.3.2(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)) vitest: specifier: ^2.0.1 - version: 2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@25.0.0)(sass@1.77.6)(terser@5.32.0) + version: 2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) web-ext: specifier: ^7.6.2 version: 7.6.2 webpack: specifier: ^5.79.0 - version: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + version: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) webpack-cli: specifier: ^5.0.1 version: 5.1.4(webpack@5.92.1) @@ -612,7 +613,7 @@ importers: devDependencies: '@headlessui/tailwindcss': specifier: ^0.1.3 - version: 0.1.3(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3))) + version: 0.1.3(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))) '@types/react': specifier: ^18.3.3 version: 18.3.3 @@ -621,7 +622,7 @@ importers: version: 18.3.0 '@vitejs/plugin-react': specifier: ^4.3.1 - version: 4.3.1(vite@5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0)) + version: 4.3.1(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)) autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.39) @@ -630,13 +631,13 @@ importers: version: 8.4.39 tailwindcss: specifier: ^3.4.4 - version: 3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3)) + version: 3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)) typescript: specifier: ^5.5.3 version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) + version: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) dapps/kiosk-cli: dependencies: @@ -724,7 +725,7 @@ importers: devDependencies: '@tailwindcss/forms': specifier: ^0.5.7 - version: 0.5.7(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3))) + version: 0.5.7(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))) '@tsconfig/docusaurus': specifier: ^2.0.3 version: 2.0.3 @@ -736,7 +737,7 @@ importers: version: 18.3.0 '@vitejs/plugin-react': specifier: ^4.3.1 - version: 4.3.1(vite@5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0)) + version: 4.3.1(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)) autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.39) @@ -745,19 +746,19 @@ importers: version: 8.4.39 tailwindcss: specifier: ^3.4.4 - version: 3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3)) + version: 3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)) tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3))) + version: 1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))) typescript: specifier: ^5.5.3 version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) + version: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.5.3)(vite@5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0)) + version: 4.3.2(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)) dapps/sponsored-transactions: dependencies: @@ -785,7 +786,7 @@ importers: version: 18.3.0 '@vitejs/plugin-react': specifier: ^4.3.1 - version: 4.3.1(vite@5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0)) + version: 4.3.1(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)) autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.39) @@ -794,13 +795,13 @@ importers: version: 8.4.39 tailwindcss: specifier: ^3.4.4 - version: 3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3)) + version: 3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)) typescript: specifier: ^5.5.3 version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) + version: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) examples/mev_bot: dependencies: @@ -816,7 +817,7 @@ importers: devDependencies: ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3) + version: 10.9.2(@swc/core@1.6.13)(@types/node@20.14.10)(typescript@5.5.3) typescript: specifier: ^5.5.3 version: 5.5.3 @@ -868,7 +869,7 @@ importers: version: 6.1.0(eslint@8.45.0)(typescript@5.5.3) '@vitejs/plugin-react-swc': specifier: ^3.7.0 - version: 3.7.0(@swc/helpers@0.5.13)(vite@5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0)) + version: 3.7.0(@swc/helpers@0.5.5)(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)) eslint: specifier: ^8.45.0 version: 8.45.0 @@ -886,10 +887,10 @@ importers: version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) + version: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) vite-tsconfig-paths: specifier: ^4.3.2 - version: 4.3.2(typescript@5.5.3)(vite@5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0)) + version: 4.3.2(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)) examples/trading/api: dependencies: @@ -923,7 +924,7 @@ importers: version: 5.16.2 ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@20.14.10)(typescript@5.5.3) + version: 10.9.2(@swc/core@1.6.13)(@types/node@20.14.10)(typescript@5.5.3) typescript: specifier: ^5.5.3 version: 5.5.3 @@ -975,7 +976,7 @@ importers: version: 6.1.0(eslint@8.45.0)(typescript@5.5.3) '@vitejs/plugin-react-swc': specifier: ^3.7.0 - version: 3.7.0(@swc/helpers@0.5.13)(vite@5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0)) + version: 3.7.0(@swc/helpers@0.5.5)(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)) autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.39) @@ -996,13 +997,38 @@ importers: version: 3.3.2 tailwindcss: specifier: ^3.4.4 - version: 3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3)) + version: 3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)) typescript: specifier: ^5.5.3 version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) + version: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) + + examples/usdc-transfer-app: + dependencies: + '@mysten/dapp-kit': + specifier: ^0.14.25 + version: 0.14.25(@tanstack/react-query@5.59.0(react@18.3.1))(@types/react-dom@18.3.0)(@types/react@18.3.3)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) + '@mysten/sui': + specifier: ^1.12.0 + version: 1.12.0(typescript@5.5.3) + '@tanstack/react-query': + specifier: ^5.50.1 + version: 5.59.0(react@18.3.1) + parcel: + specifier: ^2.12.0 + version: 2.12.0(@swc/helpers@0.5.5)(postcss@8.4.39)(relateurl@0.2.7)(terser@5.31.1)(typescript@5.5.3) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) + devDependencies: + process: + specifier: ^0.11.10 + version: 0.11.10 sdk/bcs: dependencies: @@ -1024,7 +1050,7 @@ importers: version: 5.5.3 vitest: specifier: ^2.0.1 - version: 2.0.1(@types/node@22.5.4)(happy-dom@15.7.3)(jsdom@25.0.0)(sass@1.78.0)(terser@5.32.0) + version: 2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) sdk/build-scripts: dependencies: @@ -1037,7 +1063,7 @@ importers: version: 1.16.3 '@vanilla-extract/esbuild-plugin': specifier: ^2.3.8 - version: 2.3.8(@types/node@20.14.10)(babel-plugin-macros@3.1.0)(esbuild@0.23.0)(sass@1.78.0)(terser@5.32.0) + version: 2.3.8(@types/node@20.14.10)(babel-plugin-macros@3.1.0)(esbuild@0.23.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) autoprefixer: specifier: ^10.4.19 version: 10.4.19(postcss@8.4.39) @@ -1117,7 +1143,7 @@ importers: version: 7.16.0(eslint@9.6.0)(typescript@5.5.3) '@vitejs/plugin-react-swc': specifier: ^3.7.0 - version: 3.7.0(@swc/helpers@0.5.13)(vite@5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0)) + version: 3.7.0(@swc/helpers@0.5.5)(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)) eslint: specifier: ^9.6.0 version: 9.6.0 @@ -1135,7 +1161,7 @@ importers: version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) + version: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) sdk/create-dapp/templates/react-e2e-counter: dependencies: @@ -1178,7 +1204,7 @@ importers: version: 6.1.0(eslint@8.45.0)(typescript@5.5.3) '@vitejs/plugin-react-swc': specifier: ^3.7.0 - version: 3.7.0(@swc/helpers@0.5.13)(vite@5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0)) + version: 3.7.0(@swc/helpers@0.5.5)(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)) eslint: specifier: ^8.45.0 version: 8.45.0 @@ -1196,7 +1222,7 @@ importers: version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) + version: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) sdk/dapp-kit: dependencies: @@ -1232,7 +1258,7 @@ importers: version: 2.1.1 zustand: specifier: ^4.5.4 - version: 4.5.4(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1) + version: 4.5.4(@types/react@18.3.3)(immer@9.0.21)(react@18.3.1) devDependencies: '@mysten/build-scripts': specifier: workspace:* @@ -1248,7 +1274,7 @@ importers: version: 10.3.1 '@testing-library/jest-dom': specifier: ^6.4.6 - version: 6.4.6(vitest@2.0.1(@types/node@22.5.4)(happy-dom@15.7.3)(jsdom@24.1.0)(sass@1.78.0)(terser@5.32.0)) + version: 6.4.6(vitest@2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)) '@testing-library/react': specifier: ^16.0.0 version: 16.0.0(@testing-library/dom@10.3.1)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -1260,10 +1286,10 @@ importers: version: 18.3.3 '@vanilla-extract/esbuild-plugin': specifier: ^2.3.8 - version: 2.3.8(@types/node@22.5.4)(babel-plugin-macros@3.1.0)(esbuild@0.23.1)(sass@1.78.0)(terser@5.32.0) + version: 2.3.8(@types/node@20.14.10)(babel-plugin-macros@3.1.0)(esbuild@0.23.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) '@vanilla-extract/vite-plugin': specifier: ^4.0.13 - version: 4.0.13(@types/node@22.5.4)(babel-plugin-macros@3.1.0)(sass@1.78.0)(terser@5.32.0)(vite@5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0)) + version: 4.0.13(@types/node@20.14.10)(babel-plugin-macros@3.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)) jsdom: specifier: ^24.1.0 version: 24.1.0 @@ -1281,10 +1307,10 @@ importers: version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) + version: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) vitest: specifier: ^2.0.1 - version: 2.0.1(@types/node@22.5.4)(happy-dom@15.7.3)(jsdom@24.1.0)(sass@1.78.0)(terser@5.32.0) + version: 2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) sdk/deepbook: dependencies: @@ -1303,7 +1329,7 @@ importers: version: 0.2.3 ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@20.14.10)(typescript@5.5.3) + version: 10.9.2(@swc/core@1.6.13)(@types/node@20.14.10)(typescript@5.5.3) ts-retry-promise: specifier: ^0.8.1 version: 0.8.1 @@ -1312,10 +1338,10 @@ importers: version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@20.14.10)(sass@1.78.0)(terser@5.32.0) + version: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) vitest: specifier: ^2.0.1 - version: 2.0.1(@types/node@20.14.10)(happy-dom@15.7.3)(jsdom@25.0.0)(sass@1.78.0)(terser@5.32.0) + version: 2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) wait-on: specifier: ^7.2.0 version: 7.2.0 @@ -1340,7 +1366,7 @@ importers: version: 0.2.3 ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@20.14.10)(typescript@5.5.3) + version: 10.9.2(@swc/core@1.6.13)(@types/node@20.14.10)(typescript@5.5.3) ts-retry-promise: specifier: ^0.8.1 version: 0.8.1 @@ -1349,10 +1375,10 @@ importers: version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@20.14.10)(sass@1.78.0)(terser@5.32.0) + version: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) vitest: specifier: ^2.0.1 - version: 2.0.1(@types/node@20.14.10)(happy-dom@15.7.3)(jsdom@25.0.0)(sass@1.78.0)(terser@5.32.0) + version: 2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) wait-on: specifier: ^7.2.0 version: 7.2.0 @@ -1396,27 +1422,30 @@ importers: specifier: ^20.14.10 version: 20.14.10 next: - specifier: ^14.2.4 - version: 14.2.4(@babel/core@7.25.2)(@playwright/test@1.47.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.78.0) + specifier: ^14.2.10 + version: 14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6) nextra: specifier: ^2.13.4 - version: 2.13.4(next@14.2.4(@babel/core@7.25.2)(@playwright/test@1.47.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.78.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.13.4(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) nextra-theme-docs: specifier: ^2.13.4 - version: 2.13.4(next@14.2.4(@babel/core@7.25.2)(@playwright/test@1.47.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.78.0))(nextra@2.13.4(next@14.2.4(@babel/core@7.25.2)(@playwright/test@1.47.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.78.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: 2.13.4(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(nextra@2.13.4(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: specifier: ^18.3.1 version: 18.3.1 react-dom: specifier: ^18.3.1 version: 18.3.1(react@18.3.1) + typedoc-plugin-mermaid: + specifier: ^1.12.0 + version: 1.12.0(typedoc@0.26.3(typescript@5.5.3)) devDependencies: '@types/react': specifier: ^18.3.3 version: 18.3.3 typedoc: specifier: ^0.26.3 - version: 0.26.3(typescript@5.6.2) + version: 0.26.3(typescript@5.5.3) sdk/enoki: dependencies: @@ -1450,7 +1479,7 @@ importers: version: 18.3.0 '@vitejs/plugin-react-swc': specifier: ^3.7.0 - version: 3.7.0(@swc/helpers@0.5.13)(vite@5.3.3(@types/node@20.14.10)(sass@1.78.0)(terser@5.32.0)) + version: 3.7.0(@swc/helpers@0.5.5)(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)) react: specifier: ^18.3.1 version: 18.3.1 @@ -1462,7 +1491,7 @@ importers: version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@20.14.10)(sass@1.78.0)(terser@5.32.0) + version: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) sdk/graphql-transport: dependencies: @@ -1514,7 +1543,7 @@ importers: version: 5.5.3 vitest: specifier: ^2.0.1 - version: 2.0.1(@types/node@20.14.10)(happy-dom@15.7.3)(jsdom@25.0.0)(sass@1.78.0)(terser@5.32.0) + version: 2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) wait-on: specifier: ^7.2.0 version: 7.2.0 @@ -1542,10 +1571,10 @@ importers: version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) + version: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) vitest: specifier: ^2.0.1 - version: 2.0.1(@types/node@22.5.4)(happy-dom@15.7.3)(jsdom@25.0.0)(sass@1.78.0)(terser@5.32.0) + version: 2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) wait-on: specifier: ^7.2.0 version: 7.2.0 @@ -1579,7 +1608,7 @@ importers: version: 5.5.3 vitest: specifier: ^2.0.1 - version: 2.0.1(@types/node@20.14.10)(happy-dom@15.7.3)(jsdom@25.0.0)(sass@1.78.0)(terser@5.32.0) + version: 2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) sdk/move-bytecode-template: devDependencies: @@ -1594,7 +1623,7 @@ importers: version: 5.5.3 vitest: specifier: ^2.0.1 - version: 2.0.1(@types/node@22.5.4)(happy-dom@15.7.3)(jsdom@25.0.0)(sass@1.78.0)(terser@5.32.0) + version: 2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) wasm-pack: specifier: ^0.13.0 version: 0.13.0 @@ -1613,13 +1642,13 @@ importers: version: link:../build-scripts ts-node: specifier: ^10.9.2 - version: 10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3) + version: 10.9.2(@swc/core@1.6.13)(@types/node@20.14.10)(typescript@5.5.3) typescript: specifier: ^5.5.3 version: 5.5.3 vitest: specifier: ^2.0.1 - version: 2.0.1(@types/node@22.5.4)(happy-dom@15.7.3)(jsdom@25.0.0)(sass@1.78.0)(terser@5.32.0) + version: 2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) sdk/typescript: dependencies: @@ -1713,10 +1742,10 @@ importers: version: 5.5.3 vite: specifier: ^5.3.3 - version: 5.3.3(@types/node@20.14.10)(sass@1.78.0)(terser@5.32.0) + version: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) vitest: specifier: ^2.0.1 - version: 2.0.1(@types/node@20.14.10)(happy-dom@15.7.3)(jsdom@25.0.0)(sass@1.78.0)(terser@5.32.0) + version: 2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) wait-on: specifier: ^7.2.0 version: 7.2.0 @@ -1741,7 +1770,7 @@ importers: version: 5.5.3 typescript-json-schema: specifier: ^0.64.0 - version: 0.64.0(@swc/core@1.7.24(@swc/helpers@0.5.13)) + version: 0.64.0(@swc/core@1.6.13) sdk/zklogin: dependencies: @@ -1772,7 +1801,7 @@ importers: version: 5.5.3 vitest: specifier: ^2.0.1 - version: 2.0.1(@types/node@20.14.10)(happy-dom@15.7.3)(jsdom@25.0.0)(sass@1.78.0)(terser@5.32.0) + version: 2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) sdk/zksend: dependencies: @@ -1803,7 +1832,7 @@ importers: version: 5.5.3 vitest: specifier: ^2.0.1 - version: 2.0.1(@types/node@20.14.10)(happy-dom@15.7.3)(jsdom@25.0.0)(sass@1.78.0)(terser@5.32.0) + version: 2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) packages: @@ -1907,18 +1936,10 @@ packages: resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.25.4': - resolution: {integrity: sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==} - engines: {node: '>=6.9.0'} - '@babel/core@7.24.7': resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==} engines: {node: '>=6.9.0'} - '@babel/core@7.25.2': - resolution: {integrity: sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==} - engines: {node: '>=6.9.0'} - '@babel/eslint-parser@7.18.9': resolution: {integrity: sha512-KzSGpMBggz4fKbRbWLNyPVTuQr6cmCcBhOyXTw/fieOVaw5oYAwcAj4a7UKcDYCPxQq+CG1NCDZH9e2JTXquiQ==} engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} @@ -1934,10 +1955,6 @@ packages: resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} engines: {node: '>=6.9.0'} - '@babel/generator@7.25.6': - resolution: {integrity: sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==} - engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.24.7': resolution: {integrity: sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==} engines: {node: '>=6.9.0'} @@ -1950,10 +1967,6 @@ packages: resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.25.2': - resolution: {integrity: sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==} - engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.24.7': resolution: {integrity: sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==} engines: {node: '>=6.9.0'} @@ -2002,12 +2015,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-module-transforms@7.25.2': - resolution: {integrity: sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@babel/helper-optimise-call-expression@7.24.7': resolution: {integrity: sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==} engines: {node: '>=6.9.0'} @@ -2020,10 +2027,6 @@ packages: resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==} engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.24.8': - resolution: {integrity: sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==} - engines: {node: '>=6.9.0'} - '@babel/helper-remap-async-to-generator@7.24.7': resolution: {integrity: sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==} engines: {node: '>=6.9.0'} @@ -2056,10 +2059,6 @@ packages: resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.24.8': - resolution: {integrity: sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==} - engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.22.20': resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} @@ -2076,10 +2075,6 @@ packages: resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.24.8': - resolution: {integrity: sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==} - engines: {node: '>=6.9.0'} - '@babel/helper-wrap-function@7.24.7': resolution: {integrity: sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==} engines: {node: '>=6.9.0'} @@ -2088,10 +2083,6 @@ packages: resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.25.6': - resolution: {integrity: sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==} - engines: {node: '>=6.9.0'} - '@babel/highlight@7.22.5': resolution: {integrity: sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==} engines: {node: '>=6.9.0'} @@ -2119,11 +2110,6 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/parser@7.25.6': - resolution: {integrity: sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==} - engines: {node: '>=6.0.0'} - hasBin: true - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.7': resolution: {integrity: sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==} engines: {node: '>=6.9.0'} @@ -2671,12 +2657,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx@7.25.2': - resolution: {integrity: sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-pure-annotations@7.18.6': resolution: {integrity: sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==} engines: {node: '>=6.9.0'} @@ -2857,10 +2837,6 @@ packages: resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} engines: {node: '>=6.9.0'} - '@babel/template@7.25.0': - resolution: {integrity: sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==} - engines: {node: '>=6.9.0'} - '@babel/traverse@7.23.9': resolution: {integrity: sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==} engines: {node: '>=6.9.0'} @@ -2869,10 +2845,6 @@ packages: resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.25.6': - resolution: {integrity: sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==} - engines: {node: '>=6.9.0'} - '@babel/types@7.23.9': resolution: {integrity: sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==} engines: {node: '>=6.9.0'} @@ -2881,10 +2853,6 @@ packages: resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} engines: {node: '>=6.9.0'} - '@babel/types@7.25.6': - resolution: {integrity: sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==} - engines: {node: '>=6.9.0'} - '@base2/pretty-print-object@1.0.1': resolution: {integrity: sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==} @@ -3249,12 +3217,6 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.23.1': - resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/android-arm64@0.18.20': resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} @@ -3273,12 +3235,6 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.23.1': - resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm@0.18.20': resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==} engines: {node: '>=12'} @@ -3297,12 +3253,6 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.23.1': - resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - '@esbuild/android-x64@0.18.20': resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==} engines: {node: '>=12'} @@ -3321,12 +3271,6 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.23.1': - resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - '@esbuild/darwin-arm64@0.18.20': resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==} engines: {node: '>=12'} @@ -3345,12 +3289,6 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.23.1': - resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-x64@0.18.20': resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==} engines: {node: '>=12'} @@ -3369,12 +3307,6 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.23.1': - resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - '@esbuild/freebsd-arm64@0.18.20': resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==} engines: {node: '>=12'} @@ -3393,12 +3325,6 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.23.1': - resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-x64@0.18.20': resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==} engines: {node: '>=12'} @@ -3417,12 +3343,6 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.23.1': - resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - '@esbuild/linux-arm64@0.18.20': resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==} engines: {node: '>=12'} @@ -3441,12 +3361,6 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.23.1': - resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm@0.18.20': resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==} engines: {node: '>=12'} @@ -3465,12 +3379,6 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.23.1': - resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - '@esbuild/linux-ia32@0.18.20': resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==} engines: {node: '>=12'} @@ -3489,12 +3397,6 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.23.1': - resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-loong64@0.18.20': resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==} engines: {node: '>=12'} @@ -3513,12 +3415,6 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.23.1': - resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-mips64el@0.18.20': resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==} engines: {node: '>=12'} @@ -3537,12 +3433,6 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.23.1': - resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-ppc64@0.18.20': resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==} engines: {node: '>=12'} @@ -3561,12 +3451,6 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.23.1': - resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-riscv64@0.18.20': resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==} engines: {node: '>=12'} @@ -3585,12 +3469,6 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.23.1': - resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-s390x@0.18.20': resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==} engines: {node: '>=12'} @@ -3609,12 +3487,6 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.23.1': - resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-x64@0.18.20': resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==} engines: {node: '>=12'} @@ -3633,12 +3505,6 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.23.1': - resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - '@esbuild/netbsd-x64@0.18.20': resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==} engines: {node: '>=12'} @@ -3657,24 +3523,12 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.23.1': - resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - '@esbuild/openbsd-arm64@0.23.0': resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.23.1': - resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - '@esbuild/openbsd-x64@0.18.20': resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==} engines: {node: '>=12'} @@ -3693,12 +3547,6 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.23.1': - resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - '@esbuild/sunos-x64@0.18.20': resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==} engines: {node: '>=12'} @@ -3717,12 +3565,6 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.23.1': - resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - '@esbuild/win32-arm64@0.18.20': resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==} engines: {node: '>=12'} @@ -3741,12 +3583,6 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.23.1': - resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-ia32@0.18.20': resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==} engines: {node: '>=12'} @@ -3765,12 +3601,6 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.23.1': - resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-x64@0.18.20': resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==} engines: {node: '>=12'} @@ -3789,12 +3619,6 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.23.1': - resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - '@eslint-community/eslint-utils@4.4.0': resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -4328,6 +4152,42 @@ packages: '@ledgerhq/logs@6.12.0': resolution: {integrity: sha512-ExDoj1QV5eC6TEbMdLUMMk9cfvNKhhv5gXol4SmULRVCx/3iyCPhJ74nsb3S0Vb+/f+XujBEj3vQn5+cwS0fNA==} + '@lezer/common@1.2.2': + resolution: {integrity: sha512-Z+R3hN6kXbgBWAuejUNPihylAL1Z5CaFqnIe0nTX8Ej+XlIy3EGtXxn6WtLMO+os2hRkQvm2yvaGMYliUzlJaw==} + + '@lezer/lr@1.4.2': + resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} + + '@lmdb/lmdb-darwin-arm64@2.8.5': + resolution: {integrity: sha512-KPDeVScZgA1oq0CiPBcOa3kHIqU+pTOwRFDIhxvmf8CTNvqdZQYp5cCKW0bUk69VygB2PuTiINFWbY78aR2pQw==} + cpu: [arm64] + os: [darwin] + + '@lmdb/lmdb-darwin-x64@2.8.5': + resolution: {integrity: sha512-w/sLhN4T7MW1nB3R/U8WK5BgQLz904wh+/SmA2jD8NnF7BLLoUgflCNxOeSPOWp8geP6nP/+VjWzZVip7rZ1ug==} + cpu: [x64] + os: [darwin] + + '@lmdb/lmdb-linux-arm64@2.8.5': + resolution: {integrity: sha512-vtbZRHH5UDlL01TT5jB576Zox3+hdyogvpcbvVJlmU5PdL3c5V7cj1EODdh1CHPksRl+cws/58ugEHi8bcj4Ww==} + cpu: [arm64] + os: [linux] + + '@lmdb/lmdb-linux-arm@2.8.5': + resolution: {integrity: sha512-c0TGMbm2M55pwTDIfkDLB6BpIsgxV4PjYck2HiOX+cy/JWiBXz32lYbarPqejKs9Flm7YVAKSILUducU9g2RVg==} + cpu: [arm] + os: [linux] + + '@lmdb/lmdb-linux-x64@2.8.5': + resolution: {integrity: sha512-Xkc8IUx9aEhP0zvgeKy7IQ3ReX2N8N1L0WPcQwnZweWmOuKfwpS3GRIYqLtK5za/w3E60zhFfNdS+3pBZPytqQ==} + cpu: [x64] + os: [linux] + + '@lmdb/lmdb-win32-x64@2.8.5': + resolution: {integrity: sha512-4wvrf5BgnR8RpogHhtpCPJMKBmvyZPhhUtEwMJbXh0ni2BucpfF07jlmyM11zRqQ2XIq6PbC2j7W7UCCcm1rRQ==} + cpu: [x64] + os: [win32] + '@manypkg/cli@0.21.4': resolution: {integrity: sha512-EACxxb+c/t6G0l1FrlyewZeBnyR5V1cLkXjnBfsay5TN1UgbilFpG6POglzn+lVJet9NqnEKe3RLHABzkIDZ0Q==} engines: {node: '>=14.18.0'} @@ -4372,6 +4232,40 @@ packages: '@microsoft/tsdoc@0.14.2': resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==} + '@mischnic/json-sourcemap@0.1.1': + resolution: {integrity: sha512-iA7+tyVqfrATAIsIRWQG+a7ZLLD0VaOCKV2Wd/v4mqIU3J9c4jx9p7S0nw1XH3gJCKNBOOwACOPYYSUu9pgT+w==} + engines: {node: '>=12.0.0'} + + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==} + cpu: [arm64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==} + cpu: [x64] + os: [darwin] + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==} + cpu: [arm64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==} + cpu: [arm] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==} + cpu: [x64] + os: [linux] + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==} + cpu: [x64] + os: [win32] + '@mswjs/cookies@1.1.1': resolution: {integrity: sha512-W68qOHEjx1iD+4VjQudlx26CPIoxmIAtK4ZCexU0/UJBG6jYhcuyzKJx+Iw8uhBIGd9eba64XgWVgo20it1qwA==} engines: {node: '>=18'} @@ -4380,6 +4274,25 @@ packages: resolution: {integrity: sha512-3rDakgJZ77+RiQUuSK69t1F0m8BQKA8Vh5DCS5V0DWvNY67zob2JhhQrhCO0AKLGINTRSFd1tBaHcJTkhefoSw==} engines: {node: '>=18'} + '@mysten/bcs@1.1.0': + resolution: {integrity: sha512-yy9/1Y4d0FlRywS1+9ze/T7refCbrvwFwJIOKs9M3QBK1njbcHZp+LkVeLqBvIJA5eZ3ZCzmhQ1Xq4Sed5mEBA==} + + '@mysten/dapp-kit@0.14.25': + resolution: {integrity: sha512-6efjJgikX09AprXU28srmxkLy8e21YtQRtY23OHGLtP3MUkI71kqt3PhBEK4Tg61e0+YCAY/9UlgVWMBX1Hong==} + peerDependencies: + '@tanstack/react-query': ^5.0.0 + react: '*' + + '@mysten/sui@1.12.0': + resolution: {integrity: sha512-DrSyja04xyGrTGlIQKMwZ6MywxNPkjyIcDLm915Zisoy1/uIgPoHc4cx53JyiG92z/HgowTVGGCCIzH53DIYXA==} + engines: {node: '>=18'} + + '@mysten/wallet-standard@0.13.7': + resolution: {integrity: sha512-FXlqn3Gp4E7aQf33rZQfaCEUeEq9TbmkIFA7kX/Yab5SC5892XVhkLRu040eBs8Cest98jFUZ2ZJ4YWR+a7e5g==} + + '@mysten/zksend@0.11.6': + resolution: {integrity: sha512-dkwPQxzPUK9xDTl2mMa2+VcCQNWtKwiTTr8yq17AYOSIqtmPjoX4F4q+oOunL9vgyiJONSNktO1kq37GSjIa6w==} + '@nanostores/react@0.7.2': resolution: {integrity: sha512-e3OhHJFv3NMSFYDgREdlAQqkyBTHJM91s31kOZ4OvZwJKdFk5BLk0MLbh51EOGUz9QGX2aCHfy1RvweSi7fgwA==} engines: {node: ^18.0.0 || >=20.0.0} @@ -4460,59 +4373,59 @@ packages: '@ndelangen/get-tarball@3.0.9': resolution: {integrity: sha512-9JKTEik4vq+yGosHYhZ1tiH/3WpUS0Nh0kej4Agndhox8pAdWhEx5knFVRcb/ya9knCRCs1rPxNrSXTDdfVqpA==} - '@next/env@14.2.4': - resolution: {integrity: sha512-3EtkY5VDkuV2+lNmKlbkibIJxcO4oIHEhBWne6PaAp+76J9KoSsGvNikp6ivzAT8dhhBMYrm6op2pS1ApG0Hzg==} + '@next/env@14.2.13': + resolution: {integrity: sha512-s3lh6K8cbW1h5Nga7NNeXrbe0+2jIIYK9YaA9T7IufDWnZpozdFUp6Hf0d5rNWUKu4fEuSX2rCKlGjCrtylfDw==} - '@next/swc-darwin-arm64@14.2.4': - resolution: {integrity: sha512-AH3mO4JlFUqsYcwFUHb1wAKlebHU/Hv2u2kb1pAuRanDZ7pD/A/KPD98RHZmwsJpdHQwfEc/06mgpSzwrJYnNg==} + '@next/swc-darwin-arm64@14.2.13': + resolution: {integrity: sha512-IkAmQEa2Htq+wHACBxOsslt+jMoV3msvxCn0WFSfJSkv/scy+i/EukBKNad36grRxywaXUYJc9mxEGkeIs8Bzg==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@14.2.4': - resolution: {integrity: sha512-QVadW73sWIO6E2VroyUjuAxhWLZWEpiFqHdZdoQ/AMpN9YWGuHV8t2rChr0ahy+irKX5mlDU7OY68k3n4tAZTg==} + '@next/swc-darwin-x64@14.2.13': + resolution: {integrity: sha512-Dv1RBGs2TTjkwEnFMVL5XIfJEavnLqqwYSD6LXgTPdEy/u6FlSrLBSSfe1pcfqhFEXRAgVL3Wpjibe5wXJzWog==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@14.2.4': - resolution: {integrity: sha512-KT6GUrb3oyCfcfJ+WliXuJnD6pCpZiosx2X3k66HLR+DMoilRb76LpWPGb4tZprawTtcnyrv75ElD6VncVamUQ==} + '@next/swc-linux-arm64-gnu@14.2.13': + resolution: {integrity: sha512-yB1tYEFFqo4ZNWkwrJultbsw7NPAAxlPXURXioRl9SdW6aIefOLS+0TEsKrWBtbJ9moTDgU3HRILL6QBQnMevg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@14.2.4': - resolution: {integrity: sha512-Alv8/XGSs/ytwQcbCHwze1HmiIkIVhDHYLjczSVrf0Wi2MvKn/blt7+S6FJitj3yTlMwMxII1gIJ9WepI4aZ/A==} + '@next/swc-linux-arm64-musl@14.2.13': + resolution: {integrity: sha512-v5jZ/FV/eHGoWhMKYrsAweQ7CWb8xsWGM/8m1mwwZQ/sutJjoFaXchwK4pX8NqwImILEvQmZWyb8pPTcP7htWg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@14.2.4': - resolution: {integrity: sha512-ze0ShQDBPCqxLImzw4sCdfnB3lRmN3qGMB2GWDRlq5Wqy4G36pxtNOo2usu/Nm9+V2Rh/QQnrRc2l94kYFXO6Q==} + '@next/swc-linux-x64-gnu@14.2.13': + resolution: {integrity: sha512-aVc7m4YL7ViiRv7SOXK3RplXzOEe/qQzRA5R2vpXboHABs3w8vtFslGTz+5tKiQzWUmTmBNVW0UQdhkKRORmGA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@14.2.4': - resolution: {integrity: sha512-8dwC0UJoc6fC7PX70csdaznVMNr16hQrTDAMPvLPloazlcaWfdPogq+UpZX6Drqb1OBlwowz8iG7WR0Tzk/diQ==} + '@next/swc-linux-x64-musl@14.2.13': + resolution: {integrity: sha512-4wWY7/OsSaJOOKvMsu1Teylku7vKyTuocvDLTZQq0TYv9OjiYYWt63PiE1nTuZnqQ4RPvME7Xai+9enoiN0Wrg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@14.2.4': - resolution: {integrity: sha512-jxyg67NbEWkDyvM+O8UDbPAyYRZqGLQDTPwvrBBeOSyVWW/jFQkQKQ70JDqDSYg1ZDdl+E3nkbFbq8xM8E9x8A==} + '@next/swc-win32-arm64-msvc@14.2.13': + resolution: {integrity: sha512-uP1XkqCqV2NVH9+g2sC7qIw+w2tRbcMiXFEbMihkQ8B1+V6m28sshBwAB0SDmOe0u44ne1vFU66+gx/28RsBVQ==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-ia32-msvc@14.2.4': - resolution: {integrity: sha512-twrmN753hjXRdcrZmZttb/m5xaCBFa48Dt3FbeEItpJArxriYDunWxJn+QFXdJ3hPkm4u7CKxncVvnmgQMY1ag==} + '@next/swc-win32-ia32-msvc@14.2.13': + resolution: {integrity: sha512-V26ezyjPqQpDBV4lcWIh8B/QICQ4v+M5Bo9ykLN+sqeKKBxJVDpEc6biDVyluTXTC40f5IqCU0ttth7Es2ZuMw==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@next/swc-win32-x64-msvc@14.2.4': - resolution: {integrity: sha512-tkLrjBzqFTP8DVrAAQmZelEahfR9OxWpFR++vAI9FBhCiIxtwHwBHC23SBHCTURBtwB4kc/x44imVOnkKGNVGg==} + '@next/swc-win32-x64-msvc@14.2.13': + resolution: {integrity: sha512-WwzOEAFBGhlDHE5Z73mNU8CO8mqMNLqaG+AO9ETmzdCQlJhVtWZnOl2+rqgVQS+YHunjOWptdFmNfbpwcUuEsw==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -4614,71 +4527,287 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@parcel/watcher-android-arm64@2.4.1': - resolution: {integrity: sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [android] + '@parcel/bundler-default@2.12.0': + resolution: {integrity: sha512-3ybN74oYNMKyjD6V20c9Gerdbh7teeNvVMwIoHIQMzuIFT6IGX53PyOLlOKRLbjxMc0TMimQQxIt2eQqxR5LsA==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} - '@parcel/watcher-darwin-arm64@2.4.1': - resolution: {integrity: sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [darwin] + '@parcel/cache@2.12.0': + resolution: {integrity: sha512-FX5ZpTEkxvq/yvWklRHDESVRz+c7sLTXgFuzz6uEnBcXV38j6dMSikflNpHA6q/L4GKkCqRywm9R6XQwhwIMyw==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@parcel/core': ^2.12.0 - '@parcel/watcher-darwin-x64@2.4.1': - resolution: {integrity: sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [darwin] + '@parcel/codeframe@2.12.0': + resolution: {integrity: sha512-v2VmneILFiHZJTxPiR7GEF1wey1/IXPdZMcUlNXBiPZyWDfcuNgGGVQkx/xW561rULLIvDPharOMdxz5oHOKQg==} + engines: {node: '>= 12.0.0'} - '@parcel/watcher-freebsd-x64@2.4.1': - resolution: {integrity: sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [freebsd] + '@parcel/compressor-raw@2.12.0': + resolution: {integrity: sha512-h41Q3X7ZAQ9wbQ2csP8QGrwepasLZdXiuEdpUryDce6rF9ZiHoJ97MRpdLxOhOPyASTw/xDgE1xyaPQr0Q3f5A==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} - '@parcel/watcher-linux-arm-glibc@2.4.1': - resolution: {integrity: sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==} - engines: {node: '>= 10.0.0'} - cpu: [arm] - os: [linux] + '@parcel/config-default@2.12.0': + resolution: {integrity: sha512-dPNe2n9eEsKRc1soWIY0yToMUPirPIa2QhxcCB3Z5RjpDGIXm0pds+BaiqY6uGLEEzsjhRO0ujd4v2Rmm0vuFg==} + peerDependencies: + '@parcel/core': ^2.12.0 - '@parcel/watcher-linux-arm64-glibc@2.4.1': - resolution: {integrity: sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [linux] + '@parcel/core@2.12.0': + resolution: {integrity: sha512-s+6pwEj+GfKf7vqGUzN9iSEPueUssCCQrCBUlcAfKrJe0a22hTUCjewpB0I7lNrCIULt8dkndD+sMdOrXsRl6Q==} + engines: {node: '>= 12.0.0'} - '@parcel/watcher-linux-arm64-musl@2.4.1': - resolution: {integrity: sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [linux] + '@parcel/diagnostic@2.12.0': + resolution: {integrity: sha512-8f1NOsSFK+F4AwFCKynyIu9Kr/uWHC+SywAv4oS6Bv3Acig0gtwUjugk0C9UaB8ztBZiW5TQZhw+uPZn9T/lJA==} + engines: {node: '>= 12.0.0'} - '@parcel/watcher-linux-x64-glibc@2.4.1': - resolution: {integrity: sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [linux] + '@parcel/events@2.12.0': + resolution: {integrity: sha512-nmAAEIKLjW1kB2cUbCYSmZOGbnGj8wCzhqnK727zCCWaA25ogzAtt657GPOeFyqW77KyosU728Tl63Fc8hphIA==} + engines: {node: '>= 12.0.0'} - '@parcel/watcher-linux-x64-musl@2.4.1': - resolution: {integrity: sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==} - engines: {node: '>= 10.0.0'} - cpu: [x64] - os: [linux] + '@parcel/fs@2.12.0': + resolution: {integrity: sha512-NnFkuvou1YBtPOhTdZr44WN7I60cGyly2wpHzqRl62yhObyi1KvW0SjwOMa0QGNcBOIzp4G0CapoZ93hD0RG5Q==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@parcel/core': ^2.12.0 - '@parcel/watcher-win32-arm64@2.4.1': - resolution: {integrity: sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==} - engines: {node: '>= 10.0.0'} - cpu: [arm64] - os: [win32] + '@parcel/graph@3.2.0': + resolution: {integrity: sha512-xlrmCPqy58D4Fg5umV7bpwDx5Vyt7MlnQPxW68vae5+BA4GSWetfZt+Cs5dtotMG2oCHzZxhIPt7YZ7NRyQzLA==} + engines: {node: '>= 12.0.0'} - '@parcel/watcher-win32-ia32@2.4.1': - resolution: {integrity: sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==} - engines: {node: '>= 10.0.0'} - cpu: [ia32] - os: [win32] + '@parcel/logger@2.12.0': + resolution: {integrity: sha512-cJ7Paqa7/9VJ7C+KwgJlwMqTQBOjjn71FbKk0G07hydUEBISU2aDfmc/52o60ErL9l+vXB26zTrIBanbxS8rVg==} + engines: {node: '>= 12.0.0'} + + '@parcel/markdown-ansi@2.12.0': + resolution: {integrity: sha512-WZz3rzL8k0H3WR4qTHX6Ic8DlEs17keO9gtD4MNGyMNQbqQEvQ61lWJaIH0nAtgEetu0SOITiVqdZrb8zx/M7w==} + engines: {node: '>= 12.0.0'} + + '@parcel/namer-default@2.12.0': + resolution: {integrity: sha512-9DNKPDHWgMnMtqqZIMiEj/R9PNWW16lpnlHjwK3ciRlMPgjPJ8+UNc255teZODhX0T17GOzPdGbU/O/xbxVPzA==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/node-resolver-core@3.3.0': + resolution: {integrity: sha512-rhPW9DYPEIqQBSlYzz3S0AjXxjN6Ub2yS6tzzsW/4S3Gpsgk/uEq4ZfxPvoPf/6TgZndVxmKwpmxaKtGMmf3cA==} + engines: {node: '>= 12.0.0'} + + '@parcel/optimizer-css@2.12.0': + resolution: {integrity: sha512-ifbcC97fRzpruTjaa8axIFeX4MjjSIlQfem3EJug3L2AVqQUXnM1XO8L0NaXGNLTW2qnh1ZjIJ7vXT/QhsphsA==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/optimizer-htmlnano@2.12.0': + resolution: {integrity: sha512-MfPMeCrT8FYiOrpFHVR+NcZQlXAptK2r4nGJjfT+ndPBhEEZp4yyL7n1y7HfX9geg5altc4WTb4Gug7rCoW8VQ==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/optimizer-image@2.12.0': + resolution: {integrity: sha512-bo1O7raeAIbRU5nmNVtx8divLW9Xqn0c57GVNGeAK4mygnQoqHqRZ0mR9uboh64pxv6ijXZHPhKvU9HEpjPjBQ==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + peerDependencies: + '@parcel/core': ^2.12.0 + + '@parcel/optimizer-svgo@2.12.0': + resolution: {integrity: sha512-Kyli+ZZXnoonnbeRQdoWwee9Bk2jm/49xvnfb+2OO8NN0d41lblBoRhOyFiScRnJrw7eVl1Xrz7NTkXCIO7XFQ==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/optimizer-swc@2.12.0': + resolution: {integrity: sha512-iBi6LZB3lm6WmbXfzi8J3DCVPmn4FN2lw7DGXxUXu7MouDPVWfTsM6U/5TkSHJRNRogZ2gqy5q9g34NPxHbJcw==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/package-manager@2.12.0': + resolution: {integrity: sha512-0nvAezcjPx9FT+hIL+LS1jb0aohwLZXct7jAh7i0MLMtehOi0z1Sau+QpgMlA9rfEZZ1LIeFdnZZwqSy7Ccspw==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@parcel/core': ^2.12.0 + + '@parcel/packager-css@2.12.0': + resolution: {integrity: sha512-j3a/ODciaNKD19IYdWJT+TP+tnhhn5koBGBWWtrKSu0UxWpnezIGZetit3eE+Y9+NTePalMkvpIlit2eDhvfJA==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/packager-html@2.12.0': + resolution: {integrity: sha512-PpvGB9hFFe+19NXGz2ApvPrkA9GwEqaDAninT+3pJD57OVBaxB8U+HN4a5LICKxjUppPPqmrLb6YPbD65IX4RA==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/packager-js@2.12.0': + resolution: {integrity: sha512-viMF+FszITRRr8+2iJyk+4ruGiL27Y6AF7hQ3xbJfzqnmbOhGFtLTQwuwhOLqN/mWR2VKdgbLpZSarWaO3yAMg==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/packager-raw@2.12.0': + resolution: {integrity: sha512-tJZqFbHqP24aq1F+OojFbQIc09P/u8HAW5xfndCrFnXpW4wTgM3p03P0xfw3gnNq+TtxHJ8c3UFE5LnXNNKhYA==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/packager-svg@2.12.0': + resolution: {integrity: sha512-ldaGiacGb2lLqcXas97k8JiZRbAnNREmcvoY2W2dvW4loVuDT9B9fU777mbV6zODpcgcHWsLL3lYbJ5Lt3y9cg==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/packager-wasm@2.12.0': + resolution: {integrity: sha512-fYqZzIqO9fGYveeImzF8ll6KRo2LrOXfD+2Y5U3BiX/wp9wv17dz50QLDQm9hmTcKGWxK4yWqKQh+Evp/fae7A==} + engines: {node: '>=12.0.0', parcel: ^2.12.0} + + '@parcel/plugin@2.12.0': + resolution: {integrity: sha512-nc/uRA8DiMoe4neBbzV6kDndh/58a4wQuGKw5oEoIwBCHUvE2W8ZFSu7ollSXUGRzfacTt4NdY8TwS73ScWZ+g==} + engines: {node: '>= 12.0.0'} + + '@parcel/profiler@2.12.0': + resolution: {integrity: sha512-q53fvl5LDcFYzMUtSusUBZSjQrKjMlLEBgKeQHFwkimwR1mgoseaDBDuNz0XvmzDzF1UelJ02TUKCGacU8W2qA==} + engines: {node: '>= 12.0.0'} + + '@parcel/reporter-cli@2.12.0': + resolution: {integrity: sha512-TqKsH4GVOLPSCanZ6tcTPj+rdVHERnt5y4bwTM82cajM21bCX1Ruwp8xOKU+03091oV2pv5ieB18pJyRF7IpIw==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/reporter-dev-server@2.12.0': + resolution: {integrity: sha512-tIcDqRvAPAttRlTV28dHcbWT5K2r/MBFks7nM4nrEDHWtnrCwimkDmZTc1kD8QOCCjGVwRHcQybpHvxfwol6GA==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/reporter-tracer@2.12.0': + resolution: {integrity: sha512-g8rlu9GxB8Ut/F8WGx4zidIPQ4pcYFjU9bZO+fyRIPrSUFH2bKijCnbZcr4ntqzDGx74hwD6cCG4DBoleq2UlQ==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/resolver-default@2.12.0': + resolution: {integrity: sha512-uuhbajTax37TwCxu7V98JtRLiT6hzE4VYSu5B7Qkauy14/WFt2dz6GOUXPgVsED569/hkxebPx3KCMtZW6cHHA==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/runtime-browser-hmr@2.12.0': + resolution: {integrity: sha512-4ZLp2FWyD32r0GlTulO3+jxgsA3oO1P1b5oO2IWuWilfhcJH5LTiazpL5YdusUjtNn9PGN6QLAWfxmzRIfM+Ow==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/runtime-js@2.12.0': + resolution: {integrity: sha512-sBerP32Z1crX5PfLNGDSXSdqzlllM++GVnVQVeM7DgMKS8JIFG3VLi28YkX+dYYGtPypm01JoIHCkvwiZEcQJg==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/runtime-react-refresh@2.12.0': + resolution: {integrity: sha512-SCHkcczJIDFTFdLTzrHTkQ0aTrX3xH6jrA4UsCBL6ji61+w+ohy4jEEe9qCgJVXhnJfGLE43HNXek+0MStX+Mw==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/runtime-service-worker@2.12.0': + resolution: {integrity: sha512-BXuMBsfiwpIEnssn+jqfC3jkgbS8oxeo3C7xhSQsuSv+AF2FwY3O3AO1c1RBskEW3XrBLNINOJujroNw80VTKA==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/rust@2.12.0': + resolution: {integrity: sha512-005cldMdFZFDPOjbDVEXcINQ3wT4vrxvSavRWI3Az0e3E18exO/x/mW9f648KtXugOXMAqCEqhFHcXECL9nmMw==} + engines: {node: '>= 12.0.0'} + + '@parcel/source-map@2.1.1': + resolution: {integrity: sha512-Ejx1P/mj+kMjQb8/y5XxDUn4reGdr+WyKYloBljpppUy8gs42T+BNoEOuRYqDVdgPc6NxduzIDoJS9pOFfV5Ew==} + engines: {node: ^12.18.3 || >=14} + + '@parcel/transformer-babel@2.12.0': + resolution: {integrity: sha512-zQaBfOnf/l8rPxYGnsk/ufh/0EuqvmnxafjBIpKZ//j6rGylw5JCqXSb1QvvAqRYruKeccxGv7+HrxpqKU6V4A==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/transformer-css@2.12.0': + resolution: {integrity: sha512-vXhOqoAlQGATYyQ433Z1DXKmiKmzOAUmKysbYH3FD+LKEKLMEl/pA14goqp00TW+A/EjtSKKyeMyHlMIIUqj4Q==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/transformer-html@2.12.0': + resolution: {integrity: sha512-5jW4dFFBlYBvIQk4nrH62rfA/G/KzVzEDa6S+Nne0xXhglLjkm64Ci9b/d4tKZfuGWUbpm2ASAq8skti/nfpXw==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/transformer-image@2.12.0': + resolution: {integrity: sha512-8hXrGm2IRII49R7lZ0RpmNk27EhcsH+uNKsvxuMpXPuEnWgC/ha/IrjaI29xCng1uGur74bJF43NUSQhR4aTdw==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + peerDependencies: + '@parcel/core': ^2.12.0 + + '@parcel/transformer-js@2.12.0': + resolution: {integrity: sha512-OSZpOu+FGDbC/xivu24v092D9w6EGytB3vidwbdiJ2FaPgfV7rxS0WIUjH4I0OcvHAcitArRXL0a3+HrNTdQQw==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + peerDependencies: + '@parcel/core': ^2.12.0 + + '@parcel/transformer-json@2.12.0': + resolution: {integrity: sha512-Utv64GLRCQILK5r0KFs4o7I41ixMPllwOLOhkdjJKvf1hZmN6WqfOmB1YLbWS/y5Zb/iB52DU2pWZm96vLFQZQ==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/transformer-postcss@2.12.0': + resolution: {integrity: sha512-FZqn+oUtiLfPOn67EZxPpBkfdFiTnF4iwiXPqvst3XI8H+iC+yNgzmtJkunOOuylpYY6NOU5jT8d7saqWSDv2Q==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/transformer-posthtml@2.12.0': + resolution: {integrity: sha512-z6Z7rav/pcaWdeD+2sDUcd0mmNZRUvtHaUGa50Y2mr+poxrKilpsnFMSiWBT+oOqPt7j71jzDvrdnAF4XkCljg==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/transformer-raw@2.12.0': + resolution: {integrity: sha512-Ht1fQvXxix0NncdnmnXZsa6hra20RXYh1VqhBYZLsDfkvGGFnXIgO03Jqn4Z8MkKoa0tiNbDhpKIeTjyclbBxQ==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/transformer-react-refresh-wrap@2.12.0': + resolution: {integrity: sha512-GE8gmP2AZtkpBIV5vSCVhewgOFRhqwdM5Q9jNPOY5PKcM3/Ff0qCqDiTzzGLhk0/VMBrdjssrfZkVx6S/lHdJw==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/transformer-svg@2.12.0': + resolution: {integrity: sha512-cZJqGRJ4JNdYcb+vj94J7PdOuTnwyy45dM9xqbIMH+HSiiIkfrMsdEwYft0GTyFTdsnf+hdHn3tau7Qa5hhX+A==} + engines: {node: '>= 12.0.0', parcel: ^2.12.0} + + '@parcel/types@2.12.0': + resolution: {integrity: sha512-8zAFiYNCwNTQcglIObyNwKfRYQK5ELlL13GuBOrSMxueUiI5ylgsGbTS1N7J3dAGZixHO8KhHGv5a71FILn9rQ==} + + '@parcel/utils@2.12.0': + resolution: {integrity: sha512-z1JhLuZ8QmDaYoEIuUCVZlhcFrS7LMfHrb2OCRui5SQFntRWBH2fNM6H/fXXUkT9SkxcuFP2DUA6/m4+Gkz72g==} + engines: {node: '>= 12.0.0'} + + '@parcel/watcher-android-arm64@2.4.1': + resolution: {integrity: sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.4.1': + resolution: {integrity: sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.4.1': + resolution: {integrity: sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.4.1': + resolution: {integrity: sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.4.1': + resolution: {integrity: sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.4.1': + resolution: {integrity: sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.4.1': + resolution: {integrity: sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.4.1': + resolution: {integrity: sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.4.1': + resolution: {integrity: sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.4.1': + resolution: {integrity: sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.4.1': + resolution: {integrity: sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] '@parcel/watcher-win32-x64@2.4.1': resolution: {integrity: sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==} @@ -4690,6 +4819,12 @@ packages: resolution: {integrity: sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==} engines: {node: '>= 10.0.0'} + '@parcel/workers@2.12.0': + resolution: {integrity: sha512-zv5We5Jmb+ZWXlU6A+AufyjY4oZckkxsZ8J4dvyWL0W8IQvGO1JB4FGeryyttzQv3RM3OxcN/BpTGPiDG6keBw==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@parcel/core': ^2.12.0 + '@peculiar/asn1-schema@2.3.8': resolution: {integrity: sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA==} @@ -4719,11 +4854,6 @@ packages: engines: {node: '>=18'} hasBin: true - '@playwright/test@1.47.0': - resolution: {integrity: sha512-SgAdlSwYVpToI4e/IH19IHHWvoijAYH5hu2MWSXptRypLSnzj51PcGD+rsOXFayde4P9ZLi+loXVwArg6IUkCA==} - engines: {node: '>=18'} - hasBin: true - '@pmmmwh/react-refresh-webpack-plugin@0.5.15': resolution: {integrity: sha512-LFWllMA55pzB9D34w/wXUCf8+c+IYKuJDgxiZ3qMhl64KRMBHYM1I3VdGaD2BV5FNPV2/S2596bppxHbv2ZydQ==} engines: {node: '>= 10.13'} @@ -6340,120 +6470,60 @@ packages: cpu: [arm64] os: [darwin] - '@swc/core-darwin-arm64@1.7.24': - resolution: {integrity: sha512-s0k09qAcsoa8jIncwgRRd43VApYqXu28R4OmICtDffV4S01HtsRLRarXsMuLutoZk3tbxqitep+A8MPBuqNgdg==} - engines: {node: '>=10'} - cpu: [arm64] - os: [darwin] - '@swc/core-darwin-x64@1.6.13': resolution: {integrity: sha512-AW8akFSC+tmPE6YQQvK9S2A1B8pjnXEINg+gGgw0KRUUXunvu1/OEOeC5L2Co1wAwhD7bhnaefi06Qi9AiwOag==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-darwin-x64@1.7.24': - resolution: {integrity: sha512-1dlsulJ/fiOoJoJyQgaCewIEaZ7Sh6aJN4r5Uhl4lIZuNWa27XOb28A3K29/6HDO9JML3IJrvXPnl5o0vxDQuQ==} - engines: {node: '>=10'} - cpu: [x64] - os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.6.13': resolution: {integrity: sha512-f4gxxvDXVUm2HLYXRd311mSrmbpQF2MZ4Ja6XCQz1hWAxXdhRl1gpnZ+LH/xIfGSwQChrtLLVrkxdYUCVuIjFg==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm-gnueabihf@1.7.24': - resolution: {integrity: sha512-2ft1NmxyvHCu5CY4r2rNVybPqZtJaxpRSzvCcPlVjN/2D5Q3QgM5kBoo1t+0RCFfk4TS2V0KWJhtqKz0CNX62Q==} - engines: {node: '>=10'} - cpu: [arm] - os: [linux] - '@swc/core-linux-arm64-gnu@1.6.13': resolution: {integrity: sha512-Nf/eoW2CbG8s+9JoLtjl9FByBXyQ5cjdBsA4efO7Zw4p+YSuXDgc8HRPC+E2+ns0praDpKNZtLvDtmF2lL+2Gg==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-gnu@1.7.24': - resolution: {integrity: sha512-v/Z8I9tUUNkNHKa1Sw4r1Q7Wp66ezbRhe6xMIxvPNKVJQFaMOsRpe0t8T5qbk5sV2hJGOCKpQynSpZqQXLcJDQ==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - '@swc/core-linux-arm64-musl@1.6.13': resolution: {integrity: sha512-2OysYSYtdw79prJYuKIiux/Gj0iaGEbpS2QZWCIY4X9sGoETJ5iMg+lY+YCrIxdkkNYd7OhIbXdYFyGs/w5LDg==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.7.24': - resolution: {integrity: sha512-0jJx0IcajcyOXaJsx1jXy86lYVrbupyy2VUj/OiJux/ic4oBJLjfL+WOuc8T8/hZj2p6X0X4jvfSCqWSuic4kA==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - '@swc/core-linux-x64-gnu@1.6.13': resolution: {integrity: sha512-PkR4CZYJNk5hcd2+tMWBpnisnmYsUzazI1O5X7VkIGFcGePTqJ/bWlfUIVVExWxvAI33PQFzLbzmN5scyIUyGQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-linux-x64-gnu@1.7.24': - resolution: {integrity: sha512-2+3aKQpSGjVnWKDTKUPuJzitQlTQrGorg+PVFMRkv6l+RcNCHZQNe/8VYpMhyBhxDMb3LUlbp7776FRevcruxg==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - '@swc/core-linux-x64-musl@1.6.13': resolution: {integrity: sha512-OdsY7wryTxCKwGQcwW9jwWg3cxaHBkTTHi91+5nm7hFPpmZMz1HivJrWAMwVE7iXFw+M4l6ugB/wCvpYrUAAjA==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.7.24': - resolution: {integrity: sha512-PMQ6SkCtMoj0Ks77DiishpEmIuHpYjFLDuVOzzJCzGeGoii0yRP5lKy/VeglFYLPqJzmhK9BHlpVehVf/8ZpvA==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - '@swc/core-win32-arm64-msvc@1.6.13': resolution: {integrity: sha512-ap6uNmYjwk9M/+bFEuWRNl3hq4VqgQ/Lk+ID/F5WGqczNr0L7vEf+pOsRAn0F6EV+o/nyb3ePt8rLhE/wjHpPg==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-arm64-msvc@1.7.24': - resolution: {integrity: sha512-SNdCa4DtGXNWrPVHqctVUxgEVZVETuqERpqF50KFHO0Bvf5V/m1IJ4hFr2BxXlrzgnIW4t1Dpi6YOJbcGbEmnA==} - engines: {node: '>=10'} - cpu: [arm64] - os: [win32] - '@swc/core-win32-ia32-msvc@1.6.13': resolution: {integrity: sha512-IJ8KH4yIUHTnS/U1jwQmtbfQals7zWPG0a9hbEfIr4zI0yKzjd83lmtS09lm2Q24QBWOCFGEEbuZxR4tIlvfzA==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-ia32-msvc@1.7.24': - resolution: {integrity: sha512-5p3olHqwibMfrVFg2yVuSIPh9HArDYYlJXNZ9JKqeZk23A19J1pl9MuPmXDw+sxsiPfYJ/nUedIGeUHPF/+EDw==} - engines: {node: '>=10'} - cpu: [ia32] - os: [win32] - '@swc/core-win32-x64-msvc@1.6.13': resolution: {integrity: sha512-f6/sx6LMuEnbuxtiSL/EkR0Y6qUHFw1XVrh6rwzKXptTipUdOY+nXpKoh+1UsBm/r7H0/5DtOdrn3q5ZHbFZjQ==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core-win32-x64-msvc@1.7.24': - resolution: {integrity: sha512-gRyPIxDznS8d2ClfmWbytjp2d48bij6swHnDLWhukNuOvXdQkEmaIzjEsionFG/zhcFLnz8zKfTvjEjInAMzxg==} - engines: {node: '>=10'} - cpu: [x64] - os: [win32] - '@swc/core@1.6.13': resolution: {integrity: sha512-eailUYex6fkfaQTev4Oa3mwn0/e3mQU4H8y1WPuImYQESOQDtVrowwUGDSc19evpBbHpKtwM+hw8nLlhIsF+Tw==} engines: {node: '>=10'} @@ -6463,27 +6533,12 @@ packages: '@swc/helpers': optional: true - '@swc/core@1.7.24': - resolution: {integrity: sha512-FzJaai6z6DYdICAY1UKNN5pzTn296ksK2zzEjjaXlpZtoMkGktWT0ttS7hbdBCPGhLOu5Q9TA2zdPejKUFjgig==} - engines: {node: '>=10'} - peerDependencies: - '@swc/helpers': '*' - peerDependenciesMeta: - '@swc/helpers': - optional: true - '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - '@swc/helpers@0.5.13': - resolution: {integrity: sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==} - '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} - '@swc/types@0.1.12': - resolution: {integrity: sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA==} - '@swc/types@0.1.9': resolution: {integrity: sha512-qKnCno++jzcJ4lM4NTfYifm1EFSCeIfKiAHAfkENZAV5Kl9PjJIyd2yeeVv6c/2CckuLyv2NmRC5pv6pm2WQBg==} @@ -6512,6 +6567,9 @@ packages: '@tanstack/query-core@5.50.1': resolution: {integrity: sha512-lpfhKPrJlyV2DSVcQb/HuozH3Av3kws4ge22agx+lNGpFkS4vLZ7St0l3GLwlAD+bqB+qXGex3JdRKUNtMviEQ==} + '@tanstack/query-core@5.59.0': + resolution: {integrity: sha512-WGD8uIhX6/deH/tkZqPNcRyAhDUqs729bWKoByYHSogcshXfFbppOdTER5+qY7mFvu8KEFJwT0nxr8RfPTVh0Q==} + '@tanstack/query-devtools@5.51.1': resolution: {integrity: sha512-rehG0WmL3EXER6MAI2uHQia/n0b5c3ZROohpYm7u3G7yg4q+HsfQy6nuAo6uy40NzHUe3FmnfWCZQ0Vb/3lE6g==} @@ -6534,6 +6592,11 @@ packages: peerDependencies: react: ^18.0.0 + '@tanstack/react-query@5.59.0': + resolution: {integrity: sha512-YDXp3OORbYR+8HNQx+lf4F73NoiCmCcSvZvgxE29OifmQFk0sBlO26NWLHpcNERo92tVk3w+JQ53/vkcRUY1hA==} + peerDependencies: + react: ^18 || ^19 + '@tanstack/react-virtual@3.5.0': resolution: {integrity: sha512-rtvo7KwuIvqK9zb0VZ5IL7fiJAEnG+0EiFZz8FUOs+2mhGqdGmjKIaT1XU7Zq0eFqL0jonLlhbayJI/J2SA/Bw==} peerDependencies: @@ -6688,9 +6751,6 @@ packages: '@types/eslint@8.56.10': resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} - '@types/eslint@9.6.1': - resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} - '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} @@ -6817,9 +6877,6 @@ packages: '@types/node@20.14.10': resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==} - '@types/node@22.5.4': - resolution: {integrity: sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==} - '@types/normalize-package-data@2.4.1': resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -7373,6 +7430,9 @@ packages: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} + abortcontroller-polyfill@1.7.5: + resolution: {integrity: sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -7813,6 +7873,9 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base-x@3.0.10: + resolution: {integrity: sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==} + base-x@5.0.0: resolution: {integrity: sha512-sMW3VGSX1QWVFA6l8U62MLKz29rRfpTlYdCqLdpLo1/Yd4zZwSbnUaDfciIAowAqvq7YFnWq9hrhdg1KYgc1lQ==} @@ -7896,11 +7959,6 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - browserslist@4.23.3: - resolution: {integrity: sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - bs58@6.0.0: resolution: {integrity: sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==} @@ -8195,6 +8253,10 @@ packages: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + clsx@2.0.0: resolution: {integrity: sha512-rQ1+kcj+ttHG0MKVGBUXwayCCF1oh39BF5COIpRzuCEv8Mwjv0XucrI2ExNTOn9IlLifGClWQcU9BrZORvtw6Q==} engines: {node: '>=6'} @@ -8408,6 +8470,15 @@ packages: typescript: optional: true + cosmiconfig@9.0.0: + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} @@ -8474,6 +8545,10 @@ packages: css-select@5.1.0: resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + css-tree@2.2.1: resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} @@ -8497,6 +8572,10 @@ packages: engines: {node: '>=4'} hasBin: true + csso@4.2.0: + resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} + engines: {node: '>=8.0.0'} + csso@5.0.5: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} @@ -8505,10 +8584,6 @@ packages: resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==} engines: {node: '>=18'} - cssstyle@4.1.0: - resolution: {integrity: sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==} - engines: {node: '>=18'} - csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} @@ -8887,6 +8962,10 @@ packages: engines: {node: '>=0.10'} hasBin: true + detect-libc@2.0.3: + resolution: {integrity: sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==} + engines: {node: '>=8'} + detect-node-es@1.1.0: resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} @@ -8970,8 +9049,8 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.1.2: - resolution: {integrity: sha512-hLGGBI1tw5N8qTELr3blKjAML/LY4ANxksbS612UiJyDfyf/2D092Pvm+S7pmeTGJRqvlJkFzBoHBQKgQlOQVg==} + dompurify@3.1.6: + resolution: {integrity: sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==} domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} @@ -9000,6 +9079,9 @@ packages: resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==} engines: {node: '>=12'} + dotenv-expand@5.1.0: + resolution: {integrity: sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==} + dotenv-webpack@8.1.0: resolution: {integrity: sha512-owK1JcsPkIobeqjVrk6h7jPED/W6ZpdFsMPR+5ursB7/SdgDyO+VzAU+szK8C8u3qUhtENyYnj8eyXMR5kkGag==} engines: {node: '>=10'} @@ -9010,6 +9092,10 @@ packages: resolution: {integrity: sha512-CjA3y+Dr3FyFDOAMnxZEGtnW9KBR2M0JvvUtXNW+dYJL5ROWxP9DUHCwgFqpMk0OXCc0ljhaNTr2w/kutYIcHQ==} engines: {node: '>=12'} + dotenv@7.0.0: + resolution: {integrity: sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==} + engines: {node: '>=6'} + dotenv@8.6.0: resolution: {integrity: sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==} engines: {node: '>=10'} @@ -9045,9 +9131,6 @@ packages: electron-to-chromium@1.4.819: resolution: {integrity: sha512-8RwI6gKUokbHWcN3iRij/qpvf/wCbIVY5slODi85werwqUQwpFXM+dvUBND93Qh7SB0pW3Hlq3/wZsqQ3M9Jaw==} - electron-to-chromium@1.5.18: - resolution: {integrity: sha512-1OfuVACu+zKlmjsNdcJuVQuVE61sZOLbNM4JAQ1Rvh6EOj0/EUKhMJjRH73InPlXSh8HIJk1cVZ8pyOV/FMdUQ==} - elkjs@0.9.3: resolution: {integrity: sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==} @@ -9086,6 +9169,10 @@ packages: entities@2.2.0: resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + entities@3.0.1: + resolution: {integrity: sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==} + engines: {node: '>=0.12'} + entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -9186,11 +9273,6 @@ packages: engines: {node: '>=18'} hasBin: true - esbuild@0.23.1: - resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} - engines: {node: '>=18'} - hasBin: true - escalade@3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -9421,11 +9503,13 @@ packages: eslint@8.36.0: resolution: {integrity: sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true eslint@8.45.0: resolution: {integrity: sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true eslint@9.6.0: @@ -9896,6 +9980,10 @@ packages: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} + get-port@4.2.0: + resolution: {integrity: sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==} + engines: {node: '>=6'} + get-port@5.1.1: resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} engines: {node: '>=8'} @@ -10126,10 +10214,6 @@ packages: resolution: {integrity: sha512-vsYlEs3E9gLwA1Hp+w3qzu+RUDFf4VTT8cyKqVICoZ2k7WM++Qyd2LwzyTi5bqMJFiIC/vNpTDYuxdreENRK/g==} engines: {node: '>=16.0.0'} - happy-dom@15.7.3: - resolution: {integrity: sha512-w3RUaYNXFJX5LiNVhOJLK4GqCB1bFj1FvELtpon3HrN8gUpS09V0Vvm4/BBRRj7mLUE1+ch8PKv1JxEp/0IHjA==} - engines: {node: '>=18.0.0'} - har-schema@2.0.0: resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} engines: {node: '>=4'} @@ -10254,6 +10338,9 @@ packages: html-entities@2.5.2: resolution: {integrity: sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA==} + html-escaper@3.0.3: + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} + html-minifier-terser@6.1.0: resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} engines: {node: '>=12'} @@ -10278,9 +10365,41 @@ packages: webpack: optional: true + htmlnano@2.1.1: + resolution: {integrity: sha512-kAERyg/LuNZYmdqgCdYvugyLWNFAm8MWXpQMz1pLpetmCbFwoMxvkSoaAMlFrOC4OKTWI4KlZGT/RsNxg4ghOw==} + peerDependencies: + cssnano: ^7.0.0 + postcss: '>=8.4.31' + purgecss: ^6.0.0 + relateurl: ^0.2.7 + srcset: 5.0.1 + svgo: ^3.0.2 + terser: ^5.10.0 + uncss: ^0.17.3 + peerDependenciesMeta: + cssnano: + optional: true + postcss: + optional: true + purgecss: + optional: true + relateurl: + optional: true + srcset: + optional: true + svgo: + optional: true + terser: + optional: true + uncss: + optional: true + htmlparser2@6.1.0: resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==} + htmlparser2@7.2.0: + resolution: {integrity: sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==} + htmlparser2@8.0.2: resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} @@ -10385,9 +10504,6 @@ packages: immediate@3.0.6: resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} - immer@10.1.1: - resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==} - immer@9.0.21: resolution: {integrity: sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==} @@ -10398,9 +10514,6 @@ packages: immutable@4.3.6: resolution: {integrity: sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==} - immutable@4.3.7: - resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} - import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -10610,6 +10723,9 @@ packages: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} + is-json@2.0.1: + resolution: {integrity: sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==} + is-lower-case@2.0.2: resolution: {integrity: sha512-bVcMJy4X5Og6VZfdOZstSexlEy20Sr0k/p/b2IlQJlfdKAQuMpiv5w2Ccxb8sKdRUNAG1PnHVHjFSdRDVS6NlQ==} @@ -10899,15 +11015,6 @@ packages: canvas: optional: true - jsdom@25.0.0: - resolution: {integrity: sha512-OhoFVT59T7aEq75TVw9xxEfkXgacpqAhQaYgP9y/fDqWQCMB/b1H66RfmPm/MaeaAIU9nDwMOVTlPN51+ao6CQ==} - engines: {node: '>=18'} - peerDependencies: - canvas: ^2.11.2 - peerDependenciesMeta: - canvas: - optional: true - jsesc@0.5.0: resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} hasBin: true @@ -11057,16 +11164,80 @@ packages: lighthouse-logger@1.4.2: resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} - lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} + lightningcss-darwin-arm64@1.27.0: + resolution: {integrity: sha512-Gl/lqIXY+d+ySmMbgDf0pgaWSqrWYxVHoc88q+Vhf2YNzZ8DwoRzGt5NZDVqqIW5ScpSnmmjcgXP87Dn2ylSSQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] - lilconfig@3.1.2: - resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} - engines: {node: '>=14'} + lightningcss-darwin-x64@1.27.0: + resolution: {integrity: sha512-0+mZa54IlcNAoQS9E0+niovhyjjQWEMrwW0p2sSdLRhLDc8LMQ/b67z7+B5q4VmjYCMSfnFi3djAAQFIDuj/Tg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + lightningcss-freebsd-x64@1.27.0: + resolution: {integrity: sha512-n1sEf85fePoU2aDN2PzYjoI8gbBqnmLGEhKq7q0DKLj0UTVmOTwDC7PtLcy/zFxzASTSBlVQYJUhwIStQMIpRA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.27.0: + resolution: {integrity: sha512-MUMRmtdRkOkd5z3h986HOuNBD1c2lq2BSQA1Jg88d9I7bmPGx08bwGcnB75dvr17CwxjxD6XPi3Qh8ArmKFqCA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.27.0: + resolution: {integrity: sha512-cPsxo1QEWq2sfKkSq2Bq5feQDHdUEwgtA9KaB27J5AX22+l4l0ptgjMZZtYtUnteBofjee+0oW1wQ1guv04a7A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.27.0: + resolution: {integrity: sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.27.0: + resolution: {integrity: sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.27.0: + resolution: {integrity: sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.27.0: + resolution: {integrity: sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.27.0: + resolution: {integrity: sha512-/OJLj94Zm/waZShL8nB5jsNj3CfNATLCTyFxZyouilfTmSoLDX7VlVAmhPHoZWVFp4vdmoiEbPEYC8HID3m6yw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.27.0: + resolution: {integrity: sha512-8f7aNmS1+etYSLHht0fQApPc2kNO8qGRutifN5rVIc6Xo6ABsEbqOr758UwI7ALVbTt4x1fllKt0PYgzD9S3yQ==} + engines: {node: '>= 12.0.0'} + + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + lilconfig@3.1.2: + resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} lines-and-columns@2.0.3: resolution: {integrity: sha512-cNOjgCnLB+FnvWWtyRTzmB3POJ+cXxTA81LoW7u8JdmhfXzriropYwpjShnz1QLLWsQwY7nIxoDmcPTwphDK9w==} @@ -11084,6 +11255,10 @@ packages: enquirer: optional: true + lmdb@2.8.5: + resolution: {integrity: sha512-9bMdFfc80S+vSldBmG3HOuLVHnxRdNTlpzR6QDnzqCQtCzGUEAGTzBKYMeIM+I/sU4oZfgbcbS7X7F65/z/oxQ==} + hasBin: true + load-yaml-file@0.2.0: resolution: {integrity: sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==} engines: {node: '>=6'} @@ -11311,6 +11486,9 @@ packages: mdast-util-to-string@3.2.0: resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==} + mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + mdn-data@2.0.28: resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} @@ -11625,6 +11803,13 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msgpackr-extract@3.0.3: + resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==} + hasBin: true + + msgpackr@1.11.0: + resolution: {integrity: sha512-I8qXuuALqJe5laEBYoFykChhSXLikZmUhccjGsPuSJ/7uPip2TJ7lwdIQwWSAi0jGZDXv4WOP8Qg65QZRuXxXw==} + msw@2.3.1: resolution: {integrity: sha512-ocgvBCLn/5l3jpl1lssIb3cniuACJLoOfZu01e3n5dbJrpA5PeeWn28jCLgQDNt6d7QT8tF2fYRzm9JoEHtiig==} engines: {node: '>=18'} @@ -11717,8 +11902,8 @@ packages: react: '*' react-dom: '*' - next@14.2.4: - resolution: {integrity: sha512-R8/V7vugY+822rsQGQCjoLhMuC9oFj9SOi4Cl4b2wjDrseD0LRZ10W7R6Czo4w9ZznVSshKjuIomsRjvm9EKJQ==} + next@14.2.13: + resolution: {integrity: sha512-BseY9YNw8QJSwLYD7hlZzl6QVDoSFHL/URN5K64kVEVpCsSOWeyjbIGK+dZUaRViHTaMQX8aqmnn0PHBbGZezg==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -11757,6 +11942,9 @@ packages: node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} + node-addon-api@6.1.0: + resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + node-addon-api@7.1.0: resolution: {integrity: sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==} engines: {node: ^16 || ^18 || >= 20} @@ -11798,6 +11986,14 @@ packages: resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} engines: {node: '>= 6.13.0'} + node-gyp-build-optional-packages@5.1.1: + resolution: {integrity: sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==} + hasBin: true + + node-gyp-build-optional-packages@5.2.2: + resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==} + hasBin: true + node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -11807,9 +12003,6 @@ packages: node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} - node-releases@2.0.18: - resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} - non-layered-tidy-tree-layout@2.0.2: resolution: {integrity: sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==} @@ -11865,9 +12058,6 @@ packages: nwsapi@2.2.10: resolution: {integrity: sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==} - nwsapi@2.2.12: - resolution: {integrity: sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==} - oauth-sign@0.9.0: resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} @@ -11982,6 +12172,9 @@ packages: resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} engines: {node: '>=10'} + ordered-binary@1.5.2: + resolution: {integrity: sha512-JTo+4+4Fw7FreyAvlSLjb1BBVaxEQAacmjD3jjuyPZclpbEghTvQZbXBb2qPd2LeIMxiHwXBZUcpmG2Gl/mDEA==} + os-locale@5.0.0: resolution: {integrity: sha512-tqZcNEDAIZKBEPnHPlVDvKrp7NzgLi7jRmhKiUoa2NUmhl13FtkAGLUVR+ZsYvApBQdBfYm43A4tXXQ4IrYLBA==} engines: {node: '>=10'} @@ -12080,6 +12273,11 @@ packages: param-case@3.0.4: resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} + parcel@2.12.0: + resolution: {integrity: sha512-W+gxAq7aQ9dJIg/XLKGcRT0cvnStFAQHPaI0pvD0U2l6IVLueUAm3nwN7lkY62zZNmlvNx6jNtE4wlbS+CyqSg==} + engines: {node: '>= 12.0.0'} + hasBin: true + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -12284,21 +12482,11 @@ packages: engines: {node: '>=18'} hasBin: true - playwright-core@1.47.0: - resolution: {integrity: sha512-1DyHT8OqkcfCkYUD9zzUTfg7EfTd+6a8MkD/NWOvjo0u/SCNd5YmY/lJwFvUZOxJbWNds+ei7ic2+R/cRz/PDg==} - engines: {node: '>=18'} - hasBin: true - playwright@1.45.1: resolution: {integrity: sha512-Hjrgae4kpSQBr98nhCj3IScxVeVUixqj+5oyif8TdIn2opTCPEzqAqNMeK42i3cWDCVu9MI+ZsGWw+gVR4ISBg==} engines: {node: '>=18'} hasBin: true - playwright@1.47.0: - resolution: {integrity: sha512-jOWiRq2pdNAX/mwLiwFYnPHpEZ4rM+fRSQpRHwEwZlP2PUANvL3+aJOF/bvISMhFD30rqMxUB4RJx9aQbfh4Ww==} - engines: {node: '>=18'} - hasBin: true - polished@4.3.1: resolution: {integrity: sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==} engines: {node: '>=10'} @@ -12579,6 +12767,22 @@ packages: resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==} engines: {node: ^10 || ^12 || >=14} + posthtml-parser@0.10.2: + resolution: {integrity: sha512-PId6zZ/2lyJi9LiKfe+i2xv57oEjJgWbsHGGANwos5AvdQp98i6AtamAl8gzSVFGfQ43Glb5D614cvZf012VKg==} + engines: {node: '>=12'} + + posthtml-parser@0.11.0: + resolution: {integrity: sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==} + engines: {node: '>=12'} + + posthtml-render@3.0.0: + resolution: {integrity: sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==} + engines: {node: '>=12'} + + posthtml@0.16.6: + resolution: {integrity: sha512-JcEmHlyLK/o0uGAlj65vgg+7LIms0xKXe60lcDOTU7oVX/3LuEuLwrQpW3VJ7de5TaFKiW4kWkaIpJL42FEgxQ==} + engines: {node: '>=12.0.0'} + preferred-pm@3.1.4: resolution: {integrity: sha512-lEHd+yEm22jXdCphDrkvIJQU66EuLojPPtvZkpKIkiD+l0DMThF/niqZKJSoU8Vl7iuvtmzyMhir9LdVy5WMnA==} engines: {node: '>=10'} @@ -12853,6 +13057,9 @@ packages: peerDependencies: react: '>=16.13.1' + react-error-overlay@6.0.9: + resolution: {integrity: sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==} + react-fast-compare@2.0.4: resolution: {integrity: sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==} @@ -12915,6 +13122,10 @@ packages: resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} engines: {node: '>=0.10.0'} + react-refresh@0.9.0: + resolution: {integrity: sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ==} + engines: {node: '>=0.10.0'} + react-remove-scroll-bar@2.3.4: resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==} engines: {node: '>=10'} @@ -13060,9 +13271,6 @@ packages: redux@4.2.1: resolution: {integrity: sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==} - redux@5.0.1: - resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} - regenerate-unicode-properties@10.1.1: resolution: {integrity: sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==} engines: {node: '>=4'} @@ -13348,11 +13556,6 @@ packages: engines: {node: '>=14.0.0'} hasBin: true - sass@1.78.0: - resolution: {integrity: sha512-AaIqGSrjo5lA2Yg7RvFZrlXDBCp3nV4XP73GrLGvdRWWwk+8H3l0SDvq/5bA4eF+0RFPLuWUk3E+P1U/YqnpsQ==} - engines: {node: '>=14.0.0'} - hasBin: true - sax@1.2.4: resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} @@ -13393,11 +13596,6 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.6.3: - resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} - engines: {node: '>=10'} - hasBin: true - send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} engines: {node: '>= 0.8.0'} @@ -13610,11 +13808,19 @@ packages: sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + srcset@4.0.0: + resolution: {integrity: sha512-wvLeHgcVHKO8Sc/H/5lkGreJQVeYMm9rlmt8PuR1xE31rIuXhuzznUUqAt8MqLhB3MqJdFzlNAfpcWnxiFUcPw==} + engines: {node: '>=12'} + sshpk@1.17.0: resolution: {integrity: sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==} engines: {node: '>=0.10.0'} hasBin: true + stable@0.1.8: + resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} + deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -13841,6 +14047,11 @@ packages: svg-parser@2.0.4: resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} + svgo@2.8.0: + resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} + engines: {node: '>=10.13.0'} + hasBin: true + svgo@3.0.2: resolution: {integrity: sha512-Z706C1U2pb1+JGP48fbazf3KxHrWOsLme6Rv7imFBn5EnuanDW1GPaA/P1/dvObE670JDePC3mnj0k0B7P0jjQ==} engines: {node: '>=14.0.0'} @@ -13940,11 +14151,6 @@ packages: engines: {node: '>=10'} hasBin: true - terser@5.32.0: - resolution: {integrity: sha512-v3Gtw3IzpBJ0ugkxEX8U0W6+TnPKRRCWGh1jC/iM/e3Ki5+qvO1L1EAZ56bZasc64aXHwRHNIQEzm6//i5cemQ==} - engines: {node: '>=10'} - hasBin: true - test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -13975,6 +14181,9 @@ packages: throwback@4.1.0: resolution: {integrity: sha512-dLFe8bU8SeH0xeqeKL7BNo8XoPC/o91nz9/ooeplZPiso+DZukhoyZcSz9TFnUNScm+cA9qjU1m1853M6sPOng==} + timsort@0.3.0: + resolution: {integrity: sha512-qsdtZH+vMoCARQtyod4imc2nIJwg9Cc7lPRrw9CzF8ZKR0khdr8+2nX80PBhET3tcyTtJDxAffGh2rXH4tyU8A==} + tiny-case@1.0.3: resolution: {integrity: sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==} @@ -14250,10 +14459,6 @@ packages: resolution: {integrity: sha512-ADn2w7hVPcK6w1I0uWnM//y1rLXZhzB9mr0a3OirzclKF1Wp6VzevUmzz/NRAWunOT6E8HrnpGY7xOfc6K57fA==} engines: {node: '>=16'} - type-fest@4.26.1: - resolution: {integrity: sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==} - engines: {node: '>=16'} - type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -14283,6 +14488,12 @@ packages: typedarray@0.0.6: resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + typedoc-plugin-mermaid@1.12.0: + resolution: {integrity: sha512-CRjw29j0YbQEh4ygG7xeGjq8zKUikgd1BBBrW3ttzTeCPLrNKZopWFPd1/leSb9dUEJ3p9exOO84aP4CgjYp3g==} + engines: {node: '>=16.0.0'} + peerDependencies: + typedoc: '>=0.23.0 || 0.24.x || 0.25.x || 0.26.x' + typedoc@0.26.3: resolution: {integrity: sha512-6d2Sw9disvvpdk4K7VNjKr5/3hzijtfQVHRthhDqJgnhMHy1wQz4yPMJVKXElvnZhFr0nkzo+GzjXDTRV5yLpg==} engines: {node: '>= 18'} @@ -14309,11 +14520,6 @@ packages: engines: {node: '>=14.17'} hasBin: true - typescript@5.6.2: - resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} - engines: {node: '>=14.17'} - hasBin: true - ua-parser-js@1.0.37: resolution: {integrity: sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==} @@ -14343,9 +14549,6 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} - unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} engines: {node: '>=4'} @@ -14584,6 +14787,10 @@ packages: utila@0.4.0: resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==} + utility-types@3.11.0: + resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==} + engines: {node: '>= 4'} + utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} @@ -14762,6 +14969,9 @@ packages: wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + weak-lru-cache@1.2.2: + resolution: {integrity: sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw==} + web-ext@7.6.2: resolution: {integrity: sha512-xlxbzgFBIS/UWWlvWxyR1PIqRRzDj1cutoHh+VZu4ZTcJTfv35KVdKkLRZv4PQwHu4dg8VfTg7WEcNP4QLaaFQ==} engines: {node: '>=14.0.0', npm: '>=6.9.0'} @@ -15240,7 +15450,7 @@ snapshots: '@amplitude/utils@1.10.2': dependencies: '@amplitude/types': 1.10.2 - tslib: 2.6.3 + tslib: 2.7.0 '@ampproject/remapping@2.3.0': dependencies: @@ -15297,8 +15507,6 @@ snapshots: '@babel/compat-data@7.24.7': {} - '@babel/compat-data@7.25.4': {} - '@babel/core@7.24.7': dependencies: '@ampproject/remapping': 2.3.0 @@ -15319,26 +15527,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/core@7.25.2': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.25.6 - '@babel/helper-compilation-targets': 7.25.2 - '@babel/helper-module-transforms': 7.25.2(@babel/core@7.25.2) - '@babel/helpers': 7.25.6 - '@babel/parser': 7.25.6 - '@babel/template': 7.25.0 - '@babel/traverse': 7.25.6 - '@babel/types': 7.25.6 - convert-source-map: 2.0.0 - debug: 4.3.7 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 7.6.3 - transitivePeerDependencies: - - supports-color - '@babel/eslint-parser@7.18.9(@babel/core@7.24.7)(eslint@8.45.0)': dependencies: '@babel/core': 7.24.7 @@ -15361,13 +15549,6 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 - '@babel/generator@7.25.6': - dependencies: - '@babel/types': 7.25.6 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 - '@babel/helper-annotate-as-pure@7.24.7': dependencies: '@babel/types': 7.24.7 @@ -15387,14 +15568,6 @@ snapshots: lru-cache: 5.1.1 semver: 7.6.2 - '@babel/helper-compilation-targets@7.25.2': - dependencies: - '@babel/compat-data': 7.25.4 - '@babel/helper-validator-option': 7.24.8 - browserslist: 4.23.3 - lru-cache: 5.1.1 - semver: 7.6.3 - '@babel/helper-create-class-features-plugin@7.24.7(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 @@ -15478,16 +15651,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.25.2(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - '@babel/traverse': 7.25.6 - transitivePeerDependencies: - - supports-color - '@babel/helper-optimise-call-expression@7.24.7': dependencies: '@babel/types': 7.24.7 @@ -15496,8 +15659,6 @@ snapshots: '@babel/helper-plugin-utils@7.24.7': {} - '@babel/helper-plugin-utils@7.24.8': {} - '@babel/helper-remap-async-to-generator@7.24.7(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 @@ -15538,8 +15699,6 @@ snapshots: '@babel/helper-string-parser@7.24.7': {} - '@babel/helper-string-parser@7.24.8': {} - '@babel/helper-validator-identifier@7.22.20': {} '@babel/helper-validator-identifier@7.22.5': {} @@ -15548,8 +15707,6 @@ snapshots: '@babel/helper-validator-option@7.24.7': {} - '@babel/helper-validator-option@7.24.8': {} - '@babel/helper-wrap-function@7.24.7': dependencies: '@babel/helper-function-name': 7.24.7 @@ -15564,11 +15721,6 @@ snapshots: '@babel/template': 7.24.7 '@babel/types': 7.24.7 - '@babel/helpers@7.25.6': - dependencies: - '@babel/template': 7.25.0 - '@babel/types': 7.25.6 - '@babel/highlight@7.22.5': dependencies: '@babel/helper-validator-identifier': 7.22.5 @@ -15600,10 +15752,6 @@ snapshots: dependencies: '@babel/types': 7.24.7 - '@babel/parser@7.25.6': - dependencies: - '@babel/types': 7.25.6 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.7(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 @@ -15746,11 +15894,6 @@ snapshots: '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-import-assertions@7.23.3(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 @@ -15781,11 +15924,6 @@ snapshots: '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 @@ -16211,17 +16349,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2)': - dependencies: - '@babel/core': 7.25.2 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-plugin-utils': 7.24.8 - '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.25.2) - '@babel/types': 7.25.6 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-react-pure-annotations@7.18.6(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 @@ -16523,12 +16650,6 @@ snapshots: '@babel/parser': 7.24.7 '@babel/types': 7.24.7 - '@babel/template@7.25.0': - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/parser': 7.25.6 - '@babel/types': 7.25.6 - '@babel/traverse@7.23.9': dependencies: '@babel/code-frame': 7.24.7 @@ -16559,18 +16680,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/traverse@7.25.6': - dependencies: - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.25.6 - '@babel/parser': 7.25.6 - '@babel/template': 7.25.0 - '@babel/types': 7.25.6 - debug: 4.3.7 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - '@babel/types@7.23.9': dependencies: '@babel/helper-string-parser': 7.23.4 @@ -16583,12 +16692,6 @@ snapshots: '@babel/helper-validator-identifier': 7.24.7 to-fast-properties: 2.0.0 - '@babel/types@7.25.6': - dependencies: - '@babel/helper-string-parser': 7.24.8 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 - '@base2/pretty-print-object@1.0.1': {} '@blakeembrey/deque@1.0.5': {} @@ -17044,9 +17147,6 @@ snapshots: '@esbuild/aix-ppc64@0.23.0': optional: true - '@esbuild/aix-ppc64@0.23.1': - optional: true - '@esbuild/android-arm64@0.18.20': optional: true @@ -17056,9 +17156,6 @@ snapshots: '@esbuild/android-arm64@0.23.0': optional: true - '@esbuild/android-arm64@0.23.1': - optional: true - '@esbuild/android-arm@0.18.20': optional: true @@ -17068,9 +17165,6 @@ snapshots: '@esbuild/android-arm@0.23.0': optional: true - '@esbuild/android-arm@0.23.1': - optional: true - '@esbuild/android-x64@0.18.20': optional: true @@ -17080,9 +17174,6 @@ snapshots: '@esbuild/android-x64@0.23.0': optional: true - '@esbuild/android-x64@0.23.1': - optional: true - '@esbuild/darwin-arm64@0.18.20': optional: true @@ -17092,9 +17183,6 @@ snapshots: '@esbuild/darwin-arm64@0.23.0': optional: true - '@esbuild/darwin-arm64@0.23.1': - optional: true - '@esbuild/darwin-x64@0.18.20': optional: true @@ -17104,9 +17192,6 @@ snapshots: '@esbuild/darwin-x64@0.23.0': optional: true - '@esbuild/darwin-x64@0.23.1': - optional: true - '@esbuild/freebsd-arm64@0.18.20': optional: true @@ -17116,9 +17201,6 @@ snapshots: '@esbuild/freebsd-arm64@0.23.0': optional: true - '@esbuild/freebsd-arm64@0.23.1': - optional: true - '@esbuild/freebsd-x64@0.18.20': optional: true @@ -17128,9 +17210,6 @@ snapshots: '@esbuild/freebsd-x64@0.23.0': optional: true - '@esbuild/freebsd-x64@0.23.1': - optional: true - '@esbuild/linux-arm64@0.18.20': optional: true @@ -17140,9 +17219,6 @@ snapshots: '@esbuild/linux-arm64@0.23.0': optional: true - '@esbuild/linux-arm64@0.23.1': - optional: true - '@esbuild/linux-arm@0.18.20': optional: true @@ -17152,9 +17228,6 @@ snapshots: '@esbuild/linux-arm@0.23.0': optional: true - '@esbuild/linux-arm@0.23.1': - optional: true - '@esbuild/linux-ia32@0.18.20': optional: true @@ -17164,9 +17237,6 @@ snapshots: '@esbuild/linux-ia32@0.23.0': optional: true - '@esbuild/linux-ia32@0.23.1': - optional: true - '@esbuild/linux-loong64@0.18.20': optional: true @@ -17176,9 +17246,6 @@ snapshots: '@esbuild/linux-loong64@0.23.0': optional: true - '@esbuild/linux-loong64@0.23.1': - optional: true - '@esbuild/linux-mips64el@0.18.20': optional: true @@ -17188,9 +17255,6 @@ snapshots: '@esbuild/linux-mips64el@0.23.0': optional: true - '@esbuild/linux-mips64el@0.23.1': - optional: true - '@esbuild/linux-ppc64@0.18.20': optional: true @@ -17200,9 +17264,6 @@ snapshots: '@esbuild/linux-ppc64@0.23.0': optional: true - '@esbuild/linux-ppc64@0.23.1': - optional: true - '@esbuild/linux-riscv64@0.18.20': optional: true @@ -17212,9 +17273,6 @@ snapshots: '@esbuild/linux-riscv64@0.23.0': optional: true - '@esbuild/linux-riscv64@0.23.1': - optional: true - '@esbuild/linux-s390x@0.18.20': optional: true @@ -17224,9 +17282,6 @@ snapshots: '@esbuild/linux-s390x@0.23.0': optional: true - '@esbuild/linux-s390x@0.23.1': - optional: true - '@esbuild/linux-x64@0.18.20': optional: true @@ -17236,9 +17291,6 @@ snapshots: '@esbuild/linux-x64@0.23.0': optional: true - '@esbuild/linux-x64@0.23.1': - optional: true - '@esbuild/netbsd-x64@0.18.20': optional: true @@ -17248,15 +17300,9 @@ snapshots: '@esbuild/netbsd-x64@0.23.0': optional: true - '@esbuild/netbsd-x64@0.23.1': - optional: true - '@esbuild/openbsd-arm64@0.23.0': optional: true - '@esbuild/openbsd-arm64@0.23.1': - optional: true - '@esbuild/openbsd-x64@0.18.20': optional: true @@ -17266,9 +17312,6 @@ snapshots: '@esbuild/openbsd-x64@0.23.0': optional: true - '@esbuild/openbsd-x64@0.23.1': - optional: true - '@esbuild/sunos-x64@0.18.20': optional: true @@ -17278,9 +17321,6 @@ snapshots: '@esbuild/sunos-x64@0.23.0': optional: true - '@esbuild/sunos-x64@0.23.1': - optional: true - '@esbuild/win32-arm64@0.18.20': optional: true @@ -17290,9 +17330,6 @@ snapshots: '@esbuild/win32-arm64@0.23.0': optional: true - '@esbuild/win32-arm64@0.23.1': - optional: true - '@esbuild/win32-ia32@0.18.20': optional: true @@ -17302,9 +17339,6 @@ snapshots: '@esbuild/win32-ia32@0.23.0': optional: true - '@esbuild/win32-ia32@0.23.1': - optional: true - '@esbuild/win32-x64@0.18.20': optional: true @@ -17314,9 +17348,6 @@ snapshots: '@esbuild/win32-x64@0.23.0': optional: true - '@esbuild/win32-x64@0.23.1': - optional: true - '@eslint-community/eslint-utils@4.4.0(eslint@8.36.0)': dependencies: eslint: 8.36.0 @@ -17676,7 +17707,7 @@ snapshots: '@graphql-tools/utils': 10.0.13(graphql@16.9.0) dataloader: 2.2.2 graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 value-or-promise: 1.0.12 '@graphql-tools/code-file-loader@8.1.0(graphql@16.9.0)': @@ -17698,13 +17729,13 @@ snapshots: '@graphql-tools/utils': 10.0.13(graphql@16.9.0) dataloader: 2.2.2 graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 '@graphql-tools/documents@1.0.0(graphql@16.9.0)': dependencies: graphql: 16.9.0 lodash.sortby: 4.7.0 - tslib: 2.6.3 + tslib: 2.7.0 '@graphql-tools/executor-graphql-ws@1.1.0(graphql@16.9.0)': dependencies: @@ -17713,7 +17744,7 @@ snapshots: graphql: 16.9.0 graphql-ws: 5.14.3(graphql@16.9.0) isomorphic-ws: 5.0.0(ws@8.18.0) - tslib: 2.6.3 + tslib: 2.7.0 ws: 8.18.0 transitivePeerDependencies: - bufferutil @@ -17727,7 +17758,7 @@ snapshots: extract-files: 11.0.0 graphql: 16.9.0 meros: 1.3.0(@types/node@20.14.10) - tslib: 2.6.3 + tslib: 2.7.0 value-or-promise: 1.0.12 transitivePeerDependencies: - '@types/node' @@ -17738,7 +17769,7 @@ snapshots: '@types/ws': 8.5.10 graphql: 16.9.0 isomorphic-ws: 5.0.0(ws@8.18.0) - tslib: 2.6.3 + tslib: 2.7.0 ws: 8.18.0 transitivePeerDependencies: - bufferutil @@ -17750,7 +17781,7 @@ snapshots: '@graphql-typed-document-node/core': 3.2.0(graphql@16.9.0) '@repeaterjs/repeater': 3.0.5 graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 value-or-promise: 1.0.12 '@graphql-tools/git-loader@8.0.4(graphql@16.9.0)': @@ -17798,7 +17829,7 @@ snapshots: '@babel/types': 7.23.9 '@graphql-tools/utils': 10.0.13(graphql@16.9.0) graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 transitivePeerDependencies: - supports-color @@ -17807,7 +17838,7 @@ snapshots: '@graphql-tools/utils': 10.0.13(graphql@16.9.0) graphql: 16.9.0 resolve-from: 5.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@graphql-tools/json-file-loader@8.0.0(graphql@16.9.0)': dependencies: @@ -17834,7 +17865,7 @@ snapshots: '@graphql-tools/optimize@2.0.0(graphql@16.9.0)': dependencies: graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 '@graphql-tools/prisma-loader@8.0.2(@types/node@20.14.10)(graphql@16.9.0)': dependencies: @@ -17869,7 +17900,7 @@ snapshots: '@ardatan/relay-compiler': 12.0.0(graphql@16.9.0) '@graphql-tools/utils': 10.0.13(graphql@16.9.0) graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 transitivePeerDependencies: - encoding - supports-color @@ -17879,7 +17910,7 @@ snapshots: '@ardatan/relay-compiler': 12.0.0(graphql@16.9.0) '@graphql-tools/utils': 10.3.1(graphql@16.9.0) graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 transitivePeerDependencies: - encoding - supports-color @@ -17889,7 +17920,7 @@ snapshots: '@graphql-tools/merge': 9.0.1(graphql@16.9.0) '@graphql-tools/utils': 10.0.13(graphql@16.9.0) graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 value-or-promise: 1.0.12 '@graphql-tools/url-loader@8.0.1(@types/node@20.14.10)(graphql@16.9.0)': @@ -17928,7 +17959,7 @@ snapshots: cross-inspect: 1.0.0 dset: 3.1.3 graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 '@graphql-tools/wrap@10.0.1(graphql@16.9.0)': dependencies: @@ -17936,7 +17967,7 @@ snapshots: '@graphql-tools/schema': 10.0.2(graphql@16.9.0) '@graphql-tools/utils': 10.0.13(graphql@16.9.0) graphql: 16.9.0 - tslib: 2.6.3 + tslib: 2.7.0 value-or-promise: 1.0.12 '@graphql-typed-document-node/core@3.2.0(graphql@16.9.0)': @@ -17971,9 +18002,9 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@headlessui/tailwindcss@0.1.3(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3)))': + '@headlessui/tailwindcss@0.1.3(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))': dependencies: - tailwindcss: 3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3)) + tailwindcss: 3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)) '@hookform/resolvers@3.9.0(react-hook-form@7.52.1(react@18.3.1))': dependencies: @@ -18178,19 +18209,43 @@ snapshots: '@ledgerhq/logs@6.12.0': {} - '@manypkg/cli@0.21.4': + '@lezer/common@1.2.2': {} + + '@lezer/lr@1.4.2': dependencies: - '@manypkg/get-packages': 2.2.2 - chalk: 2.4.2 - detect-indent: 6.1.0 - find-up: 4.1.0 - fs-extra: 8.1.0 - normalize-path: 3.0.0 - p-limit: 2.3.0 - package-json: 8.1.1 - parse-github-url: 1.0.3 - sembear: 0.5.2 - semver: 7.6.2 + '@lezer/common': 1.2.2 + + '@lmdb/lmdb-darwin-arm64@2.8.5': + optional: true + + '@lmdb/lmdb-darwin-x64@2.8.5': + optional: true + + '@lmdb/lmdb-linux-arm64@2.8.5': + optional: true + + '@lmdb/lmdb-linux-arm@2.8.5': + optional: true + + '@lmdb/lmdb-linux-x64@2.8.5': + optional: true + + '@lmdb/lmdb-win32-x64@2.8.5': + optional: true + + '@manypkg/cli@0.21.4': + dependencies: + '@manypkg/get-packages': 2.2.2 + chalk: 2.4.2 + detect-indent: 6.1.0 + find-up: 4.1.0 + fs-extra: 8.1.0 + normalize-path: 3.0.0 + p-limit: 2.3.0 + package-json: 8.1.1 + parse-github-url: 1.0.3 + sembear: 0.5.2 + semver: 7.6.2 spawndamnit: 2.0.0 validate-npm-package-name: 3.0.0 @@ -18269,6 +18324,30 @@ snapshots: '@microsoft/tsdoc@0.14.2': {} + '@mischnic/json-sourcemap@0.1.1': + dependencies: + '@lezer/common': 1.2.2 + '@lezer/lr': 1.4.2 + json5: 2.2.3 + + '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3': + optional: true + + '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3': + optional: true + '@mswjs/cookies@1.1.1': {} '@mswjs/interceptors@0.29.1': @@ -18280,6 +18359,71 @@ snapshots: outvariant: 1.4.3 strict-event-emitter: 0.5.1 + '@mysten/bcs@1.1.0': + dependencies: + bs58: 6.0.0 + + '@mysten/dapp-kit@0.14.25(@tanstack/react-query@5.59.0(react@18.3.1))(@types/react-dom@18.3.0)(@types/react@18.3.3)(immer@9.0.21)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3)': + dependencies: + '@mysten/sui': 1.12.0(typescript@5.5.3) + '@mysten/wallet-standard': 0.13.7(typescript@5.5.3) + '@mysten/zksend': 0.11.6(typescript@5.5.3) + '@radix-ui/react-dialog': 1.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-dropdown-menu': 2.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-slot': 1.1.0(@types/react@18.3.3)(react@18.3.1) + '@tanstack/react-query': 5.59.0(react@18.3.1) + '@vanilla-extract/css': 1.15.3(babel-plugin-macros@3.1.0) + '@vanilla-extract/dynamic': 2.1.1 + '@vanilla-extract/recipes': 0.5.3(@vanilla-extract/css@1.15.3(babel-plugin-macros@3.1.0)) + clsx: 2.1.1 + react: 18.3.1 + zustand: 4.5.4(@types/react@18.3.3)(immer@9.0.21)(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + - babel-plugin-macros + - immer + - react-dom + - svelte + - typescript + + '@mysten/sui@1.12.0(typescript@5.5.3)': + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.9.0) + '@mysten/bcs': 1.1.0 + '@noble/curves': 1.4.2 + '@noble/hashes': 1.4.0 + '@scure/bip32': 1.4.0 + '@scure/bip39': 1.3.0 + '@suchipi/femver': 1.0.0 + bech32: 2.0.0 + gql.tada: 1.8.2(graphql@16.9.0)(typescript@5.5.3) + graphql: 16.9.0 + tweetnacl: 1.0.3 + valibot: 0.36.0 + transitivePeerDependencies: + - svelte + - typescript + + '@mysten/wallet-standard@0.13.7(typescript@5.5.3)': + dependencies: + '@mysten/sui': 1.12.0(typescript@5.5.3) + '@wallet-standard/core': 1.0.3 + transitivePeerDependencies: + - svelte + - typescript + + '@mysten/zksend@0.11.6(typescript@5.5.3)': + dependencies: + '@mysten/sui': 1.12.0(typescript@5.5.3) + '@mysten/wallet-standard': 0.13.7(typescript@5.5.3) + mitt: 3.0.1 + nanostores: 0.10.3 + valibot: 0.36.0 + transitivePeerDependencies: + - svelte + - typescript + '@nanostores/react@0.7.2(nanostores@0.10.3)(react@18.3.1)': dependencies: nanostores: 0.10.3 @@ -18338,33 +18482,33 @@ snapshots: pump: 3.0.0 tar-fs: 2.1.1 - '@next/env@14.2.4': {} + '@next/env@14.2.13': {} - '@next/swc-darwin-arm64@14.2.4': + '@next/swc-darwin-arm64@14.2.13': optional: true - '@next/swc-darwin-x64@14.2.4': + '@next/swc-darwin-x64@14.2.13': optional: true - '@next/swc-linux-arm64-gnu@14.2.4': + '@next/swc-linux-arm64-gnu@14.2.13': optional: true - '@next/swc-linux-arm64-musl@14.2.4': + '@next/swc-linux-arm64-musl@14.2.13': optional: true - '@next/swc-linux-x64-gnu@14.2.4': + '@next/swc-linux-x64-gnu@14.2.13': optional: true - '@next/swc-linux-x64-musl@14.2.4': + '@next/swc-linux-x64-musl@14.2.13': optional: true - '@next/swc-win32-arm64-msvc@14.2.4': + '@next/swc-win32-arm64-msvc@14.2.13': optional: true - '@next/swc-win32-ia32-msvc@14.2.4': + '@next/swc-win32-ia32-msvc@14.2.13': optional: true - '@next/swc-win32-x64-msvc@14.2.4': + '@next/swc-win32-x64-msvc@14.2.13': optional: true '@noble/curves@1.4.2': @@ -18411,7 +18555,7 @@ snapshots: debug: 4.3.5(supports-color@8.1.1) globby: 11.1.0 is-wsl: 2.2.0 - tslib: 2.6.3 + tslib: 2.7.0 transitivePeerDependencies: - supports-color @@ -18433,7 +18577,7 @@ snapshots: debug: 4.3.5(supports-color@8.1.1) globby: 11.1.0 is-wsl: 2.2.0 - tslib: 2.6.3 + tslib: 2.7.0 transitivePeerDependencies: - supports-color @@ -18549,6 +18693,520 @@ snapshots: '@open-draft/until@2.1.0': {} + '@parcel/bundler-default@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/diagnostic': 2.12.0 + '@parcel/graph': 3.2.0 + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/rust': 2.12.0 + '@parcel/utils': 2.12.0 + nullthrows: 1.1.1 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/cache@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/core': 2.12.0(@swc/helpers@0.5.5) + '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/logger': 2.12.0 + '@parcel/utils': 2.12.0 + lmdb: 2.8.5 + + '@parcel/codeframe@2.12.0': + dependencies: + chalk: 4.1.2 + + '@parcel/compressor-raw@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/config-default@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)(postcss@8.4.39)(relateurl@0.2.7)(terser@5.31.1)(typescript@5.5.3)': + dependencies: + '@parcel/bundler-default': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/compressor-raw': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/core': 2.12.0(@swc/helpers@0.5.5) + '@parcel/namer-default': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/optimizer-css': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/optimizer-htmlnano': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(postcss@8.4.39)(relateurl@0.2.7)(terser@5.31.1)(typescript@5.5.3) + '@parcel/optimizer-image': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/optimizer-svgo': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/optimizer-swc': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/packager-css': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/packager-html': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/packager-js': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/packager-raw': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/packager-svg': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/packager-wasm': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/reporter-dev-server': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/resolver-default': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/runtime-browser-hmr': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/runtime-js': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/runtime-react-refresh': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/runtime-service-worker': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/transformer-babel': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/transformer-css': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/transformer-html': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/transformer-image': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/transformer-js': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/transformer-json': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/transformer-postcss': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/transformer-posthtml': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/transformer-raw': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/transformer-react-refresh-wrap': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/transformer-svg': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + transitivePeerDependencies: + - '@swc/helpers' + - cssnano + - postcss + - purgecss + - relateurl + - srcset + - terser + - typescript + - uncss + + '@parcel/core@2.12.0(@swc/helpers@0.5.5)': + dependencies: + '@mischnic/json-sourcemap': 0.1.1 + '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/diagnostic': 2.12.0 + '@parcel/events': 2.12.0 + '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/graph': 3.2.0 + '@parcel/logger': 2.12.0 + '@parcel/package-manager': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/profiler': 2.12.0 + '@parcel/rust': 2.12.0 + '@parcel/source-map': 2.1.1 + '@parcel/types': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/utils': 2.12.0 + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + abortcontroller-polyfill: 1.7.5 + base-x: 3.0.10 + browserslist: 4.23.1 + clone: 2.1.2 + dotenv: 7.0.0 + dotenv-expand: 5.1.0 + json5: 2.2.3 + msgpackr: 1.11.0 + nullthrows: 1.1.1 + semver: 7.6.2 + transitivePeerDependencies: + - '@swc/helpers' + + '@parcel/diagnostic@2.12.0': + dependencies: + '@mischnic/json-sourcemap': 0.1.1 + nullthrows: 1.1.1 + + '@parcel/events@2.12.0': {} + + '@parcel/fs@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)': + dependencies: + '@parcel/core': 2.12.0(@swc/helpers@0.5.5) + '@parcel/rust': 2.12.0 + '@parcel/types': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/utils': 2.12.0 + '@parcel/watcher': 2.4.1 + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + transitivePeerDependencies: + - '@swc/helpers' + + '@parcel/graph@3.2.0': + dependencies: + nullthrows: 1.1.1 + + '@parcel/logger@2.12.0': + dependencies: + '@parcel/diagnostic': 2.12.0 + '@parcel/events': 2.12.0 + + '@parcel/markdown-ansi@2.12.0': + dependencies: + chalk: 4.1.2 + + '@parcel/namer-default@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/diagnostic': 2.12.0 + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + nullthrows: 1.1.1 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/node-resolver-core@3.3.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@mischnic/json-sourcemap': 0.1.1 + '@parcel/diagnostic': 2.12.0 + '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/rust': 2.12.0 + '@parcel/utils': 2.12.0 + nullthrows: 1.1.1 + semver: 7.6.2 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/optimizer-css@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/diagnostic': 2.12.0 + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/source-map': 2.1.1 + '@parcel/utils': 2.12.0 + browserslist: 4.23.1 + lightningcss: 1.27.0 + nullthrows: 1.1.1 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/optimizer-htmlnano@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(postcss@8.4.39)(relateurl@0.2.7)(terser@5.31.1)(typescript@5.5.3)': + dependencies: + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + htmlnano: 2.1.1(postcss@8.4.39)(relateurl@0.2.7)(svgo@2.8.0)(terser@5.31.1)(typescript@5.5.3) + nullthrows: 1.1.1 + posthtml: 0.16.6 + svgo: 2.8.0 + transitivePeerDependencies: + - '@parcel/core' + - cssnano + - postcss + - purgecss + - relateurl + - srcset + - terser + - typescript + - uncss + + '@parcel/optimizer-image@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/core': 2.12.0(@swc/helpers@0.5.5) + '@parcel/diagnostic': 2.12.0 + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/rust': 2.12.0 + '@parcel/utils': 2.12.0 + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + + '@parcel/optimizer-svgo@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/diagnostic': 2.12.0 + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/utils': 2.12.0 + svgo: 2.8.0 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/optimizer-swc@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)': + dependencies: + '@parcel/diagnostic': 2.12.0 + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/source-map': 2.1.1 + '@parcel/utils': 2.12.0 + '@swc/core': 1.6.13(@swc/helpers@0.5.5) + nullthrows: 1.1.1 + transitivePeerDependencies: + - '@parcel/core' + - '@swc/helpers' + + '@parcel/package-manager@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)': + dependencies: + '@parcel/core': 2.12.0(@swc/helpers@0.5.5) + '@parcel/diagnostic': 2.12.0 + '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/logger': 2.12.0 + '@parcel/node-resolver-core': 3.3.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/types': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/utils': 2.12.0 + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@swc/core': 1.6.13(@swc/helpers@0.5.5) + semver: 7.6.2 + transitivePeerDependencies: + - '@swc/helpers' + + '@parcel/packager-css@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/diagnostic': 2.12.0 + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/source-map': 2.1.1 + '@parcel/utils': 2.12.0 + lightningcss: 1.27.0 + nullthrows: 1.1.1 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/packager-html@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/types': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/utils': 2.12.0 + nullthrows: 1.1.1 + posthtml: 0.16.6 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/packager-js@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/diagnostic': 2.12.0 + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/rust': 2.12.0 + '@parcel/source-map': 2.1.1 + '@parcel/types': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/utils': 2.12.0 + globals: 13.24.0 + nullthrows: 1.1.1 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/packager-raw@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/packager-svg@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/types': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/utils': 2.12.0 + posthtml: 0.16.6 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/packager-wasm@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/plugin@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/types': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/profiler@2.12.0': + dependencies: + '@parcel/diagnostic': 2.12.0 + '@parcel/events': 2.12.0 + chrome-trace-event: 1.0.4 + + '@parcel/reporter-cli@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/types': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/utils': 2.12.0 + chalk: 4.1.2 + term-size: 2.2.1 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/reporter-dev-server@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/utils': 2.12.0 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/reporter-tracer@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/utils': 2.12.0 + chrome-trace-event: 1.0.4 + nullthrows: 1.1.1 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/resolver-default@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/node-resolver-core': 3.3.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/runtime-browser-hmr@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/utils': 2.12.0 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/runtime-js@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/diagnostic': 2.12.0 + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/utils': 2.12.0 + nullthrows: 1.1.1 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/runtime-react-refresh@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/utils': 2.12.0 + react-error-overlay: 6.0.9 + react-refresh: 0.9.0 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/runtime-service-worker@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/utils': 2.12.0 + nullthrows: 1.1.1 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/rust@2.12.0': {} + + '@parcel/source-map@2.1.1': + dependencies: + detect-libc: 1.0.3 + + '@parcel/transformer-babel@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/diagnostic': 2.12.0 + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/source-map': 2.1.1 + '@parcel/utils': 2.12.0 + browserslist: 4.23.1 + json5: 2.2.3 + nullthrows: 1.1.1 + semver: 7.6.2 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/transformer-css@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/diagnostic': 2.12.0 + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/source-map': 2.1.1 + '@parcel/utils': 2.12.0 + browserslist: 4.23.1 + lightningcss: 1.27.0 + nullthrows: 1.1.1 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/transformer-html@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/diagnostic': 2.12.0 + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/rust': 2.12.0 + nullthrows: 1.1.1 + posthtml: 0.16.6 + posthtml-parser: 0.10.2 + posthtml-render: 3.0.0 + semver: 7.6.2 + srcset: 4.0.0 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/transformer-image@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/core': 2.12.0(@swc/helpers@0.5.5) + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/utils': 2.12.0 + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + nullthrows: 1.1.1 + + '@parcel/transformer-js@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/core': 2.12.0(@swc/helpers@0.5.5) + '@parcel/diagnostic': 2.12.0 + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/rust': 2.12.0 + '@parcel/source-map': 2.1.1 + '@parcel/utils': 2.12.0 + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@swc/helpers': 0.5.5 + browserslist: 4.23.1 + nullthrows: 1.1.1 + regenerator-runtime: 0.13.11 + semver: 7.6.2 + + '@parcel/transformer-json@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + json5: 2.2.3 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/transformer-postcss@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/diagnostic': 2.12.0 + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/rust': 2.12.0 + '@parcel/utils': 2.12.0 + clone: 2.1.2 + nullthrows: 1.1.1 + postcss-value-parser: 4.2.0 + semver: 7.6.2 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/transformer-posthtml@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/utils': 2.12.0 + nullthrows: 1.1.1 + posthtml: 0.16.6 + posthtml-parser: 0.10.2 + posthtml-render: 3.0.0 + semver: 7.6.2 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/transformer-raw@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/transformer-react-refresh-wrap@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/utils': 2.12.0 + react-refresh: 0.9.0 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/transformer-svg@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/diagnostic': 2.12.0 + '@parcel/plugin': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/rust': 2.12.0 + nullthrows: 1.1.1 + posthtml: 0.16.6 + posthtml-parser: 0.10.2 + posthtml-render: 3.0.0 + semver: 7.6.2 + transitivePeerDependencies: + - '@parcel/core' + + '@parcel/types@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)': + dependencies: + '@parcel/cache': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/diagnostic': 2.12.0 + '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/package-manager': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/source-map': 2.1.1 + '@parcel/workers': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + utility-types: 3.11.0 + transitivePeerDependencies: + - '@parcel/core' + - '@swc/helpers' + + '@parcel/utils@2.12.0': + dependencies: + '@parcel/codeframe': 2.12.0 + '@parcel/diagnostic': 2.12.0 + '@parcel/logger': 2.12.0 + '@parcel/markdown-ansi': 2.12.0 + '@parcel/rust': 2.12.0 + '@parcel/source-map': 2.1.1 + chalk: 4.1.2 + nullthrows: 1.1.1 + '@parcel/watcher-android-arm64@2.4.1': optional: true @@ -18605,22 +19263,32 @@ snapshots: '@parcel/watcher-win32-ia32': 2.4.1 '@parcel/watcher-win32-x64': 2.4.1 + '@parcel/workers@2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))': + dependencies: + '@parcel/core': 2.12.0(@swc/helpers@0.5.5) + '@parcel/diagnostic': 2.12.0 + '@parcel/logger': 2.12.0 + '@parcel/profiler': 2.12.0 + '@parcel/types': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/utils': 2.12.0 + nullthrows: 1.1.1 + '@peculiar/asn1-schema@2.3.8': dependencies: asn1js: 3.0.5 pvtsutils: 1.3.5 - tslib: 2.6.3 + tslib: 2.7.0 '@peculiar/json-schema@1.1.12': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 '@peculiar/webcrypto@1.4.5': dependencies: '@peculiar/asn1-schema': 2.3.8 '@peculiar/json-schema': 1.1.12 pvtsutils: 1.3.5 - tslib: 2.6.3 + tslib: 2.7.0 webcrypto-core: 1.7.8 '@phenomnomnominal/tsquery@3.0.0(typescript@3.9.10)': @@ -18637,12 +19305,7 @@ snapshots: dependencies: playwright: 1.45.1 - '@playwright/test@1.47.0': - dependencies: - playwright: 1.47.0 - optional: true - - '@pmmmwh/react-refresh-webpack-plugin@0.5.15(@types/webpack@5.28.5(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4(webpack@5.92.1)))(react-refresh@0.14.2)(type-fest@4.26.1)(webpack-hot-middleware@2.26.1)(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4))': + '@pmmmwh/react-refresh-webpack-plugin@0.5.15(@types/webpack@5.28.5(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)))(react-refresh@0.14.2)(type-fest@4.21.0)(webpack-hot-middleware@2.26.1)(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)))': dependencies: ansi-html: 0.0.9 core-js-pure: 3.31.1 @@ -18652,10 +19315,10 @@ snapshots: react-refresh: 0.14.2 schema-utils: 4.2.0 source-map: 0.7.4 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) optionalDependencies: - '@types/webpack': 5.28.5(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4(webpack@5.92.1)) - type-fest: 4.26.1 + '@types/webpack': 5.28.5(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) + type-fest: 4.21.0 webpack-hot-middleware: 2.26.1 '@pnpm/config.env-replace@1.1.0': {} @@ -19709,7 +20372,7 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 - '@reduxjs/toolkit@1.9.5(react-redux@8.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@5.0.1))(react@18.3.1)': + '@reduxjs/toolkit@1.9.5(react-redux@8.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1))(react@18.3.1)': dependencies: immer: 9.0.21 redux: 4.2.1 @@ -19717,7 +20380,7 @@ snapshots: reselect: 4.1.8 optionalDependencies: react: 18.3.1 - react-redux: 8.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@5.0.1) + react-redux: 8.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1) '@remix-run/router@1.17.1': {} @@ -19791,7 +20454,7 @@ snapshots: '@sentry/core': 7.59.2 '@sentry/types': 7.59.2 '@sentry/utils': 7.59.2 - tslib: 2.6.2 + tslib: 2.7.0 '@sentry-internal/tracing@7.61.0': dependencies: @@ -19843,7 +20506,7 @@ snapshots: dependencies: '@sentry/types': 7.59.2 '@sentry/utils': 7.59.2 - tslib: 2.6.2 + tslib: 2.7.0 '@sentry/core@7.61.0': dependencies: @@ -20148,7 +20811,7 @@ snapshots: - encoding - supports-color - '@storybook/builder-webpack5@7.6.20(@swc/helpers@0.5.13)(typescript@5.5.3)(webpack-cli@5.1.4(webpack@5.92.1))': + '@storybook/builder-webpack5@7.6.20(@swc/helpers@0.5.5)(typescript@5.5.3)(webpack-cli@5.1.4(webpack@5.92.1))': dependencies: '@babel/core': 7.24.7 '@storybook/channels': 7.6.20 @@ -20159,33 +20822,33 @@ snapshots: '@storybook/node-logger': 7.6.20 '@storybook/preview': 7.6.20 '@storybook/preview-api': 7.6.20 - '@swc/core': 1.6.13(@swc/helpers@0.5.13) + '@swc/core': 1.6.13(@swc/helpers@0.5.5) '@types/node': 18.19.39 '@types/semver': 7.5.8 - babel-loader: 9.1.3(@babel/core@7.24.7)(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + babel-loader: 9.1.3(@babel/core@7.24.7)(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) browser-assert: 1.2.1 case-sensitive-paths-webpack-plugin: 2.4.0 cjs-module-lexer: 1.3.1 constants-browserify: 1.0.0 - css-loader: 6.11.0(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + css-loader: 6.11.0(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) es-module-lexer: 1.5.4 express: 4.20.0 - fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.5.3)(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + fork-ts-checker-webpack-plugin: 8.0.0(typescript@5.5.3)(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) fs-extra: 11.2.0 - html-webpack-plugin: 5.6.0(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + html-webpack-plugin: 5.6.0(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) magic-string: 0.30.10 path-browserify: 1.0.1 process: 0.11.10 semver: 7.6.2 - style-loader: 3.3.4(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) - swc-loader: 0.2.6(@swc/core@1.6.13(@swc/helpers@0.5.13))(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) - terser-webpack-plugin: 5.3.10(@swc/core@1.6.13(@swc/helpers@0.5.13))(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + style-loader: 3.3.4(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) + swc-loader: 0.2.6(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) + terser-webpack-plugin: 5.3.10(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) ts-dedent: 2.2.0 url: 0.11.3 util: 0.12.5 util-deprecate: 1.0.2 - webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.13))(webpack-cli@5.1.4(webpack@5.92.1)) - webpack-dev-middleware: 6.1.3(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) + webpack-dev-middleware: 6.1.3(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) webpack-hot-middleware: 2.26.1 webpack-virtual-modules: 0.5.0 optionalDependencies: @@ -20571,16 +21234,16 @@ snapshots: '@storybook/postinstall@7.6.20': {} - '@storybook/preset-react-webpack@7.6.20(@babel/core@7.24.7)(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/webpack@5.28.5(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4(webpack@5.92.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@4.26.1)(typescript@5.5.3)(webpack-cli@5.1.4(webpack@5.92.1))(webpack-hot-middleware@2.26.1)': + '@storybook/preset-react-webpack@7.6.20(@babel/core@7.24.7)(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/webpack@5.28.5(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@4.21.0)(typescript@5.5.3)(webpack-cli@5.1.4(webpack@5.92.1))(webpack-hot-middleware@2.26.1)': dependencies: '@babel/preset-flow': 7.24.7(@babel/core@7.24.7) '@babel/preset-react': 7.24.7(@babel/core@7.24.7) - '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(@types/webpack@5.28.5(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4(webpack@5.92.1)))(react-refresh@0.14.2)(type-fest@4.26.1)(webpack-hot-middleware@2.26.1)(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + '@pmmmwh/react-refresh-webpack-plugin': 0.5.15(@types/webpack@5.28.5(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)))(react-refresh@0.14.2)(type-fest@4.21.0)(webpack-hot-middleware@2.26.1)(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) '@storybook/core-webpack': 7.6.20 '@storybook/docs-tools': 7.6.20 '@storybook/node-logger': 7.6.20 '@storybook/react': 7.6.20(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.5.3)(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + '@storybook/react-docgen-typescript-plugin': 1.0.6--canary.9.0c3f3b7.0(typescript@5.5.3)(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) '@types/node': 18.19.39 '@types/semver': 7.5.8 babel-plugin-add-react-displayname: 0.0.5 @@ -20591,7 +21254,7 @@ snapshots: react-dom: 18.3.1(react@18.3.1) react-refresh: 0.14.2 semver: 7.6.2 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) optionalDependencies: '@babel/core': 7.24.7 typescript: 5.5.3 @@ -20663,7 +21326,7 @@ snapshots: '@storybook/preview@7.6.20': {} - '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.5.3)(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4))': + '@storybook/react-docgen-typescript-plugin@1.0.6--canary.9.0c3f3b7.0(typescript@5.5.3)(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)))': dependencies: debug: 4.3.7 endent: 2.1.0 @@ -20671,9 +21334,9 @@ snapshots: flat-cache: 3.2.0 micromatch: 4.0.7 react-docgen-typescript: 2.2.2(typescript@5.5.3) - tslib: 2.6.3 + tslib: 2.7.0 typescript: 5.5.3 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) transitivePeerDependencies: - supports-color @@ -20682,10 +21345,10 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/react-webpack5@7.6.20(@babel/core@7.24.7)(@swc/core@1.7.24(@swc/helpers@0.5.13))(@swc/helpers@0.5.13)(@types/webpack@5.28.5(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4(webpack@5.92.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@4.26.1)(typescript@5.5.3)(webpack-cli@5.1.4(webpack@5.92.1))(webpack-hot-middleware@2.26.1)': + '@storybook/react-webpack5@7.6.20(@babel/core@7.24.7)(@swc/core@1.6.13(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)(@types/webpack@5.28.5(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@4.21.0)(typescript@5.5.3)(webpack-cli@5.1.4(webpack@5.92.1))(webpack-hot-middleware@2.26.1)': dependencies: - '@storybook/builder-webpack5': 7.6.20(@swc/helpers@0.5.13)(typescript@5.5.3)(webpack-cli@5.1.4(webpack@5.92.1)) - '@storybook/preset-react-webpack': 7.6.20(@babel/core@7.24.7)(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/webpack@5.28.5(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4(webpack@5.92.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@4.26.1)(typescript@5.5.3)(webpack-cli@5.1.4(webpack@5.92.1))(webpack-hot-middleware@2.26.1) + '@storybook/builder-webpack5': 7.6.20(@swc/helpers@0.5.5)(typescript@5.5.3)(webpack-cli@5.1.4(webpack@5.92.1)) + '@storybook/preset-react-webpack': 7.6.20(@babel/core@7.24.7)(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/webpack@5.28.5(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(type-fest@4.21.0)(typescript@5.5.3)(webpack-cli@5.1.4(webpack@5.92.1))(webpack-hot-middleware@2.26.1) '@storybook/react': 7.6.20(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) '@types/node': 18.19.39 react: 18.3.1 @@ -20922,64 +21585,34 @@ snapshots: '@swc/core-darwin-arm64@1.6.13': optional: true - '@swc/core-darwin-arm64@1.7.24': - optional: true - '@swc/core-darwin-x64@1.6.13': optional: true - '@swc/core-darwin-x64@1.7.24': - optional: true - '@swc/core-linux-arm-gnueabihf@1.6.13': optional: true - '@swc/core-linux-arm-gnueabihf@1.7.24': - optional: true - '@swc/core-linux-arm64-gnu@1.6.13': optional: true - '@swc/core-linux-arm64-gnu@1.7.24': - optional: true - '@swc/core-linux-arm64-musl@1.6.13': optional: true - '@swc/core-linux-arm64-musl@1.7.24': - optional: true - '@swc/core-linux-x64-gnu@1.6.13': optional: true - '@swc/core-linux-x64-gnu@1.7.24': - optional: true - '@swc/core-linux-x64-musl@1.6.13': optional: true - '@swc/core-linux-x64-musl@1.7.24': - optional: true - '@swc/core-win32-arm64-msvc@1.6.13': optional: true - '@swc/core-win32-arm64-msvc@1.7.24': - optional: true - '@swc/core-win32-ia32-msvc@1.6.13': optional: true - '@swc/core-win32-ia32-msvc@1.7.24': - optional: true - '@swc/core-win32-x64-msvc@1.6.13': optional: true - '@swc/core-win32-x64-msvc@1.7.24': - optional: true - - '@swc/core@1.6.13(@swc/helpers@0.5.13)': + '@swc/core@1.6.13(@swc/helpers@0.5.5)': dependencies: '@swc/counter': 0.1.3 '@swc/types': 0.1.9 @@ -20994,42 +21627,14 @@ snapshots: '@swc/core-win32-arm64-msvc': 1.6.13 '@swc/core-win32-ia32-msvc': 1.6.13 '@swc/core-win32-x64-msvc': 1.6.13 - '@swc/helpers': 0.5.13 - - '@swc/core@1.7.24(@swc/helpers@0.5.13)': - dependencies: - '@swc/counter': 0.1.3 - '@swc/types': 0.1.12 - optionalDependencies: - '@swc/core-darwin-arm64': 1.7.24 - '@swc/core-darwin-x64': 1.7.24 - '@swc/core-linux-arm-gnueabihf': 1.7.24 - '@swc/core-linux-arm64-gnu': 1.7.24 - '@swc/core-linux-arm64-musl': 1.7.24 - '@swc/core-linux-x64-gnu': 1.7.24 - '@swc/core-linux-x64-musl': 1.7.24 - '@swc/core-win32-arm64-msvc': 1.7.24 - '@swc/core-win32-ia32-msvc': 1.7.24 - '@swc/core-win32-x64-msvc': 1.7.24 - '@swc/helpers': 0.5.13 - optional: true + '@swc/helpers': 0.5.5 '@swc/counter@0.1.3': {} - '@swc/helpers@0.5.13': - dependencies: - tslib: 2.7.0 - optional: true - '@swc/helpers@0.5.5': dependencies: '@swc/counter': 0.1.3 - tslib: 2.6.3 - - '@swc/types@0.1.12': - dependencies: - '@swc/counter': 0.1.3 - optional: true + tslib: 2.7.0 '@swc/types@0.1.9': dependencies: @@ -21039,14 +21644,14 @@ snapshots: dependencies: defer-to-connect: 2.0.1 - '@tailwindcss/aspect-ratio@0.4.2(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3)))': + '@tailwindcss/aspect-ratio@0.4.2(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))': dependencies: - tailwindcss: 3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3)) + tailwindcss: 3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)) - '@tailwindcss/forms@0.5.7(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3)))': + '@tailwindcss/forms@0.5.7(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3)))': dependencies: mini-svg-data-uri: 1.4.4 - tailwindcss: 3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3)) + tailwindcss: 3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)) '@tanstack/eslint-plugin-query@5.50.1(eslint@8.45.0)(typescript@5.5.3)': dependencies: @@ -21060,6 +21665,8 @@ snapshots: '@tanstack/query-core@5.50.1': {} + '@tanstack/query-core@5.59.0': {} + '@tanstack/query-devtools@5.51.1': {} '@tanstack/query-persist-client-core@4.29.25': @@ -21082,6 +21689,11 @@ snapshots: '@tanstack/query-core': 5.50.1 react: 18.3.1 + '@tanstack/react-query@5.59.0(react@18.3.1)': + dependencies: + '@tanstack/query-core': 5.59.0 + react: 18.3.1 + '@tanstack/react-virtual@3.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@tanstack/virtual-core': 3.5.0 @@ -21101,7 +21713,7 @@ snapshots: lz-string: 1.5.0 pretty-format: 27.5.1 - '@testing-library/jest-dom@6.4.6(vitest@2.0.1(@types/node@22.5.4)(happy-dom@15.7.3)(jsdom@24.1.0)(sass@1.78.0)(terser@5.32.0))': + '@testing-library/jest-dom@6.4.6(vitest@2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1))': dependencies: '@adobe/css-tools': 4.4.0 '@babel/runtime': 7.24.7 @@ -21112,7 +21724,7 @@ snapshots: lodash: 4.17.21 redent: 3.0.0 optionalDependencies: - vitest: 2.0.1(@types/node@22.5.4)(happy-dom@15.7.3)(jsdom@24.1.0)(sass@1.78.0)(terser@5.32.0) + vitest: 2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) '@testing-library/react@16.0.0(@testing-library/dom@10.3.1)(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -21217,11 +21829,11 @@ snapshots: '@types/doctrine@0.0.9': {} - '@types/dotenv-webpack@7.0.7(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4(webpack@5.92.1))': + '@types/dotenv-webpack@7.0.7(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))': dependencies: '@types/node': 20.14.10 tapable: 2.2.1 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) transitivePeerDependencies: - '@swc/core' - esbuild @@ -21244,12 +21856,6 @@ snapshots: '@types/estree': 1.0.5 '@types/json-schema': 7.0.15 - '@types/eslint@9.6.1': - dependencies: - '@types/estree': 1.0.5 - '@types/json-schema': 7.0.15 - optional: true - '@types/estree-jsx@1.0.5': dependencies: '@types/estree': 1.0.5 @@ -21381,10 +21987,6 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/node@22.5.4': - dependencies: - undici-types: 6.19.8 - '@types/normalize-package-data@2.4.1': {} '@types/parse-json@4.0.2': {} @@ -21451,11 +22053,11 @@ snapshots: '@types/webextension-polyfill@0.10.7': {} - '@types/webpack@5.28.5(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4(webpack@5.92.1))': + '@types/webpack@5.28.5(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))': dependencies: '@types/node': 20.14.10 tapable: 2.2.1 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) transitivePeerDependencies: - '@swc/core' - esbuild @@ -21848,9 +22450,9 @@ snapshots: dependencies: '@vanilla-extract/private': 1.0.5 - '@vanilla-extract/esbuild-plugin@2.3.8(@types/node@20.14.10)(babel-plugin-macros@3.1.0)(esbuild@0.23.0)(sass@1.78.0)(terser@5.32.0)': + '@vanilla-extract/esbuild-plugin@2.3.8(@types/node@20.14.10)(babel-plugin-macros@3.1.0)(esbuild@0.23.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)': dependencies: - '@vanilla-extract/integration': 7.1.7(@types/node@20.14.10)(babel-plugin-macros@3.1.0)(sass@1.78.0)(terser@5.32.0) + '@vanilla-extract/integration': 7.1.7(@types/node@20.14.10)(babel-plugin-macros@3.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) optionalDependencies: esbuild: 0.23.0 transitivePeerDependencies: @@ -21864,23 +22466,7 @@ snapshots: - supports-color - terser - '@vanilla-extract/esbuild-plugin@2.3.8(@types/node@22.5.4)(babel-plugin-macros@3.1.0)(esbuild@0.23.1)(sass@1.78.0)(terser@5.32.0)': - dependencies: - '@vanilla-extract/integration': 7.1.7(@types/node@22.5.4)(babel-plugin-macros@3.1.0)(sass@1.78.0)(terser@5.32.0) - optionalDependencies: - esbuild: 0.23.1 - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - - '@vanilla-extract/integration@7.1.7(@types/node@20.14.10)(babel-plugin-macros@3.1.0)(sass@1.78.0)(terser@5.32.0)': + '@vanilla-extract/integration@7.1.7(@types/node@20.14.10)(babel-plugin-macros@3.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)': dependencies: '@babel/core': 7.24.7 '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.24.7) @@ -21892,33 +22478,8 @@ snapshots: find-up: 5.0.0 javascript-stringify: 2.1.0 mlly: 1.7.1 - vite: 5.3.3(@types/node@20.14.10)(sass@1.78.0)(terser@5.32.0) - vite-node: 1.6.0(@types/node@20.14.10)(sass@1.78.0)(terser@5.32.0) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - - '@vanilla-extract/integration@7.1.7(@types/node@22.5.4)(babel-plugin-macros@3.1.0)(sass@1.78.0)(terser@5.32.0)': - dependencies: - '@babel/core': 7.24.7 - '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.24.7) - '@vanilla-extract/babel-plugin-debug-ids': 1.0.6 - '@vanilla-extract/css': 1.15.3(babel-plugin-macros@3.1.0) - dedent: 1.5.3(babel-plugin-macros@3.1.0) - esbuild: 0.21.5 - eval: 0.1.8 - find-up: 5.0.0 - javascript-stringify: 2.1.0 - mlly: 1.7.1 - vite: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) - vite-node: 1.6.0(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) + vite: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) + vite-node: 1.6.0(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -21936,10 +22497,10 @@ snapshots: dependencies: '@vanilla-extract/css': 1.15.3(babel-plugin-macros@3.1.0) - '@vanilla-extract/vite-plugin@4.0.13(@types/node@22.5.4)(babel-plugin-macros@3.1.0)(sass@1.78.0)(terser@5.32.0)(vite@5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))': + '@vanilla-extract/vite-plugin@4.0.13(@types/node@20.14.10)(babel-plugin-macros@3.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1))': dependencies: - '@vanilla-extract/integration': 7.1.7(@types/node@22.5.4)(babel-plugin-macros@3.1.0)(sass@1.78.0)(terser@5.32.0) - vite: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) + '@vanilla-extract/integration': 7.1.7(@types/node@20.14.10)(babel-plugin-macros@3.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) + vite: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -21951,39 +22512,21 @@ snapshots: - supports-color - terser - '@vitejs/plugin-react-swc@3.7.0(@swc/helpers@0.5.13)(vite@5.3.3(@types/node@20.14.10)(sass@1.78.0)(terser@5.32.0))': - dependencies: - '@swc/core': 1.6.13(@swc/helpers@0.5.13) - vite: 5.3.3(@types/node@20.14.10)(sass@1.78.0)(terser@5.32.0) - transitivePeerDependencies: - - '@swc/helpers' - - '@vitejs/plugin-react-swc@3.7.0(@swc/helpers@0.5.13)(vite@5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))': + '@vitejs/plugin-react-swc@3.7.0(@swc/helpers@0.5.5)(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1))': dependencies: - '@swc/core': 1.6.13(@swc/helpers@0.5.13) - vite: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) + '@swc/core': 1.6.13(@swc/helpers@0.5.5) + vite: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) transitivePeerDependencies: - '@swc/helpers' - '@vitejs/plugin-react@4.3.1(vite@5.3.3(@types/node@20.14.10)(sass@1.77.6)(terser@5.32.0))': - dependencies: - '@babel/core': 7.24.7 - '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.7) - '@types/babel__core': 7.20.5 - react-refresh: 0.14.2 - vite: 5.3.3(@types/node@20.14.10)(sass@1.77.6)(terser@5.32.0) - transitivePeerDependencies: - - supports-color - - '@vitejs/plugin-react@4.3.1(vite@5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0))': + '@vitejs/plugin-react@4.3.1(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1))': dependencies: '@babel/core': 7.24.7 '@babel/plugin-transform-react-jsx-self': 7.24.7(@babel/core@7.24.7) '@babel/plugin-transform-react-jsx-source': 7.24.7(@babel/core@7.24.7) '@types/babel__core': 7.20.5 react-refresh: 0.14.2 - vite: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) + vite: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) transitivePeerDependencies: - supports-color @@ -22027,7 +22570,7 @@ snapshots: '@vue/shared': 3.4.31 entities: 4.5.0 estree-walker: 2.0.2 - source-map-js: 1.2.0 + source-map-js: 1.2.1 '@vue/compiler-dom@3.4.31': dependencies: @@ -22146,19 +22689,19 @@ snapshots: '@webassemblyjs/ast': 1.12.1 '@xtuc/long': 4.2.2 - '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack@5.92.1))(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4))': + '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack@5.92.1))(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)))': dependencies: - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) webpack-cli: 5.1.4(webpack@5.92.1) - '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack@5.92.1))(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4))': + '@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack@5.92.1))(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)))': dependencies: - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) webpack-cli: 5.1.4(webpack@5.92.1) - '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack@5.92.1))(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4))': + '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack@5.92.1))(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)))': dependencies: - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) webpack-cli: 5.1.4(webpack@5.92.1) '@whatwg-node/events@0.0.3': {} @@ -22184,7 +22727,7 @@ snapshots: busboy: 1.6.0 fast-querystring: 1.1.2 fast-url-parser: 1.1.3 - tslib: 2.6.3 + tslib: 2.7.0 '@whatwg-node/node-fetch@0.5.5': dependencies: @@ -22192,7 +22735,7 @@ snapshots: '@whatwg-node/events': 0.1.1 busboy: 1.6.0 fast-querystring: 1.1.2 - tslib: 2.6.3 + tslib: 2.7.0 '@xtuc/ieee754@1.2.0': {} @@ -22201,7 +22744,7 @@ snapshots: '@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.18.20)': dependencies: esbuild: 0.18.20 - tslib: 2.6.3 + tslib: 2.7.0 '@yarnpkg/fslib@2.10.3': dependencies: @@ -22217,6 +22760,8 @@ snapshots: dependencies: event-target-shim: 5.0.1 + abortcontroller-polyfill@1.7.5: {} + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -22542,7 +23087,7 @@ snapshots: dependencies: pvtsutils: 1.3.5 pvutils: 1.1.3 - tslib: 2.6.3 + tslib: 2.7.0 assert-plus@1.0.0: {} @@ -22560,11 +23105,11 @@ snapshots: ast-types@0.15.2: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 ast-types@0.16.1: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 astral-regex@2.0.0: {} @@ -22624,12 +23169,12 @@ snapshots: dependencies: '@babel/core': 7.24.7 - babel-loader@9.1.3(@babel/core@7.24.7)(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)): + babel-loader@9.1.3(@babel/core@7.24.7)(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))): dependencies: '@babel/core': 7.24.7 find-cache-dir: 4.0.0 schema-utils: 4.2.0 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) babel-plugin-add-react-displayname@0.0.5: {} @@ -22760,6 +23305,10 @@ snapshots: balanced-match@1.0.2: {} + base-x@3.0.10: + dependencies: + safe-buffer: 5.2.1 + base-x@5.0.0: {} base64-js@1.5.1: {} @@ -22864,13 +23413,6 @@ snapshots: node-releases: 2.0.14 update-browserslist-db: 1.1.0(browserslist@4.23.1) - browserslist@4.23.3: - dependencies: - caniuse-lite: 1.0.30001660 - electron-to-chromium: 1.5.18 - node-releases: 2.0.18 - update-browserslist-db: 1.1.0(browserslist@4.23.3) - bs58@6.0.0: dependencies: base-x: 5.0.0 @@ -22952,7 +23494,7 @@ snapshots: camel-case@4.1.2: dependencies: pascal-case: 3.1.2 - tslib: 2.6.3 + tslib: 2.7.0 camelcase-css@2.0.1: {} @@ -22969,7 +23511,7 @@ snapshots: capital-case@1.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 upper-case-first: 2.0.2 cardinal@2.1.1: @@ -23041,7 +23583,7 @@ snapshots: path-case: 3.0.4 sentence-case: 3.0.4 snake-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 character-entities-html4@2.1.0: {} @@ -23187,7 +23729,7 @@ snapshots: strip-ansi: 6.0.1 supports-color: 8.1.1 supports-hyperlinks: 2.3.0 - tslib: 2.6.3 + tslib: 2.7.0 cli-width@3.0.0: {} @@ -23225,6 +23767,8 @@ snapshots: clone@1.0.4: {} + clone@2.1.2: {} + clsx@2.0.0: {} clsx@2.1.1: {} @@ -23368,7 +23912,7 @@ snapshots: constant-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 upper-case: 2.0.2 constants-browserify@1.0.0: {} @@ -23389,7 +23933,7 @@ snapshots: cookie@0.6.0: {} - copy-webpack-plugin@11.0.0(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)): + copy-webpack-plugin@11.0.0(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))): dependencies: fast-glob: 3.3.2 glob-parent: 6.0.2 @@ -23397,7 +23941,7 @@ snapshots: normalize-path: 3.0.0 schema-utils: 4.2.0 serialize-javascript: 6.0.2 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) core-js-compat@3.37.1: dependencies: @@ -23444,6 +23988,15 @@ snapshots: optionalDependencies: typescript: 5.5.3 + cosmiconfig@9.0.0(typescript@5.5.3): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.5.3 + create-require@1.1.1: {} cross-env@7.0.3: @@ -23458,7 +24011,7 @@ snapshots: cross-inspect@1.0.0: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 cross-spawn@5.1.0: dependencies: @@ -23490,7 +24043,7 @@ snapshots: postcss-selector-parser: 6.1.0 postcss-value-parser: 4.2.0 - css-loader@6.11.0(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)): + css-loader@6.11.0(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))): dependencies: icss-utils: 5.1.0(postcss@8.4.39) postcss: 8.4.39 @@ -23501,7 +24054,7 @@ snapshots: postcss-value-parser: 4.2.0 semver: 7.6.2 optionalDependencies: - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) css-prefers-color-scheme@9.0.1(postcss@8.4.39): dependencies: @@ -23523,15 +24076,20 @@ snapshots: domutils: 3.0.1 nth-check: 2.0.1 + css-tree@1.1.3: + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + css-tree@2.2.1: dependencies: mdn-data: 2.0.28 - source-map-js: 1.2.0 + source-map-js: 1.2.1 css-tree@2.3.1: dependencies: mdn-data: 2.0.30 - source-map-js: 1.2.0 + source-map-js: 1.2.1 css-what@6.1.0: {} @@ -23541,6 +24099,10 @@ snapshots: cssesc@3.0.0: {} + csso@4.2.0: + dependencies: + css-tree: 1.1.3 + csso@5.0.5: dependencies: css-tree: 2.2.1 @@ -23549,11 +24111,6 @@ snapshots: dependencies: rrweb-cssom: 0.6.0 - cssstyle@4.1.0: - dependencies: - rrweb-cssom: 0.7.1 - optional: true - csstype@3.1.3: {} cytoscape-cose-bilkent@4.1.0(cytoscape@3.29.2): @@ -23914,6 +24471,8 @@ snapshots: detect-libc@1.0.3: {} + detect-libc@2.0.3: {} + detect-node-es@1.1.0: {} detect-package-manager@2.0.1: @@ -23991,7 +24550,7 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.1.2: {} + dompurify@3.1.6: {} domutils@2.8.0: dependencies: @@ -24014,7 +24573,7 @@ snapshots: dot-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 dot-prop@5.3.0: dependencies: @@ -24030,13 +24589,17 @@ snapshots: dotenv-expand@10.0.0: {} - dotenv-webpack@8.1.0(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)): + dotenv-expand@5.1.0: {} + + dotenv-webpack@8.1.0(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))): dependencies: dotenv-defaults: 2.0.2 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) dotenv@16.4.1: {} + dotenv@7.0.0: {} + dotenv@8.6.0: {} dset@3.1.3: {} @@ -24072,8 +24635,6 @@ snapshots: electron-to-chromium@1.4.819: {} - electron-to-chromium@1.5.18: {} - elkjs@0.9.3: {} emoji-regex@8.0.0: {} @@ -24108,6 +24669,8 @@ snapshots: entities@2.2.0: {} + entities@3.0.1: {} + entities@4.5.0: {} env-paths@2.2.1: {} @@ -24346,34 +24909,6 @@ snapshots: '@esbuild/win32-ia32': 0.23.0 '@esbuild/win32-x64': 0.23.0 - esbuild@0.23.1: - optionalDependencies: - '@esbuild/aix-ppc64': 0.23.1 - '@esbuild/android-arm': 0.23.1 - '@esbuild/android-arm64': 0.23.1 - '@esbuild/android-x64': 0.23.1 - '@esbuild/darwin-arm64': 0.23.1 - '@esbuild/darwin-x64': 0.23.1 - '@esbuild/freebsd-arm64': 0.23.1 - '@esbuild/freebsd-x64': 0.23.1 - '@esbuild/linux-arm': 0.23.1 - '@esbuild/linux-arm64': 0.23.1 - '@esbuild/linux-ia32': 0.23.1 - '@esbuild/linux-loong64': 0.23.1 - '@esbuild/linux-mips64el': 0.23.1 - '@esbuild/linux-ppc64': 0.23.1 - '@esbuild/linux-riscv64': 0.23.1 - '@esbuild/linux-s390x': 0.23.1 - '@esbuild/linux-x64': 0.23.1 - '@esbuild/netbsd-x64': 0.23.1 - '@esbuild/openbsd-arm64': 0.23.1 - '@esbuild/openbsd-x64': 0.23.1 - '@esbuild/sunos-x64': 0.23.1 - '@esbuild/win32-arm64': 0.23.1 - '@esbuild/win32-ia32': 0.23.1 - '@esbuild/win32-x64': 0.23.1 - optional: true - escalade@3.1.1: {} escalade@3.1.2: {} @@ -24400,7 +24935,7 @@ snapshots: dependencies: eslint: 8.45.0 - eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.1.0(eslint@8.45.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.45.0))(eslint@8.45.0)(typescript@5.5.3): + eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.24.7))(@babel/plugin-transform-react-jsx@7.24.7(@babel/core@7.24.7))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.1.0(eslint@8.45.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.45.0))(eslint@8.45.0)(typescript@5.5.3): dependencies: '@babel/core': 7.24.7 '@babel/eslint-parser': 7.18.9(@babel/core@7.24.7)(eslint@8.45.0) @@ -24410,7 +24945,7 @@ snapshots: babel-preset-react-app: 10.0.1 confusing-browser-globals: 1.0.11 eslint: 8.45.0 - eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(eslint@8.45.0) + eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.24.7))(@babel/plugin-transform-react-jsx@7.24.7(@babel/core@7.24.7))(eslint@8.45.0) eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.45.0)(typescript@5.5.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.1.0(eslint@8.45.0)(typescript@5.5.3))(eslint-plugin-import@2.29.1)(eslint@8.45.0))(eslint@8.45.0) eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.45.0)(typescript@5.5.3))(eslint@8.45.0)(typescript@5.5.3))(eslint@8.45.0)(typescript@5.5.3) eslint-plugin-jsx-a11y: 6.6.1(eslint@8.45.0) @@ -24474,10 +25009,10 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-flowtype@8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.25.2))(@babel/plugin-transform-react-jsx@7.25.2(@babel/core@7.25.2))(eslint@8.45.0): + eslint-plugin-flowtype@8.0.3(@babel/plugin-syntax-flow@7.24.7(@babel/core@7.24.7))(@babel/plugin-transform-react-jsx@7.24.7(@babel/core@7.24.7))(eslint@8.45.0): dependencies: - '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.25.2) - '@babel/plugin-transform-react-jsx': 7.25.2(@babel/core@7.25.2) + '@babel/plugin-syntax-flow': 7.24.7(@babel/core@7.24.7) + '@babel/plugin-transform-react-jsx': 7.24.7(@babel/core@7.24.7) eslint: 8.45.0 lodash: 4.17.21 string-natural-compare: 3.0.1 @@ -24571,14 +25106,14 @@ snapshots: dependencies: eslint: 8.36.0 - eslint-plugin-prettier@5.1.3(@types/eslint@9.6.1)(eslint-config-prettier@8.8.0(eslint@8.45.0))(eslint@8.45.0)(prettier@3.3.2): + eslint-plugin-prettier@5.1.3(@types/eslint@8.56.10)(eslint-config-prettier@8.8.0(eslint@8.45.0))(eslint@8.45.0)(prettier@3.3.2): dependencies: eslint: 8.45.0 prettier: 3.3.2 prettier-linter-helpers: 1.0.0 synckit: 0.8.8 optionalDependencies: - '@types/eslint': 9.6.1 + '@types/eslint': 8.56.10 eslint-config-prettier: 8.8.0(eslint@8.45.0) eslint-plugin-react-hooks@4.6.2(eslint@8.45.0): @@ -24670,7 +25205,7 @@ snapshots: eslint-visitor-keys@4.0.0: {} - eslint-webpack-plugin@4.2.0(eslint@8.45.0)(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)): + eslint-webpack-plugin@4.2.0(eslint@8.45.0)(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))): dependencies: '@types/eslint': 8.56.10 eslint: 8.45.0 @@ -24678,7 +25213,7 @@ snapshots: micromatch: 4.0.7 normalize-path: 3.0.0 schema-utils: 4.2.0 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) eslint@8.36.0: dependencies: @@ -25188,7 +25723,7 @@ snapshots: forever-agent@0.6.1: {} - fork-ts-checker-webpack-plugin@8.0.0(typescript@5.5.3)(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)): + fork-ts-checker-webpack-plugin@8.0.0(typescript@5.5.3)(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))): dependencies: '@babel/code-frame': 7.24.7 chalk: 4.1.2 @@ -25203,7 +25738,7 @@ snapshots: semver: 7.6.2 tapable: 2.2.1 typescript: 5.5.3 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) form-data-encoder@2.1.4: {} @@ -25379,6 +25914,8 @@ snapshots: get-package-type@0.1.0: {} + get-port@4.2.0: {} + get-port@5.1.1: {} get-stream@3.0.0: {} @@ -25695,13 +26232,6 @@ snapshots: webidl-conversions: 7.0.0 whatwg-mimetype: 3.0.0 - happy-dom@15.7.3: - dependencies: - entities: 4.5.0 - webidl-conversions: 7.0.0 - whatwg-mimetype: 3.0.0 - optional: true - har-schema@2.0.0: {} har-validator@5.1.5: @@ -25864,7 +26394,7 @@ snapshots: header-case@2.0.4: dependencies: capital-case: 1.0.4 - tslib: 2.6.3 + tslib: 2.7.0 headers-polyfill@4.0.3: {} @@ -25880,6 +26410,8 @@ snapshots: html-entities@2.5.2: {} + html-escaper@3.0.3: {} + html-minifier-terser@6.1.0: dependencies: camel-case: 4.1.2 @@ -25894,7 +26426,7 @@ snapshots: html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.0(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)): + html-webpack-plugin@5.6.0(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -25902,7 +26434,20 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.1 optionalDependencies: - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) + + htmlnano@2.1.1(postcss@8.4.39)(relateurl@0.2.7)(svgo@2.8.0)(terser@5.31.1)(typescript@5.5.3): + dependencies: + cosmiconfig: 9.0.0(typescript@5.5.3) + posthtml: 0.16.6 + timsort: 0.3.0 + optionalDependencies: + postcss: 8.4.39 + relateurl: 0.2.7 + svgo: 2.8.0 + terser: 5.31.1 + transitivePeerDependencies: + - typescript htmlparser2@6.1.0: dependencies: @@ -25911,6 +26456,13 @@ snapshots: domutils: 2.8.0 entities: 2.2.0 + htmlparser2@7.2.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + domutils: 2.8.0 + entities: 3.0.1 + htmlparser2@8.0.2: dependencies: domelementtype: 2.3.0 @@ -26032,18 +26584,12 @@ snapshots: immediate@3.0.6: {} - immer@10.1.1: - optional: true - immer@9.0.21: {} immutable@3.7.6: {} immutable@4.3.6: {} - immutable@4.3.7: - optional: true - import-fresh@3.3.0: dependencies: parent-module: 1.0.1 @@ -26238,9 +26784,11 @@ snapshots: is-interactive@1.0.0: {} + is-json@2.0.1: {} + is-lower-case@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 is-mergeable-object@1.1.1: {} @@ -26352,7 +26900,7 @@ snapshots: is-upper-case@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 is-utf8@0.2.1: {} @@ -26546,35 +27094,6 @@ snapshots: - supports-color - utf-8-validate - jsdom@25.0.0: - dependencies: - cssstyle: 4.1.0 - data-urls: 5.0.0 - decimal.js: 10.4.3 - form-data: 4.0.0 - html-encoding-sniffer: 4.0.0 - http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.5 - is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.12 - parse5: 7.1.2 - rrweb-cssom: 0.7.1 - saxes: 6.0.0 - symbol-tree: 3.2.4 - tough-cookie: 4.1.4 - w3c-xmlserializer: 5.0.0 - webidl-conversions: 7.0.0 - whatwg-encoding: 3.1.1 - whatwg-mimetype: 4.0.0 - whatwg-url: 14.0.0 - ws: 8.18.0 - xml-name-validator: 5.0.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - optional: true - jsesc@0.5.0: {} jsesc@2.5.2: {} @@ -26730,6 +27249,51 @@ snapshots: transitivePeerDependencies: - supports-color + lightningcss-darwin-arm64@1.27.0: + optional: true + + lightningcss-darwin-x64@1.27.0: + optional: true + + lightningcss-freebsd-x64@1.27.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.27.0: + optional: true + + lightningcss-linux-arm64-gnu@1.27.0: + optional: true + + lightningcss-linux-arm64-musl@1.27.0: + optional: true + + lightningcss-linux-x64-gnu@1.27.0: + optional: true + + lightningcss-linux-x64-musl@1.27.0: + optional: true + + lightningcss-win32-arm64-msvc@1.27.0: + optional: true + + lightningcss-win32-x64-msvc@1.27.0: + optional: true + + lightningcss@1.27.0: + dependencies: + detect-libc: 1.0.3 + optionalDependencies: + lightningcss-darwin-arm64: 1.27.0 + lightningcss-darwin-x64: 1.27.0 + lightningcss-freebsd-x64: 1.27.0 + lightningcss-linux-arm-gnueabihf: 1.27.0 + lightningcss-linux-arm64-gnu: 1.27.0 + lightningcss-linux-arm64-musl: 1.27.0 + lightningcss-linux-x64-gnu: 1.27.0 + lightningcss-linux-x64-musl: 1.27.0 + lightningcss-win32-arm64-msvc: 1.27.0 + lightningcss-win32-x64-msvc: 1.27.0 + lilconfig@2.1.0: {} lilconfig@3.1.2: {} @@ -26755,6 +27319,21 @@ snapshots: optionalDependencies: enquirer: 2.4.1 + lmdb@2.8.5: + dependencies: + msgpackr: 1.11.0 + node-addon-api: 6.1.0 + node-gyp-build-optional-packages: 5.1.1 + ordered-binary: 1.5.2 + weak-lru-cache: 1.2.2 + optionalDependencies: + '@lmdb/lmdb-darwin-arm64': 2.8.5 + '@lmdb/lmdb-darwin-x64': 2.8.5 + '@lmdb/lmdb-linux-arm': 2.8.5 + '@lmdb/lmdb-linux-arm64': 2.8.5 + '@lmdb/lmdb-linux-x64': 2.8.5 + '@lmdb/lmdb-win32-x64': 2.8.5 + load-yaml-file@0.2.0: dependencies: graceful-fs: 4.2.11 @@ -26833,11 +27412,11 @@ snapshots: lower-case-first@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 lower-case@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 lowercase-keys@3.0.0: {} @@ -27093,6 +27672,8 @@ snapshots: dependencies: '@types/mdast': 3.0.15 + mdn-data@2.0.14: {} + mdn-data@2.0.28: {} mdn-data@2.0.30: {} @@ -27136,7 +27717,7 @@ snapshots: d3-sankey: 0.12.3 dagre-d3-es: 7.0.10 dayjs: 1.11.11 - dompurify: 3.1.2 + dompurify: 3.1.6 elkjs: 0.9.3 katex: 0.16.10 khroma: 2.1.0 @@ -27477,11 +28058,11 @@ snapshots: min-indent@1.0.1: {} - mini-css-extract-plugin@2.9.0(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)): + mini-css-extract-plugin@2.9.0(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))): dependencies: schema-utils: 4.2.0 tapable: 2.2.1 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) mini-svg-data-uri@1.4.4: {} @@ -27553,6 +28134,22 @@ snapshots: ms@2.1.3: {} + msgpackr-extract@3.0.3: + dependencies: + node-gyp-build-optional-packages: 5.2.2 + optionalDependencies: + '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3 + '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3 + optional: true + + msgpackr@1.11.0: + optionalDependencies: + msgpackr-extract: 3.0.3 + msw@2.3.1(typescript@5.5.3): dependencies: '@bundled-es-modules/cookie': 2.0.0 @@ -27638,46 +28235,46 @@ snapshots: transitivePeerDependencies: - supports-color - next-seo@6.5.0(next@14.2.4(@babel/core@7.25.2)(@playwright/test@1.47.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.78.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-seo@6.5.0(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - next: 14.2.4(@babel/core@7.25.2)(@playwright/test@1.47.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.78.0) + next: 14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next-themes@0.2.1(next@14.2.4(@babel/core@7.25.2)(@playwright/test@1.47.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.78.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + next-themes@0.2.1(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - next: 14.2.4(@babel/core@7.25.2)(@playwright/test@1.47.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.78.0) + next: 14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - next@14.2.4(@babel/core@7.25.2)(@playwright/test@1.47.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.78.0): + next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6): dependencies: - '@next/env': 14.2.4 + '@next/env': 14.2.13 '@swc/helpers': 0.5.5 busboy: 1.6.0 - caniuse-lite: 1.0.30001640 + caniuse-lite: 1.0.30001660 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - styled-jsx: 5.1.1(@babel/core@7.25.2)(react@18.3.1) + styled-jsx: 5.1.1(@babel/core@7.24.7)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.4 - '@next/swc-darwin-x64': 14.2.4 - '@next/swc-linux-arm64-gnu': 14.2.4 - '@next/swc-linux-arm64-musl': 14.2.4 - '@next/swc-linux-x64-gnu': 14.2.4 - '@next/swc-linux-x64-musl': 14.2.4 - '@next/swc-win32-arm64-msvc': 14.2.4 - '@next/swc-win32-ia32-msvc': 14.2.4 - '@next/swc-win32-x64-msvc': 14.2.4 - '@playwright/test': 1.47.0 - sass: 1.78.0 + '@next/swc-darwin-arm64': 14.2.13 + '@next/swc-darwin-x64': 14.2.13 + '@next/swc-linux-arm64-gnu': 14.2.13 + '@next/swc-linux-arm64-musl': 14.2.13 + '@next/swc-linux-x64-gnu': 14.2.13 + '@next/swc-linux-x64-musl': 14.2.13 + '@next/swc-win32-arm64-msvc': 14.2.13 + '@next/swc-win32-ia32-msvc': 14.2.13 + '@next/swc-win32-x64-msvc': 14.2.13 + '@playwright/test': 1.45.1 + sass: 1.77.6 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros - nextra-theme-docs@2.13.4(next@14.2.4(@babel/core@7.25.2)(@playwright/test@1.47.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.78.0))(nextra@2.13.4(next@14.2.4(@babel/core@7.25.2)(@playwright/test@1.47.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.78.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + nextra-theme-docs@2.13.4(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(nextra@2.13.4(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@headlessui/react': 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@popperjs/core': 2.11.8 @@ -27688,16 +28285,16 @@ snapshots: git-url-parse: 13.1.1 intersection-observer: 0.12.2 match-sorter: 6.3.4 - next: 14.2.4(@babel/core@7.25.2)(@playwright/test@1.47.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.78.0) - next-seo: 6.5.0(next@14.2.4(@babel/core@7.25.2)(@playwright/test@1.47.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.78.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - next-themes: 0.2.1(next@14.2.4(@babel/core@7.25.2)(@playwright/test@1.47.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.78.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - nextra: 2.13.4(next@14.2.4(@babel/core@7.25.2)(@playwright/test@1.47.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.78.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next: 14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6) + next-seo: 6.5.0(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + next-themes: 0.2.1(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + nextra: 2.13.4(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) scroll-into-view-if-needed: 3.1.0 zod: 3.23.8 - nextra@2.13.4(next@14.2.4(@babel/core@7.25.2)(@playwright/test@1.47.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.78.0))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + nextra@2.13.4(next@14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: '@headlessui/react': 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@mdx-js/mdx': 2.3.0 @@ -27711,7 +28308,7 @@ snapshots: gray-matter: 4.0.3 katex: 0.16.10 lodash.get: 4.4.2 - next: 14.2.4(@babel/core@7.25.2)(@playwright/test@1.47.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.78.0) + next: 14.2.13(@babel/core@7.24.7)(@playwright/test@1.45.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.6) next-mdx-remote: 4.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) p-limit: 3.1.0 react: 18.3.1 @@ -27734,10 +28331,12 @@ snapshots: no-case@3.0.4: dependencies: lower-case: 2.0.2 - tslib: 2.6.3 + tslib: 2.7.0 node-abort-controller@3.1.1: {} + node-addon-api@6.1.0: {} + node-addon-api@7.1.0: {} node-dir@0.1.17: @@ -27764,6 +28363,15 @@ snapshots: node-forge@1.3.1: {} + node-gyp-build-optional-packages@5.1.1: + dependencies: + detect-libc: 2.0.3 + + node-gyp-build-optional-packages@5.2.2: + dependencies: + detect-libc: 2.0.3 + optional: true + node-int64@0.4.0: {} node-notifier@10.0.0: @@ -27777,8 +28385,6 @@ snapshots: node-releases@2.0.14: {} - node-releases@2.0.18: {} - non-layered-tidy-tree-layout@2.0.2: {} normalize-package-data@2.5.0: @@ -27829,9 +28435,6 @@ snapshots: nwsapi@2.2.10: {} - nwsapi@2.2.12: - optional: true - oauth-sign@0.9.0: {} object-assign@4.1.1: {} @@ -27982,6 +28585,8 @@ snapshots: strip-ansi: 6.0.1 wcwidth: 1.0.1 + ordered-binary@1.5.2: {} + os-locale@5.0.0: dependencies: execa: 4.1.0 @@ -28064,7 +28669,34 @@ snapshots: param-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 + + parcel@2.12.0(@swc/helpers@0.5.5)(postcss@8.4.39)(relateurl@0.2.7)(terser@5.31.1)(typescript@5.5.3): + dependencies: + '@parcel/config-default': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5)(postcss@8.4.39)(relateurl@0.2.7)(terser@5.31.1)(typescript@5.5.3) + '@parcel/core': 2.12.0(@swc/helpers@0.5.5) + '@parcel/diagnostic': 2.12.0 + '@parcel/events': 2.12.0 + '@parcel/fs': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/logger': 2.12.0 + '@parcel/package-manager': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5))(@swc/helpers@0.5.5) + '@parcel/reporter-cli': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/reporter-dev-server': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/reporter-tracer': 2.12.0(@parcel/core@2.12.0(@swc/helpers@0.5.5)) + '@parcel/utils': 2.12.0 + chalk: 4.1.2 + commander: 7.2.0 + get-port: 4.2.0 + transitivePeerDependencies: + - '@swc/helpers' + - cssnano + - postcss + - purgecss + - relateurl + - srcset + - terser + - typescript + - uncss parent-module@1.0.1: dependencies: @@ -28136,7 +28768,7 @@ snapshots: pascal-case@3.1.2: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 password-prompt@1.1.3: dependencies: @@ -28148,7 +28780,7 @@ snapshots: path-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 path-equal@1.2.5: {} @@ -28270,22 +28902,12 @@ snapshots: playwright-core@1.45.1: {} - playwright-core@1.47.0: - optional: true - playwright@1.45.1: dependencies: playwright-core: 1.45.1 optionalDependencies: fsevents: 2.3.2 - playwright@1.47.0: - dependencies: - playwright-core: 1.47.0 - optionalDependencies: - fsevents: 2.3.2 - optional: true - polished@4.3.1: dependencies: '@babel/runtime': 7.24.7 @@ -28445,29 +29067,21 @@ snapshots: '@csstools/utilities': 1.0.0(postcss@8.4.39) postcss: 8.4.39 - postcss-load-config@4.0.2(postcss@8.4.39)(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@20.14.10)(typescript@5.5.3)): - dependencies: - lilconfig: 3.1.2 - yaml: 2.4.5 - optionalDependencies: - postcss: 8.4.39 - ts-node: 10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@20.14.10)(typescript@5.5.3) - - postcss-load-config@4.0.2(postcss@8.4.39)(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3)): + postcss-load-config@4.0.2(postcss@8.4.39)(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)): dependencies: lilconfig: 3.1.2 yaml: 2.4.5 optionalDependencies: postcss: 8.4.39 - ts-node: 10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3) + ts-node: 10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3) - postcss-loader@7.3.3(postcss@8.4.39)(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)): + postcss-loader@7.3.3(postcss@8.4.39)(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))): dependencies: cosmiconfig: 8.2.0 jiti: 1.19.1 postcss: 8.4.39 semver: 7.6.2 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) postcss-logical@7.0.1(postcss@8.4.39): dependencies: @@ -28619,7 +29233,7 @@ snapshots: dependencies: nanoid: 3.3.7 picocolors: 1.0.1 - source-map-js: 1.2.0 + source-map-js: 1.2.1 postcss@8.4.39: dependencies: @@ -28627,6 +29241,23 @@ snapshots: picocolors: 1.0.1 source-map-js: 1.2.0 + posthtml-parser@0.10.2: + dependencies: + htmlparser2: 7.2.0 + + posthtml-parser@0.11.0: + dependencies: + htmlparser2: 7.2.0 + + posthtml-render@3.0.0: + dependencies: + is-json: 2.0.1 + + posthtml@0.16.6: + dependencies: + posthtml-parser: 0.11.0 + posthtml-render: 3.0.0 + preferred-pm@3.1.4: dependencies: find-up: 5.0.0 @@ -28766,7 +29397,7 @@ snapshots: pvtsutils@1.3.5: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 pvutils@1.1.3: {} @@ -28867,6 +29498,8 @@ snapshots: '@babel/runtime': 7.24.7 react: 18.3.1 + react-error-overlay@6.0.9: {} + react-fast-compare@2.0.4: {} react-hook-form@7.52.1(react@18.3.1): @@ -28897,7 +29530,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-redux@8.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@5.0.1): + react-redux@8.1.1(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(redux@4.2.1): dependencies: '@babel/runtime': 7.22.6 '@types/hoist-non-react-statics': 3.3.1 @@ -28910,10 +29543,12 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 react-dom: 18.3.1(react@18.3.1) - redux: 5.0.1 + redux: 4.2.1 react-refresh@0.14.2: {} + react-refresh@0.9.0: {} + react-remove-scroll-bar@2.3.4(@types/react@18.3.3)(react@18.3.1): dependencies: react: 18.3.1 @@ -28926,7 +29561,7 @@ snapshots: dependencies: react: 18.3.1 react-style-singleton: 2.2.1(@types/react@18.3.3)(react@18.3.1) - tslib: 2.6.3 + tslib: 2.7.0 optionalDependencies: '@types/react': 18.3.3 @@ -28969,7 +29604,7 @@ snapshots: get-nonce: 1.0.1 invariant: 2.2.4 react: 18.3.1 - tslib: 2.6.3 + tslib: 2.7.0 optionalDependencies: '@types/react': 18.3.3 @@ -29047,7 +29682,7 @@ snapshots: ast-types: 0.15.2 esprima: 4.0.1 source-map: 0.6.1 - tslib: 2.6.3 + tslib: 2.7.0 recast@0.23.3: dependencies: @@ -29055,7 +29690,7 @@ snapshots: ast-types: 0.16.1 esprima: 4.0.1 source-map: 0.6.1 - tslib: 2.6.3 + tslib: 2.7.0 recast@0.23.9: dependencies: @@ -29063,7 +29698,7 @@ snapshots: esprima: 4.0.1 source-map: 0.6.1 tiny-invariant: 1.3.3 - tslib: 2.6.3 + tslib: 2.7.0 rechoir@0.6.2: dependencies: @@ -29090,9 +29725,6 @@ snapshots: dependencies: '@babel/runtime': 7.22.10 - redux@5.0.1: - optional: true - regenerate-unicode-properties@10.1.1: dependencies: regenerate: 1.4.2 @@ -29430,10 +30062,10 @@ snapshots: safer-buffer@2.1.2: {} - sass-loader@13.3.2(sass@1.77.6)(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)): + sass-loader@13.3.2(sass@1.77.6)(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))): dependencies: neo-async: 2.6.2 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) optionalDependencies: sass: 1.77.6 @@ -29443,13 +30075,6 @@ snapshots: immutable: 4.3.6 source-map-js: 1.2.0 - sass@1.78.0: - dependencies: - chokidar: 3.6.0 - immutable: 4.3.7 - source-map-js: 1.2.1 - optional: true - sax@1.2.4: {} saxes@6.0.0: @@ -29495,8 +30120,6 @@ snapshots: semver@7.6.2: {} - semver@7.6.3: {} - send@0.18.0: dependencies: debug: 2.6.9 @@ -29536,7 +30159,7 @@ snapshots: sentence-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 upper-case-first: 2.0.2 serialize-javascript@6.0.2: @@ -29707,7 +30330,7 @@ snapshots: snake-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.7.0 sonic-boom@3.3.0: dependencies: @@ -29719,8 +30342,7 @@ snapshots: source-map-js@1.2.0: {} - source-map-js@1.2.1: - optional: true + source-map-js@1.2.1: {} source-map-support@0.5.21: dependencies: @@ -29769,10 +30391,12 @@ snapshots: sponge-case@1.0.1: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 sprintf-js@1.0.3: {} + srcset@4.0.0: {} + sshpk@1.17.0: dependencies: asn1: 0.2.6 @@ -29785,6 +30409,8 @@ snapshots: safer-buffer: 2.1.2 tweetnacl: 0.14.5 + stable@0.1.8: {} + stackback@0.0.2: {} stackframe@1.3.4: {} @@ -29961,20 +30587,20 @@ snapshots: strip-json-comments@5.0.0: {} - style-loader@3.3.4(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)): + style-loader@3.3.4(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))): dependencies: - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) style-to-object@0.4.4: dependencies: inline-style-parser: 0.1.1 - styled-jsx@5.1.1(@babel/core@7.25.2)(react@18.3.1): + styled-jsx@5.1.1(@babel/core@7.24.7)(react@18.3.1): dependencies: client-only: 0.0.1 react: 18.3.1 optionalDependencies: - '@babel/core': 7.25.2 + '@babel/core': 7.24.7 stylis@4.3.2: {} @@ -30019,6 +30645,16 @@ snapshots: svg-parser@2.0.4: {} + svgo@2.8.0: + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 4.3.0 + css-tree: 1.1.3 + csso: 4.2.0 + picocolors: 1.0.1 + stable: 0.1.8 + svgo@3.0.2: dependencies: '@trysound/sax': 0.2.0 @@ -30030,63 +30666,32 @@ snapshots: swap-case@2.0.2: dependencies: - tslib: 2.6.3 - - swc-loader@0.2.6(@swc/core@1.6.13(@swc/helpers@0.5.13))(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)): - dependencies: - '@swc/core': 1.6.13(@swc/helpers@0.5.13) - '@swc/counter': 0.1.3 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) - - symbol-tree@3.2.4: {} - - synchronous-promise@2.0.17: {} - - synckit@0.8.8: - dependencies: - '@pkgr/core': 0.1.1 - tslib: 2.6.3 - - tabbable@6.1.1: {} - - tailwind-merge@2.4.0: {} - - tailwindcss-animate@1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@20.14.10)(typescript@5.5.3))): - dependencies: - tailwindcss: 3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@20.14.10)(typescript@5.5.3)) - - tailwindcss-animate@1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3))): - dependencies: - tailwindcss: 3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3)) - - tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@20.14.10)(typescript@5.5.3)): - dependencies: - '@alloc/quick-lru': 5.2.0 - arg: 5.0.2 - chokidar: 3.6.0 - didyoumean: 1.2.2 - dlv: 1.1.3 - fast-glob: 3.3.2 - glob-parent: 6.0.2 - is-glob: 4.0.3 - jiti: 1.21.6 - lilconfig: 2.1.0 - micromatch: 4.0.7 - normalize-path: 3.0.0 - object-hash: 3.0.0 - picocolors: 1.0.1 - postcss: 8.4.39 - postcss-import: 15.1.0(postcss@8.4.39) - postcss-js: 4.0.1(postcss@8.4.39) - postcss-load-config: 4.0.2(postcss@8.4.39)(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@20.14.10)(typescript@5.5.3)) - postcss-nested: 6.0.1(postcss@8.4.39) - postcss-selector-parser: 6.1.0 - resolve: 1.22.8 - sucrase: 3.35.0 - transitivePeerDependencies: - - ts-node + tslib: 2.7.0 - tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3)): + swc-loader@0.2.6(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))): + dependencies: + '@swc/core': 1.6.13(@swc/helpers@0.5.5) + '@swc/counter': 0.1.3 + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) + + symbol-tree@3.2.4: {} + + synchronous-promise@2.0.17: {} + + synckit@0.8.8: + dependencies: + '@pkgr/core': 0.1.1 + tslib: 2.7.0 + + tabbable@6.1.1: {} + + tailwind-merge@2.4.0: {} + + tailwindcss-animate@1.0.7(tailwindcss@3.4.4(ts-node@10.9.2(@types/node@20.14.10)(typescript@5.5.3))): + dependencies: + tailwindcss: 3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)) + + tailwindcss@3.4.4(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -30105,7 +30710,7 @@ snapshots: postcss: 8.4.39 postcss-import: 15.1.0(postcss@8.4.39) postcss-js: 4.0.1(postcss@8.4.39) - postcss-load-config: 4.0.2(postcss@8.4.39)(ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3)) + postcss-load-config: 4.0.2(postcss@8.4.39)(ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3)) postcss-nested: 6.0.1(postcss@8.4.39) postcss-selector-parser: 6.1.0 resolve: 1.22.8 @@ -30168,27 +30773,16 @@ snapshots: term-size@2.2.1: {} - terser-webpack-plugin@5.3.10(@swc/core@1.6.13(@swc/helpers@0.5.13))(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)): - dependencies: - '@jridgewell/trace-mapping': 0.3.25 - jest-worker: 27.5.1 - schema-utils: 3.3.0 - serialize-javascript: 6.0.2 - terser: 5.31.1 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) - optionalDependencies: - '@swc/core': 1.6.13(@swc/helpers@0.5.13) - - terser-webpack-plugin@5.3.10(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)): + terser-webpack-plugin@5.3.10(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.31.1 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) optionalDependencies: - '@swc/core': 1.7.24(@swc/helpers@0.5.13) + '@swc/core': 1.6.13(@swc/helpers@0.5.5) terser@5.31.1: dependencies: @@ -30197,14 +30791,6 @@ snapshots: commander: 2.20.3 source-map-support: 0.5.21 - terser@5.32.0: - dependencies: - '@jridgewell/source-map': 0.3.6 - acorn: 8.12.1 - commander: 2.20.3 - source-map-support: 0.5.21 - optional: true - test-exclude@6.0.0: dependencies: '@istanbuljs/schema': 0.1.3 @@ -30236,6 +30822,8 @@ snapshots: throwback@4.1.0: {} + timsort@0.3.0: {} + tiny-case@1.0.3: {} tiny-invariant@1.3.1: {} @@ -30252,7 +30840,7 @@ snapshots: title-case@3.0.3: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 title@3.5.3: dependencies: @@ -30327,7 +30915,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-loader@9.5.1(typescript@5.5.3)(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)): + ts-loader@9.5.1(typescript@5.5.3)(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))): dependencies: chalk: 4.1.2 enhanced-resolve: 5.17.0 @@ -30335,58 +30923,58 @@ snapshots: semver: 7.6.2 source-map: 0.7.4 typescript: 5.5.3 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) ts-log@2.2.5: {} - ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@16.18.101)(typescript@5.1.6): + ts-node@10.9.2(@swc/core@1.6.13(@swc/helpers@0.5.5))(@types/node@20.14.10)(typescript@5.5.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 16.18.101 + '@types/node': 20.14.10 acorn: 8.12.1 acorn-walk: 8.3.3 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.1.6 + typescript: 5.5.3 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.7.24(@swc/helpers@0.5.13) + '@swc/core': 1.6.13(@swc/helpers@0.5.5) - ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@20.14.10)(typescript@5.5.3): + ts-node@10.9.2(@swc/core@1.6.13)(@types/node@16.18.101)(typescript@5.1.6): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.14.10 + '@types/node': 16.18.101 acorn: 8.12.1 acorn-walk: 8.3.3 arg: 4.1.3 create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 5.5.3 + typescript: 5.1.6 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.7.24(@swc/helpers@0.5.13) + '@swc/core': 1.6.13(@swc/helpers@0.5.5) - ts-node@10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@22.5.4)(typescript@5.5.3): + ts-node@10.9.2(@swc/core@1.6.13)(@types/node@20.14.10)(typescript@5.5.3): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 22.5.4 + '@types/node': 20.14.10 acorn: 8.12.1 acorn-walk: 8.3.3 arg: 4.1.3 @@ -30397,7 +30985,7 @@ snapshots: v8-compile-cache-lib: 3.0.1 yn: 3.1.1 optionalDependencies: - '@swc/core': 1.7.24(@swc/helpers@0.5.13) + '@swc/core': 1.6.13(@swc/helpers@0.5.5) ts-retry-promise@0.8.1: {} @@ -30428,8 +31016,7 @@ snapshots: tslib@2.6.3: {} - tslib@2.7.0: - optional: true + tslib@2.7.0: {} tsutils@3.21.0(typescript@5.5.3): dependencies: @@ -30500,9 +31087,6 @@ snapshots: type-fest@4.21.0: {} - type-fest@4.26.1: - optional: true - type-is@1.6.18: dependencies: media-typer: 0.3.0 @@ -30552,23 +31136,28 @@ snapshots: typedarray@0.0.6: {} - typedoc@0.26.3(typescript@5.6.2): + typedoc-plugin-mermaid@1.12.0(typedoc@0.26.3(typescript@5.5.3)): + dependencies: + html-escaper: 3.0.3 + typedoc: 0.26.3(typescript@5.5.3) + + typedoc@0.26.3(typescript@5.5.3): dependencies: lunr: 2.3.9 markdown-it: 14.1.0 minimatch: 9.0.5 shiki: 1.10.3 - typescript: 5.6.2 + typescript: 5.5.3 yaml: 2.4.5 - typescript-json-schema@0.64.0(@swc/core@1.7.24(@swc/helpers@0.5.13)): + typescript-json-schema@0.64.0(@swc/core@1.6.13): dependencies: '@types/json-schema': 7.0.15 '@types/node': 16.18.101 glob: 7.2.3 path-equal: 1.2.5 safe-stable-stringify: 2.4.3 - ts-node: 10.9.2(@swc/core@1.7.24(@swc/helpers@0.5.13))(@types/node@16.18.101)(typescript@5.1.6) + ts-node: 10.9.2(@swc/core@1.6.13)(@types/node@16.18.101)(typescript@5.1.6) typescript: 5.1.6 yargs: 17.7.2 transitivePeerDependencies: @@ -30581,8 +31170,6 @@ snapshots: typescript@5.5.3: {} - typescript@5.6.2: {} - ua-parser-js@1.0.37: {} uc.micro@2.1.0: {} @@ -30606,8 +31193,6 @@ snapshots: undici-types@5.26.5: {} - undici-types@6.19.8: {} - unicode-canonical-property-names-ecmascript@2.0.0: {} unicode-match-property-ecmascript@2.0.0: @@ -30769,12 +31354,6 @@ snapshots: escalade: 3.1.2 picocolors: 1.0.1 - update-browserslist-db@1.1.0(browserslist@4.23.3): - dependencies: - browserslist: 4.23.3 - escalade: 3.1.2 - picocolors: 1.0.1 - update-notifier@6.0.2: dependencies: boxen: 7.1.1 @@ -30794,11 +31373,11 @@ snapshots: upper-case-first@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 upper-case@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 uri-js@4.4.1: dependencies: @@ -30821,14 +31400,14 @@ snapshots: use-callback-ref@1.3.0(@types/react@18.3.3)(react@18.3.1): dependencies: react: 18.3.1 - tslib: 2.6.3 + tslib: 2.7.0 optionalDependencies: '@types/react': 18.3.3 use-callback-ref@1.3.2(@types/react@18.3.3)(react@18.3.1): dependencies: react: 18.3.1 - tslib: 2.6.3 + tslib: 2.7.0 optionalDependencies: '@types/react': 18.3.3 @@ -30859,7 +31438,7 @@ snapshots: dependencies: detect-node-es: 1.1.0 react: 18.3.1 - tslib: 2.6.3 + tslib: 2.7.0 optionalDependencies: '@types/react': 18.3.3 @@ -30879,6 +31458,8 @@ snapshots: utila@0.4.0: {} + utility-types@3.11.0: {} + utils-merge@1.0.1: {} uuid@3.4.0: {} @@ -30953,64 +31534,13 @@ snapshots: unist-util-stringify-position: 4.0.0 vfile-message: 4.0.2 - vite-node@1.6.0(@types/node@20.14.10)(sass@1.78.0)(terser@5.32.0): - dependencies: - cac: 6.7.14 - debug: 4.3.7 - pathe: 1.1.2 - picocolors: 1.0.1 - vite: 5.3.3(@types/node@20.14.10)(sass@1.78.0)(terser@5.32.0) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - - vite-node@1.6.0(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0): + vite-node@1.6.0(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1): dependencies: cac: 6.7.14 debug: 4.3.7 pathe: 1.1.2 picocolors: 1.0.1 - vite: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - - vite-node@2.0.1(@types/node@20.14.10)(sass@1.77.6)(terser@5.32.0): - dependencies: - cac: 6.7.14 - debug: 4.3.5(supports-color@8.1.1) - pathe: 1.1.2 - picocolors: 1.0.1 - vite: 5.3.3(@types/node@20.14.10)(sass@1.77.6)(terser@5.32.0) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - - vite-node@2.0.1(@types/node@20.14.10)(sass@1.78.0)(terser@5.32.0): - dependencies: - cac: 6.7.14 - debug: 4.3.5(supports-color@8.1.1) - pathe: 1.1.2 - picocolors: 1.0.1 - vite: 5.3.3(@types/node@20.14.10)(sass@1.78.0)(terser@5.32.0) + vite: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) transitivePeerDependencies: - '@types/node' - less @@ -31021,13 +31551,13 @@ snapshots: - supports-color - terser - vite-node@2.0.1(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0): + vite-node@2.0.1(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1): dependencies: cac: 6.7.14 debug: 4.3.5(supports-color@8.1.1) pathe: 1.1.2 picocolors: 1.0.1 - vite: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) + vite: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) transitivePeerDependencies: - '@types/node' - less @@ -31038,29 +31568,18 @@ snapshots: - supports-color - terser - vite-tsconfig-paths@4.3.2(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.10)(sass@1.77.6)(terser@5.32.0)): - dependencies: - debug: 4.3.5(supports-color@8.1.1) - globrex: 0.1.2 - tsconfck: 3.1.1(typescript@5.5.3) - optionalDependencies: - vite: 5.3.3(@types/node@20.14.10)(sass@1.77.6)(terser@5.32.0) - transitivePeerDependencies: - - supports-color - - typescript - - vite-tsconfig-paths@4.3.2(typescript@5.5.3)(vite@5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0)): + vite-tsconfig-paths@4.3.2(typescript@5.5.3)(vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1)): dependencies: debug: 4.3.5(supports-color@8.1.1) globrex: 0.1.2 tsconfck: 3.1.1(typescript@5.5.3) optionalDependencies: - vite: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) + vite: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) transitivePeerDependencies: - supports-color - typescript - vite@5.3.3(@types/node@20.14.10)(sass@1.77.6)(terser@5.32.0): + vite@5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1): dependencies: esbuild: 0.21.5 postcss: 8.4.39 @@ -31068,32 +31587,11 @@ snapshots: optionalDependencies: '@types/node': 20.14.10 fsevents: 2.3.3 + lightningcss: 1.27.0 sass: 1.77.6 - terser: 5.32.0 - - vite@5.3.3(@types/node@20.14.10)(sass@1.78.0)(terser@5.32.0): - dependencies: - esbuild: 0.21.5 - postcss: 8.4.39 - rollup: 4.18.1 - optionalDependencies: - '@types/node': 20.14.10 - fsevents: 2.3.3 - sass: 1.78.0 - terser: 5.32.0 - - vite@5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0): - dependencies: - esbuild: 0.21.5 - postcss: 8.4.39 - rollup: 4.18.1 - optionalDependencies: - '@types/node': 22.5.4 - fsevents: 2.3.3 - sass: 1.78.0 - terser: 5.32.0 + terser: 5.31.1 - vitest@2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@25.0.0)(sass@1.77.6)(terser@5.32.0): + vitest@2.0.1(@types/node@20.14.10)(happy-dom@14.12.3)(jsdom@24.1.0)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1): dependencies: '@ampproject/remapping': 2.3.0 '@vitest/expect': 2.0.1 @@ -31110,78 +31608,12 @@ snapshots: std-env: 3.7.0 tinybench: 2.8.0 tinypool: 1.0.0 - vite: 5.3.3(@types/node@20.14.10)(sass@1.77.6)(terser@5.32.0) - vite-node: 2.0.1(@types/node@20.14.10)(sass@1.77.6)(terser@5.32.0) + vite: 5.3.3(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) + vite-node: 2.0.1(@types/node@20.14.10)(lightningcss@1.27.0)(sass@1.77.6)(terser@5.31.1) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 20.14.10 happy-dom: 14.12.3 - jsdom: 25.0.0 - transitivePeerDependencies: - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - - vitest@2.0.1(@types/node@20.14.10)(happy-dom@15.7.3)(jsdom@25.0.0)(sass@1.78.0)(terser@5.32.0): - dependencies: - '@ampproject/remapping': 2.3.0 - '@vitest/expect': 2.0.1 - '@vitest/runner': 2.0.1 - '@vitest/snapshot': 2.0.1 - '@vitest/spy': 2.0.1 - '@vitest/utils': 2.0.1 - chai: 5.1.1 - debug: 4.3.5(supports-color@8.1.1) - execa: 8.0.1 - magic-string: 0.30.10 - pathe: 1.1.2 - picocolors: 1.0.1 - std-env: 3.7.0 - tinybench: 2.8.0 - tinypool: 1.0.0 - vite: 5.3.3(@types/node@20.14.10)(sass@1.78.0)(terser@5.32.0) - vite-node: 2.0.1(@types/node@20.14.10)(sass@1.78.0)(terser@5.32.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 20.14.10 - happy-dom: 15.7.3 - jsdom: 25.0.0 - transitivePeerDependencies: - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - - vitest@2.0.1(@types/node@22.5.4)(happy-dom@15.7.3)(jsdom@24.1.0)(sass@1.78.0)(terser@5.32.0): - dependencies: - '@ampproject/remapping': 2.3.0 - '@vitest/expect': 2.0.1 - '@vitest/runner': 2.0.1 - '@vitest/snapshot': 2.0.1 - '@vitest/spy': 2.0.1 - '@vitest/utils': 2.0.1 - chai: 5.1.1 - debug: 4.3.5(supports-color@8.1.1) - execa: 8.0.1 - magic-string: 0.30.10 - pathe: 1.1.2 - picocolors: 1.0.1 - std-env: 3.7.0 - tinybench: 2.8.0 - tinypool: 1.0.0 - vite: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) - vite-node: 2.0.1(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 22.5.4 - happy-dom: 15.7.3 jsdom: 24.1.0 transitivePeerDependencies: - less @@ -31192,39 +31624,6 @@ snapshots: - supports-color - terser - vitest@2.0.1(@types/node@22.5.4)(happy-dom@15.7.3)(jsdom@25.0.0)(sass@1.78.0)(terser@5.32.0): - dependencies: - '@ampproject/remapping': 2.3.0 - '@vitest/expect': 2.0.1 - '@vitest/runner': 2.0.1 - '@vitest/snapshot': 2.0.1 - '@vitest/spy': 2.0.1 - '@vitest/utils': 2.0.1 - chai: 5.1.1 - debug: 4.3.5(supports-color@8.1.1) - execa: 8.0.1 - magic-string: 0.30.10 - pathe: 1.1.2 - picocolors: 1.0.1 - std-env: 3.7.0 - tinybench: 2.8.0 - tinypool: 1.0.0 - vite: 5.3.3(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) - vite-node: 2.0.1(@types/node@22.5.4)(sass@1.78.0)(terser@5.32.0) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 22.5.4 - happy-dom: 15.7.3 - jsdom: 25.0.0 - transitivePeerDependencies: - - less - - lightningcss - - sass - - stylus - - sugarss - - supports-color - - terser - vscode-oniguruma@1.7.0: {} vscode-textmate@8.0.0: {} @@ -31276,6 +31675,8 @@ snapshots: dependencies: defaults: 1.0.4 + weak-lru-cache@1.2.2: {} + web-ext@7.6.2: dependencies: '@babel/runtime': 7.21.0 @@ -31332,7 +31733,7 @@ snapshots: '@peculiar/json-schema': 1.1.12 asn1js: 3.0.5 pvtsutils: 1.3.5 - tslib: 2.6.3 + tslib: 2.7.0 webextension-polyfill@0.10.0: {} @@ -31343,9 +31744,9 @@ snapshots: webpack-cli@5.1.4(webpack@5.92.1): dependencies: '@discoveryjs/json-ext': 0.5.7 - '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack@5.92.1))(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) - '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack@5.92.1))(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) - '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack@5.92.1))(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack@5.92.1))(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack@5.92.1))(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack@5.92.1))(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) colorette: 2.0.20 commander: 10.0.1 cross-spawn: 7.0.3 @@ -31354,10 +31755,10 @@ snapshots: import-local: 3.1.0 interpret: 3.1.1 rechoir: 0.8.0 - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) webpack-merge: 5.8.0 - webpack-dev-middleware@6.1.3(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)): + webpack-dev-middleware@6.1.3(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))): dependencies: colorette: 2.0.20 memfs: 3.5.3 @@ -31365,7 +31766,7 @@ snapshots: range-parser: 1.2.1 schema-utils: 4.2.0 optionalDependencies: - webpack: 5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4) + webpack: 5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)) webpack-hot-middleware@2.26.1: dependencies: @@ -31384,7 +31785,7 @@ snapshots: webpack-virtual-modules@0.6.2: {} - webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.13))(webpack-cli@5.1.4(webpack@5.92.1)): + webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1)): dependencies: '@types/eslint-scope': 3.7.7 '@types/estree': 1.0.5 @@ -31407,40 +31808,7 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.6.13(@swc/helpers@0.5.13))(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) - watchpack: 2.4.1 - webpack-sources: 3.2.3 - optionalDependencies: - webpack-cli: 5.1.4(webpack@5.92.1) - transitivePeerDependencies: - - '@swc/core' - - esbuild - - uglify-js - - webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4): - dependencies: - '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.5 - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/wasm-edit': 1.12.1 - '@webassemblyjs/wasm-parser': 1.12.1 - acorn: 8.12.1 - acorn-import-attributes: 1.9.5(acorn@8.12.1) - browserslist: 4.23.1 - chrome-trace-event: 1.0.4 - enhanced-resolve: 5.17.0 - es-module-lexer: 1.5.4 - eslint-scope: 5.1.1 - events: 3.3.0 - glob-to-regexp: 0.4.1 - graceful-fs: 4.2.11 - json-parse-even-better-errors: 2.3.1 - loader-runner: 4.3.0 - mime-types: 2.1.35 - neo-async: 2.6.2 - schema-utils: 3.3.0 - tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack@5.92.1(@swc/core@1.7.24(@swc/helpers@0.5.13))(webpack-cli@5.1.4)) + terser-webpack-plugin: 5.3.10(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack@5.92.1(@swc/core@1.6.13(@swc/helpers@0.5.5))(webpack-cli@5.1.4(webpack@5.92.1))) watchpack: 2.4.1 webpack-sources: 3.2.3 optionalDependencies: @@ -31685,12 +32053,12 @@ snapshots: zod@3.23.8: {} - zustand@4.5.4(@types/react@18.3.3)(immer@10.1.1)(react@18.3.1): + zustand@4.5.4(@types/react@18.3.3)(immer@9.0.21)(react@18.3.1): dependencies: use-sync-external-store: 1.2.0(react@18.3.1) optionalDependencies: '@types/react': 18.3.3 - immer: 10.1.1 + immer: 9.0.21 react: 18.3.1 zwitch@2.0.4: {} diff --git a/rust-toolchain.toml b/rust-toolchain.toml index a56a283d2abc1..4cef0b738ff63 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "1.80.1" +channel = "1.81" diff --git a/scripts/compatibility/check-protocol-compatibility.sh b/scripts/compatibility/check-protocol-compatibility.sh index 2fd661e81cd98..47a070ba51006 100755 --- a/scripts/compatibility/check-protocol-compatibility.sh +++ b/scripts/compatibility/check-protocol-compatibility.sh @@ -2,45 +2,47 @@ # Copyright (c) Mysten Labs, Inc. # SPDX-License-Identifier: Apache-2.0 -# check if API_USER and API_KEY env vars are set -if [ -z "$API_USER" ] || [ -z "$API_KEY" ]; then - echo "Error: API_USER and API_KEY environment variables must be set" - exit 1 -fi - NETWORK="$1" -REPO_ROOT=$(git rev-parse --show-toplevel) -cd $REPO_ROOT +if [ -z "$RELEASED_COMMIT" ]; then + # check if API_USER and API_KEY env vars are set + if [ -z "$API_USER" ] || [ -z "$API_KEY" ]; then + echo "Error: API_USER and API_KEY environment variables must be set" + exit 1 + fi -if [ "$NETWORK" != "devnet" ] && [ "$NETWORK" != "testnet" ] && [ "$NETWORK" != "mainnet" ]; then - echo "Invalid network: $NETWORK" - echo "Usage: check-protocol-compatibility.sh " - exit 1 -fi + REPO_ROOT=$(git rev-parse --show-toplevel) + cd $REPO_ROOT -case "$NETWORK" in - devnet) - URL="https://$API_USER:$API_KEY@gateway.mimir.sui.io/prometheus/api/v1/query" - ;; - testnet) - URL="http://$API_USER:$API_KEY@metrics-gw-2.testnet.sui.io/prometheus/api/v1/query" - ;; - mainnet) - URL="https://$API_USER:$API_KEY@metrics-gw-2.mainnet.sui.io/prometheus/api/v1/query" - ;; -esac - -VERSIONS=$(curl -s -G -k "$URL" --data-urlencode "query=uptime{network=\"$NETWORK\"}" | jq -r '.data.result[].metric.version' | sort | uniq -c | sort -rn) -TOP_VERSION=$(echo "$VERSIONS" | head -n 1 | awk '{print $2}') - -echo "Found following versions on $NETWORK:" -echo "$VERSIONS" -echo "" -echo "Using most frequent version $TOP_VERSION for compatibility check" - -# TOP_VERSION looks like "1.0.0-ae1212baf8", split out the commit hash -ORIGIN_COMMIT=$(echo "$TOP_VERSION" | cut -d- -f2) + if [ "$NETWORK" != "devnet" ] && [ "$NETWORK" != "testnet" ] && [ "$NETWORK" != "mainnet" ]; then + echo "Invalid network: $NETWORK" + echo "Usage: check-protocol-compatibility.sh " + exit 1 + fi + + case "$NETWORK" in + devnet) + URL="https://$API_USER:$API_KEY@gateway.mimir.sui.io/prometheus/api/v1/query" + ;; + testnet) + URL="http://$API_USER:$API_KEY@metrics-gw-2.testnet.sui.io/prometheus/api/v1/query" + ;; + mainnet) + URL="https://$API_USER:$API_KEY@metrics-gw-2.mainnet.sui.io/prometheus/api/v1/query" + ;; + esac + + VERSIONS=$(curl -s -G -k "$URL" --data-urlencode "query=uptime{network=\"$NETWORK\"}" | jq -r '.data.result[].metric.version' | sort | uniq -c | sort -rn) + TOP_VERSION=$(echo "$VERSIONS" | head -n 1 | awk '{print $2}') + + echo "Found following versions on $NETWORK:" + echo "$VERSIONS" + echo "" + echo "Using most frequent version $TOP_VERSION for compatibility check" + + # TOP_VERSION looks like "1.0.0-ae1212baf8", split out the commit hash + RELEASED_COMMIT=$(echo "$TOP_VERSION" | cut -d- -f2) +fi git fetch -q || exit 1 SOURCE_COMMIT=$(git rev-parse HEAD) @@ -49,7 +51,7 @@ SOURCE_BRANCH=$(git branch -a --contains "$SOURCE_COMMIT" | head -n 1 | cut -d' echo "Source commit: $SOURCE_COMMIT" echo "Source branch: $SOURCE_BRANCH" -echo "Checking protocol compatibility with $NETWORK ($ORIGIN_COMMIT)" +echo "Checking protocol compatibility with $NETWORK ($RELEASED_COMMIT)" # put code to check if git client is clean into function function check_git_clean { @@ -58,6 +60,7 @@ function check_git_clean { # if any files are edited or staged, exit with error if ! git diff --quiet --exit-code -- $path || ! git diff --cached --quiet --exit-code -- $path; then echo "Error: $message" + git diff HEAD exit 1 fi } @@ -66,7 +69,7 @@ check_git_clean "Please commit or stash your changes before running this script" # check out all files in crates/sui-protocol-config/src/snapshots at origin commit echo "Checking out $NETWORK snapshot files" -git checkout $ORIGIN_COMMIT -- crates/sui-protocol-config/src/snapshots || exit 1 +git checkout $RELEASED_COMMIT -- crates/sui-protocol-config/src/snapshots || exit 1 if [ "$NETWORK" != "testnet" ] && [ "$NETWORK" != "mainnet" ]; then NETWORK_PATTERN="*__version_*" @@ -75,20 +78,7 @@ else fi echo "Checking for changes to snapshot files matching $NETWORK_PATTERN" - -# The fields `scoring_decision_mad_divisor`, `scoring_decision_cutoff_value` were removed from the protocol config, -# but they are still present in older snapshot files. We need to delete them from the snapshot files before -# checking if the git repo is clean. -# TODO: Remove this workaround once commit 3959d9af51172824b0e4f20802c71e416596c7df has been release to all networks. -SED=$(which gsed) -if [ -z "$SED" ]; then - SED=$(which sed) -fi - -grep -lE 'scoring_decision_mad_divisor|scoring_decision_cutoff_value' crates/sui-protocol-config/src/snapshots/$NETWORK_PATTERN | xargs $SED -Ei '/(scoring_decision_mad_divisor|scoring_decision_cutoff_value)/d' -git add . - -check_git_clean "Detected changes to snapshot files since $ORIGIN_COMMIT - not safe to release" "$NETWORK_PATTERN" +check_git_clean "Detected changes to snapshot files since $RELEASED_COMMIT - not safe to release" "$NETWORK_PATTERN" # remove any snapshot file changes that were ignored git reset --hard HEAD diff --git a/scripts/simtest/cargo-simtest b/scripts/simtest/cargo-simtest index bd07db8332388..8ae86f0553cab 100755 --- a/scripts/simtest/cargo-simtest +++ b/scripts/simtest/cargo-simtest @@ -54,9 +54,9 @@ if [ -n "$LOCAL_MSIM_PATH" ]; then else cargo_patch_args=( --config 'patch.crates-io.tokio.git = "https://github.com/MystenLabs/mysten-sim.git"' - --config 'patch.crates-io.tokio.rev = "b320996d8dfb99b273fe31c0222c659332283c99"' + --config 'patch.crates-io.tokio.rev = "9c6636c399d5c60a1759f1670b1c07b3d408799a"' --config 'patch.crates-io.futures-timer.git = "https://github.com/MystenLabs/mysten-sim.git"' - --config 'patch.crates-io.futures-timer.rev = "b320996d8dfb99b273fe31c0222c659332283c99"' + --config 'patch.crates-io.futures-timer.rev = "9c6636c399d5c60a1759f1670b1c07b3d408799a"' ) fi diff --git a/scripts/simtest/config-patch b/scripts/simtest/config-patch index ed77e318c0453..d4909af61d9ea 100644 --- a/scripts/simtest/config-patch +++ b/scripts/simtest/config-patch @@ -18,5 +18,5 @@ index c0829bc1b6..4007f97d66 100644 include_dir = "0.7.3" [patch.crates-io] -+tokio = { git = "https://github.com/MystenLabs/mysten-sim.git", rev = "b320996d8dfb99b273fe31c0222c659332283c99" } -+futures-timer = { git = "https://github.com/MystenLabs/mysten-sim.git", rev = "b320996d8dfb99b273fe31c0222c659332283c99" } ++tokio = { git = "https://github.com/MystenLabs/mysten-sim.git", rev = "9c6636c399d5c60a1759f1670b1c07b3d408799a" } ++futures-timer = { git = "https://github.com/MystenLabs/mysten-sim.git", rev = "9c6636c399d5c60a1759f1670b1c07b3d408799a" } diff --git a/sdk/bcs/CHANGELOG.md b/sdk/bcs/CHANGELOG.md index 873b938740c6c..1d9d50f57c8a7 100644 --- a/sdk/bcs/CHANGELOG.md +++ b/sdk/bcs/CHANGELOG.md @@ -1,5 +1,21 @@ # Change Log +## 1.1.0 + +### Minor Changes + +- 489f421: Updated hex, base64, and base58 utility names for better consistency + + All existing methods will continue to work, but the following methods have been deprecated and + replaced with methods with improved names: + + - `toHEX` -> `toHEX` + - `fromHEX` -> `fromHex` + - `toB64` -> `toBase64` + - `fromB64` -> `fromBase64` + - `toB58` -> `toBase58` + - `fromB58` -> `fromBase58` + ## 1.0.4 ### Patch Changes diff --git a/sdk/bcs/README.md b/sdk/bcs/README.md index f203928d24aee..fe0715c83a3f3 100644 --- a/sdk/bcs/README.md +++ b/sdk/bcs/README.md @@ -16,12 +16,12 @@ npm i @mysten/bcs ## Quickstart ```ts -import { bcs } from '@mysten/bcs'; +import { bcs, fromHex, toHex } from '@mysten/bcs'; // define UID as a 32-byte array, then add a transform to/from hex strings const UID = bcs.fixedArray(32, bcs.u8()).transform({ - input: (id: string) => fromHEX(id), - output: (id) => toHEX(Uint8Array.from(id)), + input: (id: string) => fromHex(id), + output: (id) => toHex(Uint8Array.from(id)), }); const Coin = bcs.struct('Coin', { @@ -114,9 +114,9 @@ import { bcs } from '@mysten/bcs'; const intList = bcs.vector(bcs.u8()).serialize([1, 2, 3, 4, 5]).toBytes(); const stringList = bcs.vector(bcs.string()).serialize(['a', 'b', 'c']).toBytes(); -// Arrays -const intArray = bcs.array(4, bcs.u8()).serialize([1, 2, 3, 4]).toBytes(); -const stringArray = bcs.array(3, bcs.string()).serialize(['a', 'b', 'c']).toBytes(); +// Fixed length Arrays +const intArray = bcs.fixedArray(4, bcs.u8()).serialize([1, 2, 3, 4]).toBytes(); +const stringArray = bcs.fixedArray(3, bcs.string()).serialize(['a', 'b', 'c']).toBytes(); // Option const option = bcs.option(bcs.string()).serialize('some value').toBytes(); @@ -127,7 +127,7 @@ const MyEnum = bcs.enum('MyEnum', { NoType: null, Int: bcs.u8(), String: bcs.string(), - Array: bcs.array(3, bcs.u8()), + Array: bcs.fixedArray(3, bcs.u8()), }); const noTypeEnum = MyEnum.serialize({ NoType: null }).toBytes(); @@ -163,8 +163,8 @@ const map = bcs const parsedIntList = bcs.vector(bcs.u8()).parse(intList); const parsedStringList = bcs.vector(bcs.string()).parse(stringList); -// Arrays -const parsedIntArray = bcs.array(4, bcs.u8()).parse(intArray); +// Fixed length Arrays +const parsedIntArray = bcs.fixedArray(4, bcs.u8()).parse(intArray); // Option const parsedOption = bcs.option(bcs.string()).parse(option); @@ -242,10 +242,12 @@ represent an address as a hex string, but the BCS serialization format for addre array. To handle this, you can use the `transform` API to map between the two formats: ```ts +import { bcs, toHex } from '@mysten/bcs'; + const Address = bcs.bytes(32).transform({ // To change the input type, you need to provide a type definition for the input - input: (val: string) => fromHEX(val), - output: (val) => toHEX(val), + input: (val: string) => fromHex(val), + output: (val) => toHex(val), }); const serialized = Address.serialize('0x000000...').toBytes(); @@ -306,7 +308,7 @@ preserves type information for the serialized bytes, and can be used to get raw formats. ```ts -import { bcs, fromB58, fromB64, fromHex } from '@mysten/bcs'; +import { bcs, fromBase58, fromBase64, fromHex } from '@mysten/bcs'; const serializedString = bcs.string().serialize('this is a string'); @@ -323,8 +325,8 @@ const str1 = bcs.string().parse(bytes); // If your data is encoded as string, you need to convert it to Uint8Array first const str2 = bcs.string().parse(fromHex(hex)); -const str3 = bcs.string().parse(fromB64(base64)); -const str4 = bcs.string().parse(fromB58(base58)); +const str3 = bcs.string().parse(fromBase64(base64)); +const str4 = bcs.string().parse(fromBase58(base58)); console.assert((str1 == str2) == (str3 == str4), 'Result is the same'); ``` diff --git a/sdk/bcs/package.json b/sdk/bcs/package.json index 93a502e14a589..7055428930514 100644 --- a/sdk/bcs/package.json +++ b/sdk/bcs/package.json @@ -1,6 +1,6 @@ { "name": "@mysten/bcs", - "version": "1.0.4", + "version": "1.1.0", "description": "BCS - Canonical Binary Serialization implementation for JavaScript", "license": "Apache-2.0", "author": "Mysten Labs ", diff --git a/sdk/bcs/src/b58.ts b/sdk/bcs/src/b58.ts index 266c307641c9c..5c904c08975a6 100644 --- a/sdk/bcs/src/b58.ts +++ b/sdk/bcs/src/b58.ts @@ -3,5 +3,11 @@ import bs58 from 'bs58'; -export const toB58 = (buffer: Uint8Array) => bs58.encode(buffer); -export const fromB58 = (str: string) => bs58.decode(str); +export const toBase58 = (buffer: Uint8Array) => bs58.encode(buffer); +export const fromBase58 = (str: string) => bs58.decode(str); + +/** @deprecated use toBase58 instead */ +export const toB58 = toBase58; + +/** @deprecated use fromBase58 instead */ +export const fromB58 = fromBase58; diff --git a/sdk/bcs/src/b64.ts b/sdk/bcs/src/b64.ts index bae9797418529..837bff75175c9 100644 --- a/sdk/bcs/src/b64.ts +++ b/sdk/bcs/src/b64.ts @@ -1,12 +1,12 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -export function fromB64(base64String: string): Uint8Array { +export function fromBase64(base64String: string): Uint8Array { return Uint8Array.from(atob(base64String), (char) => char.charCodeAt(0)); } const CHUNK_SIZE = 8192; -export function toB64(bytes: Uint8Array): string { +export function toBase64(bytes: Uint8Array): string { // Special-case the simple case for speed's sake. if (bytes.length < CHUNK_SIZE) { return btoa(String.fromCharCode(...bytes)); @@ -20,3 +20,9 @@ export function toB64(bytes: Uint8Array): string { return btoa(output); } + +/** @deprecated use toBase64 instead */ +export const toB64 = toBase64; + +/** @deprecated use fromBase64 instead */ +export const fromB64 = fromBase64; diff --git a/sdk/bcs/src/bcs-type.ts b/sdk/bcs/src/bcs-type.ts index 788a8b0dd2bf7..5ab84d30ebc43 100644 --- a/sdk/bcs/src/bcs-type.ts +++ b/sdk/bcs/src/bcs-type.ts @@ -1,9 +1,9 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB58, toB58 } from './b58.js'; -import { fromB64, toB64 } from './b64.js'; -import { fromHEX, toHEX } from './hex.js'; +import { fromBase58, toBase58 } from './b58.js'; +import { fromBase64, toBase64 } from './b64.js'; +import { fromHex, toHex } from './hex.js'; import { BcsReader } from './reader.js'; import { ulebEncode } from './uleb.js'; import type { BcsWriterOptions } from './writer.js'; @@ -68,15 +68,15 @@ export class BcsType { } fromHex(hex: string) { - return this.parse(fromHEX(hex)); + return this.parse(fromHex(hex)); } fromBase58(b64: string) { - return this.parse(fromB58(b64)); + return this.parse(fromBase58(b64)); } fromBase64(b64: string) { - return this.parse(fromB64(b64)); + return this.parse(fromBase64(b64)); } transform({ @@ -127,15 +127,15 @@ export class SerializedBcs { } toHex() { - return toHEX(this.#bytes); + return toHex(this.#bytes); } toBase64() { - return toB64(this.#bytes); + return toBase64(this.#bytes); } toBase58() { - return toB58(this.#bytes); + return toBase58(this.#bytes); } parse() { diff --git a/sdk/bcs/src/hex.ts b/sdk/bcs/src/hex.ts index 52ee08775e758..53d1620c17924 100644 --- a/sdk/bcs/src/hex.ts +++ b/sdk/bcs/src/hex.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -export function fromHEX(hexStr: string): Uint8Array { +export function fromHex(hexStr: string): Uint8Array { const normalized = hexStr.startsWith('0x') ? hexStr.slice(2) : hexStr; const padded = normalized.length % 2 === 0 ? normalized : `0${normalized}}`; const intArr = padded.match(/.{2}/g)?.map((byte) => parseInt(byte, 16)) ?? []; @@ -9,6 +9,12 @@ export function fromHEX(hexStr: string): Uint8Array { return Uint8Array.from(intArr); } -export function toHEX(bytes: Uint8Array): string { +export function toHex(bytes: Uint8Array): string { return bytes.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), ''); } + +/** @deprecated use toHex instead */ +export const toHEX = toHex; + +/** @deprecated use fromHex instead */ +export const fromHEX = fromHex; diff --git a/sdk/bcs/src/index.ts b/sdk/bcs/src/index.ts index 3803c3c0d3a4c..9a24402c32d42 100644 --- a/sdk/bcs/src/index.ts +++ b/sdk/bcs/src/index.ts @@ -11,12 +11,12 @@ * @property {BcsReader} */ -import { fromB58, toB58 } from './b58.js'; -import { fromB64, toB64 } from './b64.js'; +import { fromB58, fromBase58, toB58, toBase58 } from './b58.js'; +import { fromB64, fromBase64, toB64, toBase64 } from './b64.js'; import type { BcsTypeOptions } from './bcs-type.js'; import { BcsType, isSerializedBcs, SerializedBcs } from './bcs-type.js'; import { bcs } from './bcs.js'; -import { fromHEX, toHEX } from './hex.js'; +import { fromHEX, fromHex, toHEX, toHex } from './hex.js'; import { BcsReader } from './reader.js'; import type { EnumInputShape, @@ -38,10 +38,16 @@ export { isSerializedBcs, toB58, fromB58, + toBase58, + fromBase58, toB64, fromB64, + toBase64, + fromBase64, fromHEX, toHEX, + toHex, + fromHex, encodeStr, decodeStr, splitGenericParameters, diff --git a/sdk/bcs/src/utils.ts b/sdk/bcs/src/utils.ts index b781c9ebc7c3b..3eb1e8b47de03 100644 --- a/sdk/bcs/src/utils.ts +++ b/sdk/bcs/src/utils.ts @@ -1,9 +1,9 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB58, toB58 } from './b58.js'; -import { fromB64, toB64 } from './b64.js'; -import { fromHEX, toHEX } from './hex.js'; +import { fromBase58, toBase58 } from './b58.js'; +import { fromBase64, toBase64 } from './b64.js'; +import { fromHex, toHex } from './hex.js'; import type { Encoding } from './types.js'; /** @@ -16,11 +16,11 @@ import type { Encoding } from './types.js'; export function encodeStr(data: Uint8Array, encoding: Encoding): string { switch (encoding) { case 'base58': - return toB58(data); + return toBase58(data); case 'base64': - return toB64(data); + return toBase64(data); case 'hex': - return toHEX(data); + return toHex(data); default: throw new Error('Unsupported encoding, supported values are: base64, hex'); } @@ -36,11 +36,11 @@ export function encodeStr(data: Uint8Array, encoding: Encoding): string { export function decodeStr(data: string, encoding: Encoding): Uint8Array { switch (encoding) { case 'base58': - return fromB58(data); + return fromBase58(data); case 'base64': - return fromB64(data); + return fromBase64(data); case 'hex': - return fromHEX(data); + return fromHex(data); default: throw new Error('Unsupported encoding, supported values are: base64, hex'); } diff --git a/sdk/bcs/tests/bcs.test.ts b/sdk/bcs/tests/bcs.test.ts index 11e117781bec9..e1f6559898e35 100644 --- a/sdk/bcs/tests/bcs.test.ts +++ b/sdk/bcs/tests/bcs.test.ts @@ -3,7 +3,7 @@ import { describe, expect, it } from 'vitest'; -import { bcs, fromB64 } from './../src/index'; +import { bcs, fromBase64 } from './../src/index'; describe('BCS: Primitives', () => { it('should support growing size', () => { @@ -22,7 +22,7 @@ describe('BCS: Primitives', () => { const setBytes = Coin.serialize(expected, { initialSize: 1, maxSize: 1024 }); - expect(Coin.parse(fromB64(rustBcs))).toEqual(expected); + expect(Coin.parse(fromBase64(rustBcs))).toEqual(expected); expect(setBytes.toBase64()).toEqual(rustBcs); }); diff --git a/sdk/bcs/tests/builder.test.ts b/sdk/bcs/tests/builder.test.ts index 7e67e09ec2cff..b91962e096512 100644 --- a/sdk/bcs/tests/builder.test.ts +++ b/sdk/bcs/tests/builder.test.ts @@ -3,7 +3,7 @@ import { describe, expect, test } from 'vitest'; -import { BcsReader, BcsWriter, toB58, toB64, toHEX } from '../src'; +import { BcsReader, BcsWriter, toBase58, toBase64, toHex } from '../src'; import { BcsType } from '../src/bcs-type.js'; import { bcs } from '../src/bcs.js'; @@ -261,17 +261,17 @@ function testType( test(name, () => { const serialized = schema.serialize(value); const bytes = serialized.toBytes(); - expect(toHEX(bytes)).toBe(hex); + expect(toHex(bytes)).toBe(hex); expect(serialized.toHex()).toBe(hex); - expect(serialized.toBase64()).toBe(toB64(bytes)); - expect(serialized.toBase58()).toBe(toB58(bytes)); + expect(serialized.toBase64()).toBe(toBase64(bytes)); + expect(serialized.toBase58()).toBe(toBase58(bytes)); const deserialized = schema.parse(bytes); expect(deserialized).toEqual(expected); const writer = new BcsWriter({ initialSize: bytes.length }); schema.write(value, writer); - expect(toHEX(writer.toBytes())).toBe(hex); + expect(toHex(writer.toBytes())).toBe(hex); const reader = new BcsReader(bytes); diff --git a/sdk/bcs/tests/encodings.test.ts b/sdk/bcs/tests/encodings.test.ts index f60252088de4c..11c91b3fb2df8 100644 --- a/sdk/bcs/tests/encodings.test.ts +++ b/sdk/bcs/tests/encodings.test.ts @@ -3,38 +3,38 @@ import { describe, expect, it } from 'vitest'; -import { bcs, fromB58, fromB64, fromHEX, toHEX } from './../src/index'; +import { bcs, fromBase58, fromBase64, fromHex, toHex } from './../src/index'; describe('BCS: Encodings', () => { it('should de/ser hex, base58 and base64', () => { - expect(bcs.u8().parse(fromB64('AA=='))).toEqual(0); - expect(bcs.u8().parse(fromHEX('00'))).toEqual(0); - expect(bcs.u8().parse(fromB58('1'))).toEqual(0); + expect(bcs.u8().parse(fromBase64('AA=='))).toEqual(0); + expect(bcs.u8().parse(fromHex('00'))).toEqual(0); + expect(bcs.u8().parse(fromBase58('1'))).toEqual(0); const STR = 'this is a test string'; const str = bcs.string().serialize(STR); - expect(bcs.string().parse(fromB58(str.toBase58()))).toEqual(STR); - expect(bcs.string().parse(fromB64(str.toBase64()))).toEqual(STR); - expect(bcs.string().parse(fromHEX(str.toHex()))).toEqual(STR); + expect(bcs.string().parse(fromBase58(str.toBase58()))).toEqual(STR); + expect(bcs.string().parse(fromBase64(str.toBase64()))).toEqual(STR); + expect(bcs.string().parse(fromHex(str.toHex()))).toEqual(STR); }); it('should deserialize hex with leading 0s', () => { const addressLeading0 = 'a7429d7a356dd98f688f11a330a32e0a3cc1908734a8c5a5af98f34ec93df0c'; - expect(toHEX(Uint8Array.from([0, 1]))).toEqual('0001'); - expect(fromHEX('0x1')).toEqual(Uint8Array.from([1])); - expect(fromHEX('1')).toEqual(Uint8Array.from([1])); - expect(fromHEX('111')).toEqual(Uint8Array.from([1, 17])); - expect(fromHEX('001')).toEqual(Uint8Array.from([0, 1])); - expect(fromHEX('011')).toEqual(Uint8Array.from([0, 17])); - expect(fromHEX('0011')).toEqual(Uint8Array.from([0, 17])); - expect(fromHEX('0x0011')).toEqual(Uint8Array.from([0, 17])); - expect(fromHEX(addressLeading0)).toEqual( + expect(toHex(Uint8Array.from([0, 1]))).toEqual('0001'); + expect(fromHex('0x1')).toEqual(Uint8Array.from([1])); + expect(fromHex('1')).toEqual(Uint8Array.from([1])); + expect(fromHex('111')).toEqual(Uint8Array.from([1, 17])); + expect(fromHex('001')).toEqual(Uint8Array.from([0, 1])); + expect(fromHex('011')).toEqual(Uint8Array.from([0, 17])); + expect(fromHex('0011')).toEqual(Uint8Array.from([0, 17])); + expect(fromHex('0x0011')).toEqual(Uint8Array.from([0, 17])); + expect(fromHex(addressLeading0)).toEqual( Uint8Array.from([ 10, 116, 41, 215, 163, 86, 221, 152, 246, 136, 241, 26, 51, 10, 50, 224, 163, 204, 25, 8, 115, 74, 140, 90, 90, 249, 143, 52, 236, 147, 223, 12, ]), ); - expect(toHEX(fromHEX(addressLeading0))).toEqual(`0${addressLeading0}`); + expect(toHex(fromHex(addressLeading0))).toEqual(`0${addressLeading0}`); }); }); diff --git a/sdk/create-dapp/CHANGELOG.md b/sdk/create-dapp/CHANGELOG.md index 04b26c5faa11e..c3362f697c758 100644 --- a/sdk/create-dapp/CHANGELOG.md +++ b/sdk/create-dapp/CHANGELOG.md @@ -1,5 +1,52 @@ # @mysten/create-dapp +## 0.3.27 + +### Patch Changes + +- Updated dependencies [477d2a4] + - @mysten/sui@1.13.0 + - @mysten/dapp-kit@0.14.27 + +## 0.3.26 + +### Patch Changes + +- @mysten/dapp-kit@0.14.26 + +## 0.3.25 + +### Patch Changes + +- Updated dependencies [5436a90] +- Updated dependencies [5436a90] + - @mysten/sui@1.12.0 + - @mysten/dapp-kit@0.14.25 + +## 0.3.24 + +### Patch Changes + +- Updated dependencies [489f421] +- Updated dependencies [489f421] + - @mysten/sui@1.11.0 + - @mysten/dapp-kit@0.14.24 + +## 0.3.23 + +### Patch Changes + +- Updated dependencies [640b757] + - @mysten/dapp-kit@0.14.23 + +## 0.3.22 + +### Patch Changes + +- Updated dependencies [830b8d8] + - @mysten/sui@1.10.0 + - @mysten/dapp-kit@0.14.22 + ## 0.3.21 ### Patch Changes diff --git a/sdk/create-dapp/package.json b/sdk/create-dapp/package.json index 7b7ddaa056afe..7737c2db3379a 100644 --- a/sdk/create-dapp/package.json +++ b/sdk/create-dapp/package.json @@ -3,7 +3,7 @@ "author": "Mysten Labs ", "description": "A CLI for creating new Sui dApps", "homepage": "https://sdk.mystenlabs.com", - "version": "0.3.21", + "version": "0.3.27", "license": "Apache-2.0", "files": [ "CHANGELOG.md", diff --git a/sdk/dapp-kit/CHANGELOG.md b/sdk/dapp-kit/CHANGELOG.md index 1af73803b0d5c..ca385c4a3fc06 100644 --- a/sdk/dapp-kit/CHANGELOG.md +++ b/sdk/dapp-kit/CHANGELOG.md @@ -1,5 +1,57 @@ # @mysten/dapp-kit +## 0.14.27 + +### Patch Changes + +- Updated dependencies [477d2a4] + - @mysten/sui@1.13.0 + - @mysten/wallet-standard@0.13.8 + - @mysten/zksend@0.11.8 + +## 0.14.26 + +### Patch Changes + +- Updated dependencies [af39b6a] + - @mysten/zksend@0.11.7 + +## 0.14.25 + +### Patch Changes + +- Updated dependencies [5436a90] +- Updated dependencies [5436a90] + - @mysten/sui@1.12.0 + - @mysten/wallet-standard@0.13.7 + - @mysten/zksend@0.11.6 + +## 0.14.24 + +### Patch Changes + +- Updated dependencies [489f421] +- Updated dependencies [489f421] + - @mysten/sui@1.11.0 + - @mysten/wallet-standard@0.13.6 + - @mysten/zksend@0.11.5 + +## 0.14.23 + +### Patch Changes + +- 640b757: Add `getSuiClientQuery` to get the `queryOptions` config for usage with the `QueryClient` + outside of React hooks. Added `useSuiClientSuspenseQuery` to support suspense-based data fetching. + +## 0.14.22 + +### Patch Changes + +- Updated dependencies [830b8d8] + - @mysten/sui@1.10.0 + - @mysten/wallet-standard@0.13.5 + - @mysten/zksend@0.11.4 + ## 0.14.21 ### Patch Changes diff --git a/sdk/dapp-kit/package.json b/sdk/dapp-kit/package.json index b8d0249d00dc2..d9481732ea330 100644 --- a/sdk/dapp-kit/package.json +++ b/sdk/dapp-kit/package.json @@ -3,7 +3,7 @@ "author": "Mysten Labs ", "description": "A collection of React hooks and components for interacting with the Sui blockchain and wallets.", "homepage": "https://sdk.mystenlabs.com/typescript", - "version": "0.14.21", + "version": "0.14.27", "license": "Apache-2.0", "files": [ "CHANGELOG.md", diff --git a/sdk/dapp-kit/src/hooks/useSuiClientQuery.ts b/sdk/dapp-kit/src/hooks/useSuiClientQuery.ts index 29a56f580afe1..e3f9c850ae9c0 100644 --- a/sdk/dapp-kit/src/hooks/useSuiClientQuery.ts +++ b/sdk/dapp-kit/src/hooks/useSuiClientQuery.ts @@ -2,8 +2,13 @@ // SPDX-License-Identifier: Apache-2.0 import type { SuiClient } from '@mysten/sui/client'; -import type { UseQueryOptions, UseQueryResult } from '@tanstack/react-query'; -import { useQuery } from '@tanstack/react-query'; +import type { + UndefinedInitialDataOptions, + UseQueryOptions, + UseQueryResult, +} from '@tanstack/react-query'; +import { queryOptions, useQuery, useSuspenseQuery } from '@tanstack/react-query'; +import { useMemo } from 'react'; import type { PartialBy } from '../types/utilityTypes.js'; import { useSuiClientContext } from './useSuiClient.js'; @@ -35,6 +40,34 @@ export type UseSuiClientQueryOptions = Par 'queryKey' >; +export type GetSuiClientQueryOptions = { + client: SuiClient; + network: string; + method: T; + options?: PartialBy< + Omit, 'queryFn'>, + 'queryKey' + >; +} & (undefined extends SuiRpcMethods[T]['params'] + ? { params?: SuiRpcMethods[T]['params'] } + : { params: SuiRpcMethods[T]['params'] }); + +export function getSuiClientQuery({ + client, + network, + method, + params, + options, +}: GetSuiClientQueryOptions) { + return queryOptions({ + ...options, + queryKey: [network, method, params], + queryFn: async () => { + return await client[method](params as never); + }, + }); +} + export function useSuiClientQuery< T extends keyof SuiRpcMethods, TData = SuiRpcMethods[T]['result'], @@ -59,3 +92,32 @@ export function useSuiClientQuery< }, }); } + +export function useSuiClientSuspenseQuery< + T extends keyof SuiRpcMethods, + TData = SuiRpcMethods[T]['result'], +>( + ...args: undefined extends SuiRpcMethods[T]['params'] + ? [method: T, params?: SuiRpcMethods[T]['params'], options?: UndefinedInitialDataOptions] + : [method: T, params: SuiRpcMethods[T]['params'], options?: UndefinedInitialDataOptions] +) { + const [method, params, options = {}] = args as [ + method: T, + params?: SuiRpcMethods[T]['params'], + options?: UndefinedInitialDataOptions, + ]; + + const suiContext = useSuiClientContext(); + + const query = useMemo(() => { + return getSuiClientQuery({ + client: suiContext.client, + network: suiContext.network, + method, + params, + options, + }); + }, [suiContext.client, suiContext.network, method, params, options]); + + return useSuspenseQuery(query); +} diff --git a/sdk/dapp-kit/src/hooks/wallet/useReportTransactionEffects.ts b/sdk/dapp-kit/src/hooks/wallet/useReportTransactionEffects.ts index c3dec6068b093..554d93f4fe3cd 100644 --- a/sdk/dapp-kit/src/hooks/wallet/useReportTransactionEffects.ts +++ b/sdk/dapp-kit/src/hooks/wallet/useReportTransactionEffects.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { toB64 } from '@mysten/sui/utils'; +import { toBase64 } from '@mysten/sui/utils'; import type { SuiReportTransactionEffectsInput } from '@mysten/wallet-standard'; import type { UseMutationOptions, UseMutationResult } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query'; @@ -71,7 +71,7 @@ export function useReportTransactionEffects({ if (reportTransactionEffectsFeature) { return await reportTransactionEffectsFeature.reportTransactionEffects({ - effects: Array.isArray(effects) ? toB64(new Uint8Array(effects)) : effects, + effects: Array.isArray(effects) ? toBase64(new Uint8Array(effects)) : effects, account, chain: chain ?? currentWallet?.chains[0], }); diff --git a/sdk/dapp-kit/src/hooks/wallet/useSignAndExecuteTransaction.ts b/sdk/dapp-kit/src/hooks/wallet/useSignAndExecuteTransaction.ts index 4d65fc333f654..f09902e7e04c3 100644 --- a/sdk/dapp-kit/src/hooks/wallet/useSignAndExecuteTransaction.ts +++ b/sdk/dapp-kit/src/hooks/wallet/useSignAndExecuteTransaction.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { Transaction } from '@mysten/sui/transactions'; -import { toB64 } from '@mysten/sui/utils'; +import { toBase64 } from '@mysten/sui/utils'; import type { SuiSignAndExecuteTransactionInput, SuiSignAndExecuteTransactionOutput, @@ -100,7 +100,7 @@ export function useSignAndExecuteTransaction< return { digest, rawEffects, - effects: toB64(new Uint8Array(rawEffects!)), + effects: toBase64(new Uint8Array(rawEffects!)), bytes, signature, }; @@ -153,7 +153,7 @@ export function useSignAndExecuteTransaction< if ('effects' in result && result.effects?.bcs) { effects = result.effects.bcs; } else if ('rawEffects' in result) { - effects = toB64(new Uint8Array(result.rawEffects!)); + effects = toBase64(new Uint8Array(result.rawEffects!)); } else { throw new Error('Could not parse effects from transaction result.'); } diff --git a/sdk/dapp-kit/src/hooks/wallet/useUnsafeBurnerWallet.ts b/sdk/dapp-kit/src/hooks/wallet/useUnsafeBurnerWallet.ts index 69010e0485dd9..2c302bc29977d 100644 --- a/sdk/dapp-kit/src/hooks/wallet/useUnsafeBurnerWallet.ts +++ b/sdk/dapp-kit/src/hooks/wallet/useUnsafeBurnerWallet.ts @@ -4,7 +4,7 @@ import type { SuiClient } from '@mysten/sui/client'; import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; import { Transaction } from '@mysten/sui/transactions'; -import { toB64 } from '@mysten/sui/utils'; +import { toBase64 } from '@mysten/sui/utils'; import type { StandardConnectFeature, StandardConnectMethod, @@ -198,7 +198,7 @@ function registerUnsafeBurnerWallet(suiClient: SuiClient) { bytes, signature, digest, - effects: toB64(new Uint8Array(rawEffects!)), + effects: toBase64(new Uint8Array(rawEffects!)), }; }; } diff --git a/sdk/dapp-kit/test/hooks/useSignAndExecuteTransaction.test.tsx b/sdk/dapp-kit/test/hooks/useSignAndExecuteTransaction.test.tsx index f8a92a610272b..f220d4da0b474 100644 --- a/sdk/dapp-kit/test/hooks/useSignAndExecuteTransaction.test.tsx +++ b/sdk/dapp-kit/test/hooks/useSignAndExecuteTransaction.test.tsx @@ -7,7 +7,7 @@ import { Transaction } from '@mysten/sui/transactions'; import { act, renderHook, waitFor } from '@testing-library/react'; import { expect, type Mock } from 'vitest'; -import { toB58 } from '../../../bcs/dist/cjs/b58.js'; +import { toBase58 } from '../../../bcs/dist/cjs/b58.js'; import { WalletFeatureNotSupportedError, WalletNotConnectedError, @@ -154,7 +154,7 @@ describe('useSignAndExecuteTransaction', () => { const wrapper = createWalletProviderContextWrapper({}, suiClient); - const fakeDigest = toB58( + const fakeDigest = toBase58( new Uint8Array([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, diff --git a/sdk/deepbook-v3/CHANGELOG.md b/sdk/deepbook-v3/CHANGELOG.md index 07e1ad500200a..3aee7f84ea116 100644 --- a/sdk/deepbook-v3/CHANGELOG.md +++ b/sdk/deepbook-v3/CHANGELOG.md @@ -1,5 +1,93 @@ # @mysten/deepbook-v3 +## 0.12.1 + +### Patch Changes + +- Updated dependencies [477d2a4] + - @mysten/sui@1.13.0 + +## 0.12.0 + +### Minor Changes + +- 60f96ee: New stablecoin pool params + +## 0.11.0 + +### Minor Changes + +- 7b8e8ad: Mainnet pool packages + +## 0.10.0 + +### Minor Changes + +- 23c3a3a: DEEP Mainnet Redeploy + +## 0.9.0 + +### Minor Changes + +- 89f2e59: Mainnet packages + +## 0.8.5 + +### Patch Changes + +- c0fb6d6: Patch ID and bug fix + +## 0.8.4 + +### Patch Changes + +- 5df4e5e: Test Mainnet Packages + +## 0.8.3 + +### Patch Changes + +- Updated dependencies [5436a90] +- Updated dependencies [5436a90] + - @mysten/sui@1.12.0 + +## 0.8.2 + +### Patch Changes + +- f026ec6: Deepbook Package Upgrade + +## 0.8.1 + +### Patch Changes + +- Updated dependencies [489f421] +- Updated dependencies [489f421] + - @mysten/sui@1.11.0 + +## 0.8.0 + +### Minor Changes + +- 0d17307: Update deepbook sdk + +## 0.7.1 + +### Patch Changes + +- 37d259a: Locked balance feature + +## 0.7.0 + +### Minor Changes + +- 7923ed5: Newest deepbook package constants + +### Patch Changes + +- Updated dependencies [830b8d8] + - @mysten/sui@1.10.0 + ## 0.6.0 ### Minor Changes diff --git a/sdk/deepbook-v3/package.json b/sdk/deepbook-v3/package.json index ae442ee5ae5d7..d4002e7ac6e76 100644 --- a/sdk/deepbook-v3/package.json +++ b/sdk/deepbook-v3/package.json @@ -2,7 +2,7 @@ "name": "@mysten/deepbook-v3", "author": "Mysten Labs ", "description": "Sui Deepbook SDK", - "version": "0.6.0", + "version": "0.12.1", "license": "Apache-2.0", "type": "commonjs", "main": "./dist/cjs/index.js", diff --git a/sdk/deepbook-v3/src/client.ts b/sdk/deepbook-v3/src/client.ts index 9b151308ece86..e4e38e8e30e9e 100644 --- a/sdk/deepbook-v3/src/client.ts +++ b/sdk/deepbook-v3/src/client.ts @@ -567,4 +567,34 @@ export class DeepBookClient { }, }; } + + /** + * @description Get the locked balances for a pool and balance manager + * @param {string} poolKey Key of the pool + * @param {string} managerKey The key of the BalanceManager + * @returns {Promise<{ base: number, quote: number, deep: number }>} + * An object with base, quote, and deep locked for the balance manager in the pool + */ + async lockedBalance(poolKey: string, balanceManagerKey: string) { + const tx = new Transaction(); + const pool = this.#config.getPool(poolKey); + const baseScalar = this.#config.getCoin(pool.baseCoin).scalar; + const quoteScalar = this.#config.getCoin(pool.quoteCoin).scalar; + + tx.add(this.deepBook.lockedBalance(poolKey, balanceManagerKey)); + const res = await this.client.devInspectTransactionBlock({ + sender: normalizeSuiAddress(this.#address), + transactionBlock: tx, + }); + + const baseLocked = Number(bcs.U64.parse(new Uint8Array(res.results![0].returnValues![0][0]))); + const quoteLocked = Number(bcs.U64.parse(new Uint8Array(res.results![0].returnValues![1][0]))); + const deepLocked = Number(bcs.U64.parse(new Uint8Array(res.results![0].returnValues![2][0]))); + + return { + base: Number((baseLocked / baseScalar).toFixed(9)), + quote: Number((quoteLocked / quoteScalar).toFixed(9)), + deep: Number((deepLocked / DEEP_SCALAR).toFixed(9)), + }; + } } diff --git a/sdk/deepbook-v3/src/transactions/deepbook.ts b/sdk/deepbook-v3/src/transactions/deepbook.ts index 20619eb9f64b7..d0f1fec3cd586 100644 --- a/sdk/deepbook-v3/src/transactions/deepbook.ts +++ b/sdk/deepbook-v3/src/transactions/deepbook.ts @@ -648,4 +648,23 @@ export class DeepBookContract { typeArguments: [baseCoin.type, quoteCoin.type], }); }; + + /** + * @description Get the locked balance for a given pool and balance manager + * @param {string} poolKey Key of the pool + * @param {string} managerKey The key of the BalanceManager + * @returns A function that takes a Transaction object + */ + lockedBalance = (poolKey: string, managerKey: string) => (tx: Transaction) => { + const pool = this.#config.getPool(poolKey); + const baseCoin = this.#config.getCoin(pool.baseCoin); + const quoteCoin = this.#config.getCoin(pool.quoteCoin); + const managerId = this.#config.getBalanceManager(managerKey).address; + + tx.moveCall({ + target: `${this.#config.DEEPBOOK_PACKAGE_ID}::pool::locked_balance`, + arguments: [tx.object(pool.address), tx.object(managerId)], + typeArguments: [baseCoin.type, quoteCoin.type], + }); + }; } diff --git a/sdk/deepbook-v3/src/utils/constants.ts b/sdk/deepbook-v3/src/utils/constants.ts index a9ad88106e08e..bd54305f073c9 100644 --- a/sdk/deepbook-v3/src/utils/constants.ts +++ b/sdk/deepbook-v3/src/utils/constants.ts @@ -12,15 +12,15 @@ export interface DeepbookPackageIds { } export const testnetPackageIds = { - DEEPBOOK_PACKAGE_ID: '0x48cc688a15bdda6017c730a3c65b30414e642d041f2931ef14e08f6b0b2a1b7f', - REGISTRY_ID: '0x60517d0cae6f1168c1ba94cb45c06191f9837ec695d0d465d594b2f6287534f0', + DEEPBOOK_PACKAGE_ID: '0xcbf4748a965d469ea3a36cf0ccc5743b96c2d0ae6dee0762ed3eca65fac07f7e', + REGISTRY_ID: '0x98dace830ebebd44b7a3331c00750bf758f8a4b17a27380f5bb3fbe68cb984a7', DEEP_TREASURY_ID: '0x69fffdae0075f8f71f4fa793549c11079266910e8905169845af1f5d00e09dcb', } satisfies DeepbookPackageIds; export const mainnetPackageIds = { - DEEPBOOK_PACKAGE_ID: '', - REGISTRY_ID: '', - DEEP_TREASURY_ID: '', + DEEPBOOK_PACKAGE_ID: '0x2c8d603bc51326b8c13cef9dd07031a408a48dddb541963357661df5d3204809', + REGISTRY_ID: '0xaf16199a2dff736e9f07a845f23c5da6df6f756eddb631aed9d24a93efc4549d', + DEEP_TREASURY_ID: '0x032abf8948dda67a271bcc18e776dbbcfb0d58c8d288a700ff0d5521e57a1ffe', }; export const testnetCoins: CoinMap = { @@ -58,9 +58,9 @@ export const mainnetCoins: CoinMap = { scalar: 1000000000, }, USDC: { - address: ``, - type: ``, - scalar: 0, + address: `0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7`, + type: `0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC`, + scalar: 1000000, }, WUSDC: { address: `0x5d4b302506645c37ff133b98c4b50a5ae14841659738d6d733d59d0d217a93bf`, @@ -72,10 +72,15 @@ export const mainnetCoins: CoinMap = { type: `0xaf8cd5edc19c4512f4259f0bee101a40d41ebed738ade5874359610ef8eeced5::coin::COIN`, scalar: 100000000, }, + BETH: { + address: `0xd0e89b2af5e4910726fbcd8b8dd37bb79b29e5f83f7491bca830e94f7f226d29`, + type: `0xd0e89b2af5e4910726fbcd8b8dd37bb79b29e5f83f7491bca830e94f7f226d29::eth::ETH`, + scalar: 100000000, + }, WBTC: { address: `0x027792d9fed7f9844eb4839566001bb6f6cb4804f66aa2da6fe1ee242d896881`, type: `0x027792d9fed7f9844eb4839566001bb6f6cb4804f66aa2da6fe1ee242d896881::coin::COIN`, - scalar: 0, + scalar: 100000000, }, WUSDT: { address: `0xc060006111016b8a020ad5b33834984a437aaa7d3c74c18e09a95d48aceab08c`, @@ -86,22 +91,22 @@ export const mainnetCoins: CoinMap = { export const testnetPools: PoolMap = { DEEP_SUI: { - address: `0x877c81b2d760a91a405dc12974742ba2401dc1da727681d2de32c861f807ec28`, + address: `0x0d1b1746d220bd5ebac5231c7685480a16f1c707a46306095a4c67dc7ce4dcae`, baseCoin: 'DEEP', quoteCoin: 'SUI', }, SUI_DBUSDC: { - address: `0x966c99a5ce0ce3e09dacac0a42cc2b888d9e1a0c5f39b69f556c38f38ef0b81d`, + address: `0x520c89c6c78c566eed0ebf24f854a8c22d8fdd06a6f16ad01f108dad7f1baaea`, baseCoin: 'SUI', quoteCoin: 'DBUSDC', }, DEEP_DBUSDC: { - address: `0x6546ae819e61770c6070962157a2868c012c121ef4e6dd93e81e98900c61b799`, + address: `0xee4bb0db95dc571b960354713388449f0158317e278ee8cda59ccf3dcd4b5288`, baseCoin: 'DEEP', quoteCoin: 'DBUSDC', }, DBUSDT_DBUSDC: { - address: `0x9c0a6ef057b5f81d9f2d95a38ad753b8b5b8f535f9c10bb3e7d7256296eb4e18`, + address: `0x69cbb39a3821d681648469ff2a32b4872739d2294d30253ab958f85ace9e0491`, baseCoin: 'DBUSDT', quoteCoin: 'DBUSDC', }, @@ -109,8 +114,33 @@ export const testnetPools: PoolMap = { export const mainnetPools: PoolMap = { DEEP_SUI: { - address: ``, + address: `0xb663828d6217467c8a1838a03793da896cbe745b150ebd57d82f814ca579fc22`, baseCoin: 'DEEP', quoteCoin: 'SUI', }, + SUI_USDC: { + address: `0xe05dafb5133bcffb8d59f4e12465dc0e9faeaa05e3e342a08fe135800e3e4407`, + baseCoin: 'SUI', + quoteCoin: 'USDC', + }, + DEEP_USDC: { + address: `0xf948981b806057580f91622417534f491da5f61aeaf33d0ed8e69fd5691c95ce`, + baseCoin: 'DEEP', + quoteCoin: 'USDC', + }, + WUSDT_USDC: { + address: `0x4e2ca3988246e1d50b9bf209abb9c1cbfec65bd95afdacc620a36c67bdb8452f`, + baseCoin: 'WUSDT', + quoteCoin: 'USDC', + }, + WUSDC_USDC: { + address: `0xa0b9ebefb38c963fd115f52d71fa64501b79d1adcb5270563f92ce0442376545`, + baseCoin: 'WUSDC', + quoteCoin: 'USDC', + }, + BETH_USDC: { + address: `0x1109352b9112717bd2a7c3eb9a416fff1ba6951760f5bdd5424cf5e4e5b3e65c`, + baseCoin: 'BETH', + quoteCoin: 'USDC', + }, }; diff --git a/sdk/deepbook/CHANGELOG.md b/sdk/deepbook/CHANGELOG.md index c1a1b7a41cdef..73608ddc7d768 100644 --- a/sdk/deepbook/CHANGELOG.md +++ b/sdk/deepbook/CHANGELOG.md @@ -1,5 +1,35 @@ # @mysten/deepbook +## 0.8.22 + +### Patch Changes + +- Updated dependencies [477d2a4] + - @mysten/sui@1.13.0 + +## 0.8.21 + +### Patch Changes + +- Updated dependencies [5436a90] +- Updated dependencies [5436a90] + - @mysten/sui@1.12.0 + +## 0.8.20 + +### Patch Changes + +- Updated dependencies [489f421] +- Updated dependencies [489f421] + - @mysten/sui@1.11.0 + +## 0.8.19 + +### Patch Changes + +- Updated dependencies [830b8d8] + - @mysten/sui@1.10.0 + ## 0.8.18 ### Patch Changes diff --git a/sdk/deepbook/package.json b/sdk/deepbook/package.json index 9d1175246e219..711d7b93b1fbf 100644 --- a/sdk/deepbook/package.json +++ b/sdk/deepbook/package.json @@ -2,7 +2,7 @@ "name": "@mysten/deepbook", "author": "Mysten Labs ", "description": "Sui Deepbook SDK", - "version": "0.8.18", + "version": "0.8.22", "license": "Apache-2.0", "type": "commonjs", "main": "./dist/cjs/index.js", diff --git a/sdk/docs/package.json b/sdk/docs/package.json index 645bc7f36608a..7bf683235eb86 100644 --- a/sdk/docs/package.json +++ b/sdk/docs/package.json @@ -35,11 +35,12 @@ "@mysten/zksend": "workspace:*", "@tanstack/react-query": "^5.50.1", "@types/node": "^20.14.10", - "next": "^14.2.4", + "next": "^14.2.10", "nextra": "^2.13.4", "nextra-theme-docs": "^2.13.4", "react": "^18.3.1", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "typedoc-plugin-mermaid": "^1.12.0" }, "devDependencies": { "@types/react": "^18.3.3", diff --git a/sdk/docs/pages/bcs/index.mdx b/sdk/docs/pages/bcs/index.mdx index ae8b9ec1b2dff..76479c8eec3a0 100644 --- a/sdk/docs/pages/bcs/index.mdx +++ b/sdk/docs/pages/bcs/index.mdx @@ -16,12 +16,12 @@ npm i @mysten/bcs ## Quickstart ```ts -import { bcs } from '@mysten/bcs'; +import { bcs, fromHex, toHex } from '@mysten/bcs'; // define UID as a 32-byte array, then add a transform to/from hex strings const UID = bcs.fixedArray(32, bcs.u8()).transform({ - input: (id: string) => fromHEX(id), - output: (id) => toHEX(Uint8Array.from(id)), + input: (id: string) => fromHex(id), + output: (id) => toHex(Uint8Array.from(id)), }); const Coin = bcs.struct('Coin', { @@ -114,9 +114,9 @@ import { bcs } from '@mysten/bcs'; const intList = bcs.vector(bcs.u8()).serialize([1, 2, 3, 4, 5]).toBytes(); const stringList = bcs.vector(bcs.string()).serialize(['a', 'b', 'c']).toBytes(); -// Arrays -const intArray = bcs.array(4, bcs.u8()).serialize([1, 2, 3, 4]).toBytes(); -const stringArray = bcs.array(3, bcs.string()).serialize(['a', 'b', 'c']).toBytes(); +// Fixed length Arrays +const intArray = bcs.fixedArray(4, bcs.u8()).serialize([1, 2, 3, 4]).toBytes(); +const stringArray = bcs.fixedArray(3, bcs.string()).serialize(['a', 'b', 'c']).toBytes(); // Option const option = bcs.option(bcs.string()).serialize('some value').toBytes(); @@ -127,7 +127,7 @@ const MyEnum = bcs.enum('MyEnum', { NoType: null, Int: bcs.u8(), String: bcs.string(), - Array: bcs.array(3, bcs.u8()), + Array: bcs.fixedArray(3, bcs.u8()), }); const noTypeEnum = MyEnum.serialize({ NoType: null }).toBytes(); @@ -163,8 +163,8 @@ const map = bcs const parsedIntList = bcs.vector(bcs.u8()).parse(intList); const parsedStringList = bcs.vector(bcs.string()).parse(stringList); -// Arrays -const parsedIntArray = bcs.array(4, bcs.u8()).parse(intArray); +// Fixed length Arrays +const parsedIntArray = bcs.fixedArray(4, bcs.u8()).parse(intArray); // Option const parsedOption = bcs.option(bcs.string()).parse(option); @@ -242,10 +242,12 @@ represent an address as a hex string, but the BCS serialization format for addre array. To handle this, you can use the `transform` API to map between the two formats: ```ts +import { bcs, fromHex, toHex } from '@mysten/bcs'; + const Address = bcs.bytes(32).transform({ // To change the input type, you need to provide a type definition for the input - input: (val: string) => fromHEX(val), - output: (val) => toHEX(val), + input: (val: string) => fromHex(val), + output: (val) => toHex(val), }); const serialized = Address.serialize('0x000000...').toBytes(); @@ -306,7 +308,7 @@ preserves type information for the serialized bytes, and can be used to get raw formats. ```ts -import { bcs, fromB58, fromB64, fromHex } from '@mysten/bcs'; +import { bcs, fromBase58, fromBase64, fromHex } from '@mysten/bcs'; const serializedString = bcs.string().serialize('this is a string'); @@ -323,8 +325,8 @@ const str1 = bcs.string().parse(bytes); // If your data is encoded as string, you need to convert it to Uint8Array first const str2 = bcs.string().parse(fromHex(hex)); -const str3 = bcs.string().parse(fromB64(base64)); -const str4 = bcs.string().parse(fromB58(base58)); +const str3 = bcs.string().parse(fromBase64(base64)); +const str4 = bcs.string().parse(fromBase58(base58)); console.assert((str1 == str2) == (str3 == str4), 'Result is the same'); ``` diff --git a/sdk/docs/pages/dapp-kit/sui-client-provider.mdx b/sdk/docs/pages/dapp-kit/sui-client-provider.mdx index f0e04ab216b9f..6f38dcd8100a6 100644 --- a/sdk/docs/pages/dapp-kit/sui-client-provider.mdx +++ b/sdk/docs/pages/dapp-kit/sui-client-provider.mdx @@ -163,8 +163,6 @@ to get the variables defined in your configuration. - `useNetworkVariable` returns a specific variable from the network configuration ```tsx -import { createNetworkConfig, SuiClientProvider } from '@mysten/dapp-kit'; - import { createNetworkConfig, SuiClientProvider, WalletProvider } from '@mysten/dapp-kit'; import { getFullnodeUrl } from '@mysten/sui/client'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; @@ -175,13 +173,13 @@ const { networkConfig, useNetworkVariable } = createNetworkConfig({ url: getFullnodeUrl('localnet'), variables: { myMovePackageId: '0x123', - } + }, }, mainnet: { url: getFullnodeUrl('mainnet'), variables: { myMovePackageId: '0x456', - } + }, }, }); @@ -199,7 +197,6 @@ function App() { ); } - function YourApp() { const id = useNetworkVariable('myMovePackageId'); diff --git a/sdk/docs/pages/dapp-kit/wallet-components/ConnectModal.mdx b/sdk/docs/pages/dapp-kit/wallet-components/ConnectModal.mdx index 876c430e4dd1d..166ed62d9dad9 100644 --- a/sdk/docs/pages/dapp-kit/wallet-components/ConnectModal.mdx +++ b/sdk/docs/pages/dapp-kit/wallet-components/ConnectModal.mdx @@ -12,6 +12,7 @@ the dApp. ```tsx import { ConnectModal, useCurrentAccount } from '@mysten/dapp-kit'; +import { useState } from 'react'; export function YourApp() { const currentAccount = useCurrentAccount(); diff --git a/sdk/docs/pages/dapp-kit/wallet-hooks/useReportTransactionEffects.mdx b/sdk/docs/pages/dapp-kit/wallet-hooks/useReportTransactionEffects.mdx index 47b6f81012b1d..171d9b11e5594 100644 --- a/sdk/docs/pages/dapp-kit/wallet-hooks/useReportTransactionEffects.mdx +++ b/sdk/docs/pages/dapp-kit/wallet-hooks/useReportTransactionEffects.mdx @@ -14,7 +14,7 @@ import { useReportTransactionEffects, useSuiClient, } from '@mysten/dapp-kit'; -import { toB64 } from '@mysten/sui/utils'; +import { toBase64 } from '@mysten/sui/utils'; import { useState } from 'react'; function MyComponent() { diff --git a/sdk/docs/pages/dapp-kit/wallet-hooks/useSignTransaction.mdx b/sdk/docs/pages/dapp-kit/wallet-hooks/useSignTransaction.mdx index 546968ebb1ab1..ea27d51a6a6b4 100644 --- a/sdk/docs/pages/dapp-kit/wallet-hooks/useSignTransaction.mdx +++ b/sdk/docs/pages/dapp-kit/wallet-hooks/useSignTransaction.mdx @@ -12,7 +12,7 @@ import { useSignTransaction, useSuiClient, } from '@mysten/dapp-kit'; -import { toB64 } from '@mysten/sui/utils'; +import { toBase64 } from '@mysten/sui/utils'; import { useState } from 'react'; function MyComponent() { diff --git a/sdk/docs/pages/typescript/cryptography/keypairs.mdx b/sdk/docs/pages/typescript/cryptography/keypairs.mdx index b779184e20059..ba7c173b8f0c5 100644 --- a/sdk/docs/pages/typescript/cryptography/keypairs.mdx +++ b/sdk/docs/pages/typescript/cryptography/keypairs.mdx @@ -172,6 +172,21 @@ const keypair = Ed25519Keypair.fromSecretKey(secretKey); See [SIP-15](https://github.com/sui-foundation/sips/blob/main/sips/sip-15.md) for additional context and motivation. +If you know your keypair schema, you can use the `fromSecretKey` method of the appropriate keypair +to directly derive the keypair from the secret key. + +```typescript +const secretKey = 'suiprivkey1qzse89atw7d3zum8ujep76d2cxmgduyuast0y9fu23xcl0mpafgkktllhyc'; + +const keypair = Ed25519Keypair.fromSecretKey(secretKey); +``` + +You can also export a keypair to a Bech32 encoded secret key using the `getSecretKey` method. + +```typescript +const secretKey = keypair.getSecretKey(); +``` + ## Deriving a `Keypair` from a hex encoded secret key If you have an existing secret key formatted as a hex encoded string, you can derive a `Keypair` by diff --git a/sdk/docs/pages/typescript/cryptography/multisig.mdx b/sdk/docs/pages/typescript/cryptography/multisig.mdx index 51e042813f4c4..93e0c55213a72 100644 --- a/sdk/docs/pages/typescript/cryptography/multisig.mdx +++ b/sdk/docs/pages/typescript/cryptography/multisig.mdx @@ -142,7 +142,7 @@ const multisigAddress = multiSigPublicKey.toSuiAddress(); const zkLoginSig = getZkLoginSignature({ inputs: zkLoginInputs, maxEpoch: '2', - userSignature: fromB64(ephemeralSig), + userSignature: fromBase64(ephemeralSig), }); // a valid multisig with just the zklogin signature. diff --git a/sdk/docs/pages/typescript/owned-object-pool/overview.mdx b/sdk/docs/pages/typescript/owned-object-pool/overview.mdx index e0f5b66ab4bb6..9dd21a07e54a3 100644 --- a/sdk/docs/pages/typescript/owned-object-pool/overview.mdx +++ b/sdk/docs/pages/typescript/owned-object-pool/overview.mdx @@ -77,7 +77,7 @@ transaction. If you need SUI for a test network, you can import { SuiClient } from '@mysten/sui/client'; import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; import { Transaction } from '@mysten/sui/transactions'; -import { fromB64 } from '@mysten/sui/utils'; +import { fromBase64 } from '@mysten/sui/utils'; /* HERE ARE DEFINED THE PREPARATORY STEPS IF YOU WANT TO CODE ALONG*/ // Define the transaction @@ -92,7 +92,7 @@ function createPaymenttx(recipient: string): Transaction { } // Define your admin keypair and client const ADMIN_SECRET_KEY: string = ''; -const adminPrivateKeyArray = Uint8Array.from(Array.from(fromB64(ADMIN_SECRET_KEY))); +const adminPrivateKeyArray = Uint8Array.from(Array.from(fromBase64(ADMIN_SECRET_KEY))); const adminKeypair = Ed25519Keypair.fromSecretKey(adminPrivateKeyArray.slice(1)); const client = new SuiClient({ diff --git a/sdk/docs/pages/typescript/transaction-building/gas.mdx b/sdk/docs/pages/typescript/transaction-building/gas.mdx index b7b8bd145c9d9..96ab0c99bc8d5 100644 --- a/sdk/docs/pages/typescript/transaction-building/gas.mdx +++ b/sdk/docs/pages/typescript/transaction-building/gas.mdx @@ -54,3 +54,6 @@ will be the coin that all others are merged into. // of the input objects for the transaction tx.setGasPayment([coin1, coin2]); ``` + +Gas coins should be objects containing the coins objectId, version, and digest (ie +`{ objectId: string, version: string | number, digest: string }`). diff --git a/sdk/docs/pages/typescript/utils.mdx b/sdk/docs/pages/typescript/utils.mdx index dc5d3ee155e76..d738b1cafc82d 100644 --- a/sdk/docs/pages/typescript/utils.mdx +++ b/sdk/docs/pages/typescript/utils.mdx @@ -43,7 +43,7 @@ case, or exists on chain). The following methods are re-exported to help with converting between commonly used encodings -- `fromHEX`: Deserializes a hex string to a Uint8Array -- `toHEX`: Serializes a Uint8Array to a hex string -- `fromB64`: Deserializes a base64 string to a Uint8Array -- `toB64`: Serializes a Uint8Array to a base64 string +- `fromHex`: Deserializes a hex string to a Uint8Array +- `toHex`: Serializes a Uint8Array to a hex string +- `fromBase64`: Deserializes a base64 string to a Uint8Array +- `toBase64`: Serializes a Uint8Array to a base64 string diff --git a/sdk/docs/typedoc.json b/sdk/docs/typedoc.json index edf0f934b0299..1a2a7e3ff1610 100644 --- a/sdk/docs/typedoc.json +++ b/sdk/docs/typedoc.json @@ -15,5 +15,6 @@ "excludePrivate": true, "intentionallyNotExported": [], "out": "public/typedoc", - "entryPointStrategy": "packages" + "entryPointStrategy": "packages", + "plugin": ["typedoc-plugin-mermaid"] } diff --git a/sdk/enoki/CHANGELOG.md b/sdk/enoki/CHANGELOG.md index d769926b3bf69..b4176c6e2d489 100644 --- a/sdk/enoki/CHANGELOG.md +++ b/sdk/enoki/CHANGELOG.md @@ -1,5 +1,39 @@ # @mysten/enoki +## 0.4.6 + +### Patch Changes + +- Updated dependencies [477d2a4] + - @mysten/sui@1.13.0 + - @mysten/zklogin@0.7.23 + +## 0.4.5 + +### Patch Changes + +- Updated dependencies [5436a90] +- Updated dependencies [5436a90] + - @mysten/sui@1.12.0 + - @mysten/zklogin@0.7.22 + +## 0.4.4 + +### Patch Changes + +- Updated dependencies [489f421] +- Updated dependencies [489f421] + - @mysten/sui@1.11.0 + - @mysten/zklogin@0.7.21 + +## 0.4.3 + +### Patch Changes + +- Updated dependencies [830b8d8] + - @mysten/sui@1.10.0 + - @mysten/zklogin@0.7.20 + ## 0.4.2 ### Patch Changes diff --git a/sdk/enoki/package.json b/sdk/enoki/package.json index a7bc8770aba1e..5a72fedfc8f35 100644 --- a/sdk/enoki/package.json +++ b/sdk/enoki/package.json @@ -1,6 +1,6 @@ { "name": "@mysten/enoki", - "version": "0.4.2", + "version": "0.4.6", "description": "TODO: Description", "license": "Apache-2.0", "author": "Mysten Labs ", diff --git a/sdk/enoki/src/EnokiFlow.ts b/sdk/enoki/src/EnokiFlow.ts index ad1bb67f04e0d..b2a00b5b77fb8 100644 --- a/sdk/enoki/src/EnokiFlow.ts +++ b/sdk/enoki/src/EnokiFlow.ts @@ -5,7 +5,7 @@ import type { SuiClient } from '@mysten/sui/client'; import { decodeSuiPrivateKey } from '@mysten/sui/cryptography'; import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; import type { Transaction } from '@mysten/sui/transactions'; -import { fromB64, toB64 } from '@mysten/sui/utils'; +import { fromBase64, toBase64 } from '@mysten/sui/utils'; import type { ZkLoginSignatureInputs } from '@mysten/sui/zklogin'; import { decodeJwt } from 'jose'; import type { WritableAtom } from 'nanostores'; @@ -163,7 +163,7 @@ export class EnokiFlow { expiresAt: estimatedExpiration, maxEpoch, randomness, - ephemeralKeyPair: toB64(decodeSuiPrivateKey(ephemeralKeyPair.getSecretKey()).secretKey), + ephemeralKeyPair: toBase64(decodeSuiPrivateKey(ephemeralKeyPair.getSecretKey()).secretKey), }); return oauthUrl; @@ -273,7 +273,7 @@ export class EnokiFlow { throw new Error('Missing required parameters for proof generation'); } - const ephemeralKeyPair = Ed25519Keypair.fromSecretKey(fromB64(zkp.ephemeralKeyPair)); + const ephemeralKeyPair = Ed25519Keypair.fromSecretKey(fromBase64(zkp.ephemeralKeyPair)); const proof = await this.#enokiClient.createZkLoginZkp({ network, @@ -311,7 +311,7 @@ export class EnokiFlow { address, maxEpoch: zkp.maxEpoch, proof: zkp.proof, - ephemeralKeypair: Ed25519Keypair.fromSecretKey(fromB64(zkp.ephemeralKeyPair)), + ephemeralKeypair: Ed25519Keypair.fromSecretKey(fromBase64(zkp.ephemeralKeyPair)), }); } @@ -338,7 +338,7 @@ export class EnokiFlow { return await this.#enokiClient.createSponsoredTransaction({ jwt: session.jwt, network, - transactionKindBytes: toB64(transactionKindBytes), + transactionKindBytes: toBase64(transactionKindBytes), }); } @@ -354,7 +354,7 @@ export class EnokiFlow { client: SuiClient; }) { const keypair = await this.getKeypair({ network }); - const userSignature = await keypair.signTransaction(fromB64(bytes)); + const userSignature = await keypair.signTransaction(fromBase64(bytes)); await this.#enokiClient.executeSponsoredTransaction({ digest, diff --git a/sdk/enoki/src/encryption.ts b/sdk/enoki/src/encryption.ts index 6a12071a0b79f..7f517af270946 100644 --- a/sdk/enoki/src/encryption.ts +++ b/sdk/enoki/src/encryption.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64, toB64 } from '@mysten/sui/utils'; +import { fromBase64, toBase64 } from '@mysten/sui/utils'; /** * An interface @@ -63,9 +63,9 @@ export function createDefaultEncryption(): Encryption { ); return JSON.stringify({ - payload: toB64(new Uint8Array(payload)), - iv: toB64(iv), - salt: toB64(salt), + payload: toBase64(new Uint8Array(payload)), + iv: toBase64(iv), + salt: toBase64(salt), } satisfies EncryptedJSON); }, async decrypt(password, data) { @@ -74,15 +74,15 @@ export function createDefaultEncryption(): Encryption { throw new Error('Invalid encrypted data'); } - const { derivedKey } = await keyFromPassword(password, fromB64(parsed.salt)); + const { derivedKey } = await keyFromPassword(password, fromBase64(parsed.salt)); const decryptedContent = await crypto.subtle.decrypt( { name: 'AES-GCM', - iv: fromB64(parsed.iv), + iv: fromBase64(parsed.iv), }, derivedKey, - fromB64(parsed.payload), + fromBase64(parsed.payload), ); return new TextDecoder().decode(decryptedContent); diff --git a/sdk/graphql-transport/CHANGELOG.md b/sdk/graphql-transport/CHANGELOG.md index fa2fd7fc4575b..d1746220f232a 100644 --- a/sdk/graphql-transport/CHANGELOG.md +++ b/sdk/graphql-transport/CHANGELOG.md @@ -1,5 +1,53 @@ # @mysten/graphql-transport +## 0.2.24 + +### Patch Changes + +- Updated dependencies [477d2a4] + - @mysten/sui@1.13.0 + +## 0.2.23 + +### Patch Changes + +- 5299c18: Update the GraphQL transport to account for the removal of recvAddress and the + introduction of affectedAddress. + +## 0.2.22 + +### Patch Changes + +- af39b6a: Update to reflect GraphQL schema renaming TransactionBlockFilter.signAddress to + .sentAddress. +- 4d63e50: Update GraphQL transport layer to accommodate change in schema +- 2cddd9d: Update the GraphQL transport to account for the removal of recvAddress and the + introduction of affectedAddress. + +## 0.2.21 + +### Patch Changes + +- Updated dependencies [5436a90] +- Updated dependencies [5436a90] + - @mysten/sui@1.12.0 + +## 0.2.20 + +### Patch Changes + +- Updated dependencies [489f421] +- Updated dependencies [489f421] + - @mysten/sui@1.11.0 + - @mysten/bcs@1.1.0 + +## 0.2.19 + +### Patch Changes + +- Updated dependencies [830b8d8] + - @mysten/sui@1.10.0 + ## 0.2.18 ### Patch Changes diff --git a/sdk/graphql-transport/codegen.ts b/sdk/graphql-transport/codegen.ts index 885d8c0fa7582..882fc191c5ab0 100644 --- a/sdk/graphql-transport/codegen.ts +++ b/sdk/graphql-transport/codegen.ts @@ -11,7 +11,7 @@ const header = ` const config: CodegenConfig = { overwrite: true, - schema: '../typescript/src/graphql/generated/2024.4/schema.graphql', + schema: '../../crates/sui-graphql-rpc/schema.graphql', documents: ['src/queries/*.graphql'], ignoreNoDocuments: true, generates: { diff --git a/sdk/graphql-transport/package.json b/sdk/graphql-transport/package.json index 4af5e8dca8081..481d20b2dc86e 100644 --- a/sdk/graphql-transport/package.json +++ b/sdk/graphql-transport/package.json @@ -1,6 +1,6 @@ { "name": "@mysten/graphql-transport", - "version": "0.2.18", + "version": "0.2.24", "description": "A GraphQL transport to allow SuiClient to work with RPC 2.0", "license": "Apache-2.0", "author": "Mysten Labs ", diff --git a/sdk/graphql-transport/src/generated/queries.ts b/sdk/graphql-transport/src/generated/queries.ts index 58942e01abe2e..dc0be45904337 100644 --- a/sdk/graphql-transport/src/generated/queries.ts +++ b/sdk/graphql-transport/src/generated/queries.ts @@ -37,7 +37,11 @@ export type Scalars = { * | { String: string } * | { Vector: [MoveData] } * | { Option: MoveData? } - * | { Struct: [{ name: string, value: MoveData }] } + * | { Struct: [{ name: string , value: MoveData }] } + * | { Variant: { + * name: string, + * fields: [{ name: string, value: MoveData }], + * } */ MoveData: { input: any; output: any; } /** @@ -54,6 +58,14 @@ export type Scalars = { * fields: [{ name: string, layout: MoveTypeLayout }], * } * } + * | { enum: [{ + * type: string, + * variants: [{ + * name: string, + * fields: [{ name: string, layout: MoveTypeLayout }], + * }] + * }] + * } */ MoveTypeLayout: { input: any; output: any; } /** @@ -100,6 +112,11 @@ export type Scalars = { OpenMoveTypeSignature: { input: any; output: any; } /** String containing 32B hex-encoded address, with a leading "0x". Leading zeroes can be omitted on input but will always appear in outputs (SuiAddress in output is guaranteed to be 66 characters long). */ SuiAddress: { input: any; output: any; } + /** + * An unsigned integer that can hold values up to 2^53 - 1. This can be treated similarly to `Int`, + * but it is guaranteed to be non-negative, and it may be larger than 2^32 - 1. + */ + UInt53: { input: any; output: any; } }; export type ActiveJwk = { @@ -169,7 +186,26 @@ export type Address = IOwner & { suinsRegistrations: SuinsRegistrationConnection; /** * Similar behavior to the `transactionBlocks` in Query but supporting the additional - * `AddressTransactionBlockRelationship` filter, which defaults to `SIGN`. + * `AddressTransactionBlockRelationship` filter, which defaults to `SENT`. + * + * `scanLimit` restricts the number of candidate transactions scanned when gathering a page of + * results. It is required for queries that apply more than two complex filters (on function, + * kind, sender, recipient, input object, changed object, or ids), and can be at most + * `serviceConfig.maxScanLimit`. + * + * When the scan limit is reached the page will be returned even if it has fewer than `first` + * results when paginating forward (`last` when paginating backwards). If there are more + * transactions to scan, `pageInfo.hasNextPage` (or `pageInfo.hasPreviousPage`) will be set to + * `true`, and `PageInfo.endCursor` (or `PageInfo.startCursor`) will be set to the last + * transaction that was scanned as opposed to the last (or first) transaction in the page. + * + * Requesting the next (or previous) page after this cursor will resume the search, scanning + * the next `scanLimit` many transactions in the direction of pagination, and so on until all + * transactions in the scanning range have been visited. + * + * By default, the scanning range includes all transactions known to GraphQL, but it can be + * restricted by the `after` and `before` cursors, and the `beforeCheckpoint`, + * `afterCheckpoint` and `atCheckpoint` filters. */ transactionBlocks: TransactionBlockConnection; }; @@ -242,6 +278,7 @@ export type AddressTransactionBlocksArgs = { first?: InputMaybe; last?: InputMaybe; relation?: InputMaybe; + scanLimit?: InputMaybe; }; export type AddressConnection = { @@ -273,12 +310,15 @@ export type AddressOwner = { owner?: Maybe; }; -/** The possible relationship types for a transaction block: sign, sent, received, or paid. */ +/** The possible relationship types for a transaction block: sent, or received. */ export enum AddressTransactionBlockRelationship { - /** Transactions that sent objects to this address. */ - Recv = 'RECV', - /** Transactions this address has signed either as a sender or as a sponsor. */ - Sign = 'SIGN' + /** + * Transactions that this address was involved in, either as the sender, sponsor, or as the + * owner of some object that was created, modified or transfered. + */ + Affected = 'AFFECTED', + /** Transactions this address has sent. */ + Sent = 'SENT' } /** System transaction for creating the on-chain state used by zkLogin. */ @@ -291,7 +331,7 @@ export type AuthenticatorStateCreateTransaction = { export type AuthenticatorStateExpireTransaction = { __typename?: 'AuthenticatorStateExpireTransaction'; /** The initial version that the AuthenticatorStateUpdate was shared at. */ - authenticatorObjInitialSharedVersion: Scalars['Int']['output']; + authenticatorObjInitialSharedVersion: Scalars['UInt53']['output']; /** Expire JWKs that have a lower epoch than this. */ minEpoch?: Maybe; }; @@ -300,13 +340,13 @@ export type AuthenticatorStateExpireTransaction = { export type AuthenticatorStateUpdateTransaction = { __typename?: 'AuthenticatorStateUpdateTransaction'; /** The initial version of the authenticator object that it was shared at. */ - authenticatorObjInitialSharedVersion: Scalars['Int']['output']; + authenticatorObjInitialSharedVersion: Scalars['UInt53']['output']; /** Epoch of the authenticator state update transaction. */ epoch?: Maybe; /** Newly active JWKs (JSON Web Keys). */ newActiveJwks: ActiveJwkConnection; /** Consensus round of the authenticator state update. */ - round: Scalars['Int']['output']; + round: Scalars['UInt53']['output']; }; @@ -329,7 +369,7 @@ export type AvailableRange = { export type Balance = { __typename?: 'Balance'; /** How many coins of this type constitute the balance */ - coinObjectCount?: Maybe; + coinObjectCount?: Maybe; /** Coin type for the balance, such as 0x2::sui::SUI */ coinType: MoveType; /** Total balance across all coin objects of the coin type */ @@ -385,6 +425,16 @@ export type BalanceEdge = { node: Balance; }; +export type BridgeCommitteeInitTransaction = { + __typename?: 'BridgeCommitteeInitTransaction'; + bridgeObjInitialSharedVersion: Scalars['UInt53']['output']; +}; + +export type BridgeStateCreateTransaction = { + __typename?: 'BridgeStateCreateTransaction'; + chainId: Scalars['String']['output']; +}; + /** * A system transaction that updates epoch information on-chain (increments the current epoch). * Executed by the system once per epoch, without using gas. Epoch change transactions cannot be @@ -404,7 +454,7 @@ export type ChangeEpochTransaction = { */ nonRefundableStorageFee: Scalars['BigInt']['output']; /** The protocol version in effect in the new epoch. */ - protocolVersion: Scalars['Int']['output']; + protocolVersion: Scalars['UInt53']['output']; /** Time at which the next epoch will start. */ startTimestamp: Scalars['DateTime']['output']; /** The total amount of gas charged for storage during the previous epoch (in MIST). */ @@ -449,7 +499,7 @@ export type Checkpoint = { /** The epoch this checkpoint is part of. */ epoch?: Maybe; /** The total number of transaction blocks in the network by the end of this checkpoint. */ - networkTotalTransactions?: Maybe; + networkTotalTransactions?: Maybe; /** The digest of the checkpoint at the previous sequence number. */ previousCheckpointDigest?: Maybe; /** @@ -462,13 +512,32 @@ export type Checkpoint = { * This checkpoint's position in the total order of finalized checkpoints, agreed upon by * consensus. */ - sequenceNumber: Scalars['Int']['output']; + sequenceNumber: Scalars['UInt53']['output']; /** * The timestamp at which the checkpoint is agreed to have happened according to consensus. * Transactions that access time in this checkpoint will observe this timestamp. */ timestamp: Scalars['DateTime']['output']; - /** Transactions in this checkpoint. */ + /** + * Transactions in this checkpoint. + * + * `scanLimit` restricts the number of candidate transactions scanned when gathering a page of + * results. It is required for queries that apply more than two complex filters (on function, + * kind, sender, recipient, input object, changed object, or ids), and can be at most + * `serviceConfig.maxScanLimit`. + * + * When the scan limit is reached the page will be returned even if it has fewer than `first` + * results when paginating forward (`last` when paginating backwards). If there are more + * transactions to scan, `pageInfo.hasNextPage` (or `pageInfo.hasPreviousPage`) will be set to + * `true`, and `PageInfo.endCursor` (or `PageInfo.startCursor`) will be set to the last + * transaction that was scanned as opposed to the last (or first) transaction in the page. + * + * Requesting the next (or previous) page after this cursor will resume the search, scanning + * the next `scanLimit` many transactions in the direction of pagination, and so on until all + * transactions in the scanning range have been visited. + * + * By default, the scanning range consists of all transactions in this checkpoint. + */ transactionBlocks: TransactionBlockConnection; /** * This is an aggregation of signatures from a quorum of validators for the checkpoint @@ -488,6 +557,7 @@ export type CheckpointTransactionBlocksArgs = { filter?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + scanLimit?: InputMaybe; }; export type CheckpointConnection = { @@ -512,7 +582,7 @@ export type CheckpointEdge = { /** Filter either by the digest, or the sequence number, or neither, to get the latest checkpoint. */ export type CheckpointId = { digest?: InputMaybe; - sequenceNumber?: InputMaybe; + sequenceNumber?: InputMaybe; }; /** Some 0x2::coin::Coin Move object. */ @@ -590,7 +660,28 @@ export type Coin = IMoveObject & IObject & IOwner & { owner?: Maybe; /** The transaction block that created this version of the object. */ previousTransactionBlock?: Maybe; - /** The transaction blocks that sent objects to this object. */ + /** + * The transaction blocks that sent objects to this object. + * + * `scanLimit` restricts the number of candidate transactions scanned when gathering a page of + * results. It is required for queries that apply more than two complex filters (on function, + * kind, sender, recipient, input object, changed object, or ids), and can be at most + * `serviceConfig.maxScanLimit`. + * + * When the scan limit is reached the page will be returned even if it has fewer than `first` + * results when paginating forward (`last` when paginating backwards). If there are more + * transactions to scan, `pageInfo.hasNextPage` (or `pageInfo.hasPreviousPage`) will be set to + * `true`, and `PageInfo.endCursor` (or `PageInfo.startCursor`) will be set to the last + * transaction that was scanned as opposed to the last (or first) transaction in the page. + * + * Requesting the next (or previous) page after this cursor will resume the search, scanning + * the next `scanLimit` many transactions in the direction of pagination, and so on until all + * transactions in the scanning range have been visited. + * + * By default, the scanning range includes all transactions known to GraphQL, but it can be + * restricted by the `after` and `before` cursors, and the `beforeCheckpoint`, + * `afterCheckpoint` and `atCheckpoint` filters. + */ receivedTransactionBlocks: TransactionBlockConnection; /** The `0x3::staking_pool::StakedSui` objects owned by this object. */ stakedSuis: StakedSuiConnection; @@ -614,7 +705,7 @@ export type Coin = IMoveObject & IObject & IOwner & { * manage the associated domain. */ suinsRegistrations: SuinsRegistrationConnection; - version: Scalars['Int']['output']; + version: Scalars['UInt53']['output']; }; @@ -687,6 +778,7 @@ export type CoinReceivedTransactionBlocksArgs = { filter?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + scanLimit?: InputMaybe; }; @@ -812,7 +904,28 @@ export type CoinMetadata = IMoveObject & IObject & IOwner & { owner?: Maybe; /** The transaction block that created this version of the object. */ previousTransactionBlock?: Maybe; - /** The transaction blocks that sent objects to this object. */ + /** + * The transaction blocks that sent objects to this object. + * + * `scanLimit` restricts the number of candidate transactions scanned when gathering a page of + * results. It is required for queries that apply more than two complex filters (on function, + * kind, sender, recipient, input object, changed object, or ids), and can be at most + * `serviceConfig.maxScanLimit`. + * + * When the scan limit is reached the page will be returned even if it has fewer than `first` + * results when paginating forward (`last` when paginating backwards). If there are more + * transactions to scan, `pageInfo.hasNextPage` (or `pageInfo.hasPreviousPage`) will be set to + * `true`, and `PageInfo.endCursor` (or `PageInfo.startCursor`) will be set to the last + * transaction that was scanned as opposed to the last (or first) transaction in the page. + * + * Requesting the next (or previous) page after this cursor will resume the search, scanning + * the next `scanLimit` many transactions in the direction of pagination, and so on until all + * transactions in the scanning range have been visited. + * + * By default, the scanning range includes all transactions known to GraphQL, but it can be + * restricted by the `after` and `before` cursors, and the `beforeCheckpoint`, + * `afterCheckpoint` and `atCheckpoint` filters. + */ receivedTransactionBlocks: TransactionBlockConnection; /** The `0x3::staking_pool::StakedSui` objects owned by this object. */ stakedSuis: StakedSuiConnection; @@ -840,7 +953,7 @@ export type CoinMetadata = IMoveObject & IObject & IOwner & { supply?: Maybe; /** The token's identifying abbreviation. */ symbol?: Maybe; - version: Scalars['Int']['output']; + version: Scalars['UInt53']['output']; }; @@ -913,6 +1026,7 @@ export type CoinMetadataReceivedTransactionBlocksArgs = { filter?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + scanLimit?: InputMaybe; }; @@ -949,7 +1063,7 @@ export type ConsensusCommitPrologueTransaction = { /** Epoch of the commit prologue transaction. */ epoch?: Maybe; /** Consensus round of the commit. */ - round: Scalars['Int']['output']; + round: Scalars['UInt53']['output']; }; export type DependencyConnection = { @@ -1045,9 +1159,10 @@ export type DynamicField = { */ name?: Maybe; /** - * The actual data stored in the dynamic field. - * The returned dynamic field is an object if its return type is MoveObject, - * in which case it is also accessible off-chain via its address. + * The returned dynamic field is an object if its return type is `MoveObject`, + * in which case it is also accessible off-chain via its address. Its contents + * will be from the latest version that is at most equal to its parent object's + * version */ value?: Maybe; }; @@ -1107,7 +1222,7 @@ export type EndOfEpochTransactionTransactionsArgs = { last?: InputMaybe; }; -export type EndOfEpochTransactionKind = AuthenticatorStateCreateTransaction | AuthenticatorStateExpireTransaction | ChangeEpochTransaction | CoinDenyListStateCreateTransaction | RandomnessStateCreateTransaction; +export type EndOfEpochTransactionKind = AuthenticatorStateCreateTransaction | AuthenticatorStateExpireTransaction | BridgeCommitteeInitTransaction | BridgeStateCreateTransaction | ChangeEpochTransaction | CoinDenyListStateCreateTransaction | RandomnessStateCreateTransaction; export type EndOfEpochTransactionKindConnection = { __typename?: 'EndOfEpochTransactionKindConnection'; @@ -1144,7 +1259,7 @@ export type Epoch = { /** The epoch's ending timestamp. */ endTimestamp?: Maybe; /** The epoch's id as a sequence number that starts at 0 and is incremented by one at every epoch change. */ - epochId: Scalars['Int']['output']; + epochId: Scalars['UInt53']['output']; /** The storage fees paid for transactions executed during the epoch. */ fundInflow?: Maybe; /** @@ -1196,9 +1311,9 @@ export type Epoch = { * version changes whenever the fields contained in the system state object (held in a dynamic * field attached to `0x5`) change. */ - systemStateVersion?: Maybe; + systemStateVersion?: Maybe; /** The total number of checkpoints in this epoch. */ - totalCheckpoints?: Maybe; + totalCheckpoints?: Maybe; /** The total amount of gas fees (in MIST) that were paid in this epoch. */ totalGasFees?: Maybe; /** The total MIST rewarded as stake. */ @@ -1206,8 +1321,27 @@ export type Epoch = { /** The amount added to total gas fees to make up the total stake rewards. */ totalStakeSubsidies?: Maybe; /** The total number of transaction blocks in this epoch. */ - totalTransactions?: Maybe; - /** The epoch's corresponding transaction blocks. */ + totalTransactions?: Maybe; + /** + * The epoch's corresponding transaction blocks. + * + * `scanLimit` restricts the number of candidate transactions scanned when gathering a page of + * results. It is required for queries that apply more than two complex filters (on function, + * kind, sender, recipient, input object, changed object, or ids), and can be at most + * `serviceConfig.maxScanLimit`. + * + * When the scan limit is reached the page will be returned even if it has fewer than `first` + * results when paginating forward (`last` when paginating backwards). If there are more + * transactions to scan, `pageInfo.hasNextPage` (or `pageInfo.hasPreviousPage`) will be set to + * `true`, and `PageInfo.endCursor` (or `PageInfo.startCursor`) will be set to the last + * transaction that was scanned as opposed to the last (or first) transaction in the page. + * + * Requesting the next (or previous) page after this cursor will resume the search, scanning + * the next `scanLimit` many transactions in the direction of pagination, and so on until all + * transactions in the scanning range have been visited. + * + * By default, the scanning range consists of all transactions in this epoch. + */ transactionBlocks: TransactionBlockConnection; /** Validator related properties, including the active validators. */ validatorSet?: Maybe; @@ -1246,29 +1380,34 @@ export type EpochTransactionBlocksArgs = { filter?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + scanLimit?: InputMaybe; +}; + +export type EpochConnection = { + __typename?: 'EpochConnection'; + /** A list of edges. */ + edges: Array; + /** A list of nodes. */ + nodes: Array; + /** Information to aid in pagination. */ + pageInfo: PageInfo; +}; + +/** An edge in a connection. */ +export type EpochEdge = { + __typename?: 'EpochEdge'; + /** A cursor for use in pagination */ + cursor: Scalars['String']['output']; + /** The item at the end of the edge */ + node: Epoch; }; export type Event = { __typename?: 'Event'; - /** The BCS representation of this value, Base64 encoded. */ + /** The Base64 encoded BCS serialized bytes of the event. */ bcs: Scalars['Base64']['output']; - /** Structured contents of a Move value. */ - data: Scalars['MoveData']['output']; - /** - * Representation of a Move value in JSON, where: - * - * - Addresses, IDs, and UIDs are represented in canonical form, as JSON strings. - * - Bools are represented by JSON boolean literals. - * - u8, u16, and u32 are represented as JSON numbers. - * - u64, u128, and u256 are represented as JSON strings. - * - Vectors are represented by JSON arrays. - * - Structs are represented by JSON objects. - * - Empty optional values are represented by `null`. - * - * This form is offered as a less verbose convenience in cases where the layout of the type is - * known by the client. - */ - json: Scalars['JSON']['output']; + /** The event's contents as a Move value. */ + contents: MoveValue; /** Address of the sender of the event */ sender?: Maybe
; /** @@ -1281,8 +1420,12 @@ export type Event = { sendingModule?: Maybe; /** UTC timestamp in milliseconds since epoch (1/1/1970) */ timestamp?: Maybe; - /** The value's Move type. */ - type: MoveType; + /** + * The transaction block that emitted this event. This information is only available for + * events from indexed transactions, and not from transactions that have just been executed or + * dry-run. + */ + transactionBlock?: Maybe; }; export type EventConnection = { @@ -1311,6 +1454,8 @@ export type EventFilter = { * PTB and emits an event. * * Modules can be filtered by their package, or package::module. + * We currently do not support filtering by emitting module and event type + * at the same time so if both are provided in one filter, the query will error. */ emittingModule?: InputMaybe; /** @@ -1324,7 +1469,9 @@ export type EventFilter = { * `0x2::coin::Coin<0x2::sui::SUI>`. */ eventType?: InputMaybe; + /** Filter down to events from transactions sent by this address. */ sender?: InputMaybe; + /** Filter down to the events from this transaction (given by its transaction digest). */ transactionDigest?: InputMaybe; }; @@ -1360,6 +1507,8 @@ export enum Feature { Coins = 'COINS', /** Querying an object's dynamic fields. */ DynamicFields = 'DYNAMIC_FIELDS', + /** Named packages service (utilizing dotmove package registry). */ + MoveRegistry = 'MOVE_REGISTRY', /** SuiNS name and reverse name look-up. */ NameService = 'NAME_SERVICE', /** Transaction and Event subscriptions. */ @@ -1449,6 +1598,23 @@ export type GenesisTransactionObjectsArgs = { last?: InputMaybe; }; +/** + * Interface implemented by all GraphQL types that represent a Move datatype (either structs or + * enums). This interface is used to provide a way to access fields that are shared by both + * structs and enums, e.g., the module that the datatype belongs to, the name of the datatype, + * type parameters etc. + */ +export type IMoveDatatype = { + /** The abilities of the datatype. */ + abilities?: Maybe>; + /** The module that the datatype belongs to. */ + module: MoveModule; + /** The name of the datatype. */ + name: Scalars['String']['output']; + /** The type parameters of the datatype. */ + typeParameters?: Maybe>; +}; + /** * This interface is implemented by types that represent a Move object on-chain (A Move value whose * type has `key`). @@ -1531,7 +1697,7 @@ export type IObject = { /** The current status of the object as read from the off-chain store. The possible states are: NOT_INDEXED, the object is loaded from serialized data, such as the contents of a genesis or system package upgrade transaction. LIVE, the version returned is the most recent for the object, and it is not deleted or wrapped at that version. HISTORICAL, the object was referenced at a specific version or checkpoint, so is fetched from historical tables and may not be the latest version of the object. WRAPPED_OR_DELETED, the object is deleted or wrapped and only partial information can be loaded. */ status: ObjectKind; storageRebate?: Maybe; - version: Scalars['Int']['output']; + version: Scalars['UInt53']['output']; }; @@ -1545,6 +1711,7 @@ export type IObjectReceivedTransactionBlocksArgs = { filter?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + scanLimit?: InputMaybe; }; /** @@ -1693,7 +1860,7 @@ export type Linkage = { /** The ID on-chain of the version of the dependency that this package depends on. */ upgradedId: Scalars['SuiAddress']['output']; /** The version of the dependency that this package depends on. */ - version: Scalars['Int']['output']; + version: Scalars['UInt53']['output']; }; /** Create a vector (possibly empty). */ @@ -1743,6 +1910,92 @@ export type MoveCallTransaction = { typeArguments: Array; }; +/** + * The generic representation of a Move datatype (either a struct or an enum) which exposes common + * fields and information (module, name, abilities, type parameters etc.) that is shared across + * them. + */ +export type MoveDatatype = IMoveDatatype & { + __typename?: 'MoveDatatype'; + abilities?: Maybe>; + asMoveEnum?: Maybe; + asMoveStruct?: Maybe; + module: MoveModule; + name: Scalars['String']['output']; + typeParameters?: Maybe>; +}; + +export type MoveDatatypeConnection = { + __typename?: 'MoveDatatypeConnection'; + /** A list of edges. */ + edges: Array; + /** A list of nodes. */ + nodes: Array; + /** Information to aid in pagination. */ + pageInfo: PageInfo; +}; + +/** An edge in a connection. */ +export type MoveDatatypeEdge = { + __typename?: 'MoveDatatypeEdge'; + /** A cursor for use in pagination */ + cursor: Scalars['String']['output']; + /** The item at the end of the edge */ + node: MoveDatatype; +}; + +/** Description of an enum type, defined in a Move module. */ +export type MoveEnum = IMoveDatatype & { + __typename?: 'MoveEnum'; + /** The enum's abilities. */ + abilities?: Maybe>; + /** The module this enum was originally defined in. */ + module: MoveModule; + /** The enum's (unqualified) type name. */ + name: Scalars['String']['output']; + /** + * Constraints on the enum's formal type parameters. Move bytecode does not name type + * parameters, so when they are referenced (e.g. in field types) they are identified by their + * index in this list. + */ + typeParameters?: Maybe>; + /** + * The names and types of the enum's fields. Field types reference type parameters, by their + * index in the defining enum's `typeParameters` list. + */ + variants?: Maybe>; +}; + +export type MoveEnumConnection = { + __typename?: 'MoveEnumConnection'; + /** A list of edges. */ + edges: Array; + /** A list of nodes. */ + nodes: Array; + /** Information to aid in pagination. */ + pageInfo: PageInfo; +}; + +/** An edge in a connection. */ +export type MoveEnumEdge = { + __typename?: 'MoveEnumEdge'; + /** A cursor for use in pagination */ + cursor: Scalars['String']['output']; + /** The item at the end of the edge */ + node: MoveEnum; +}; + +export type MoveEnumVariant = { + __typename?: 'MoveEnumVariant'; + /** + * The names and types of the variant's fields. Field types reference type parameters, by their + * index in the defining enum's `typeParameters` list. + */ + fields?: Maybe>; + /** The name of the variant */ + name: Scalars['String']['output']; +}; + /** Information for a particular field on a Move struct. */ export type MoveField = { __typename?: 'MoveField'; @@ -1812,8 +2065,16 @@ export type MoveModule = { __typename?: 'MoveModule'; /** The Base64 encoded bcs serialization of the module. */ bytes?: Maybe; + /** Look-up the definition of a datatype (struct or enum) defined in this module, by its name. */ + datatype?: Maybe; + /** Iterate through the datatypes (enmums and structs) defined in this module. */ + datatypes?: Maybe; /** Textual representation of the module's bytecode. */ disassembly?: Maybe; + /** Look-up the definition of a enum defined in this module, by its name. */ + enum?: Maybe; + /** Iterate through the enums defined in this module. */ + enums?: Maybe; /** Format version of this module's bytecode. */ fileFormatVersion: Scalars['Int']['output']; /** @@ -1836,6 +2097,48 @@ export type MoveModule = { }; +/** + * Represents a module in Move, a library that defines struct types + * and functions that operate on these types. + */ +export type MoveModuleDatatypeArgs = { + name: Scalars['String']['input']; +}; + + +/** + * Represents a module in Move, a library that defines struct types + * and functions that operate on these types. + */ +export type MoveModuleDatatypesArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +/** + * Represents a module in Move, a library that defines struct types + * and functions that operate on these types. + */ +export type MoveModuleEnumArgs = { + name: Scalars['String']['input']; +}; + + +/** + * Represents a module in Move, a library that defines struct types + * and functions that operate on these types. + */ +export type MoveModuleEnumsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + /** * Represents a module in Move, a library that defines struct types * and functions that operate on these types. @@ -1992,7 +2295,28 @@ export type MoveObject = IMoveObject & IObject & IOwner & { owner?: Maybe; /** The transaction block that created this version of the object. */ previousTransactionBlock?: Maybe; - /** The transaction blocks that sent objects to this object. */ + /** + * The transaction blocks that sent objects to this object. + * + * `scanLimit` restricts the number of candidate transactions scanned when gathering a page of + * results. It is required for queries that apply more than two complex filters (on function, + * kind, sender, recipient, input object, changed object, or ids), and can be at most + * `serviceConfig.maxScanLimit`. + * + * When the scan limit is reached the page will be returned even if it has fewer than `first` + * results when paginating forward (`last` when paginating backwards). If there are more + * transactions to scan, `pageInfo.hasNextPage` (or `pageInfo.hasPreviousPage`) will be set to + * `true`, and `PageInfo.endCursor` (or `PageInfo.startCursor`) will be set to the last + * transaction that was scanned as opposed to the last (or first) transaction in the page. + * + * Requesting the next (or previous) page after this cursor will resume the search, scanning + * the next `scanLimit` many transactions in the direction of pagination, and so on until all + * transactions in the scanning range have been visited. + * + * By default, the scanning range includes all transactions known to GraphQL, but it can be + * restricted by the `after` and `before` cursors, and the `beforeCheckpoint`, + * `afterCheckpoint` and `atCheckpoint` filters. + */ receivedTransactionBlocks: TransactionBlockConnection; /** The `0x3::staking_pool::StakedSui` objects owned by this object. */ stakedSuis: StakedSuiConnection; @@ -2016,7 +2340,7 @@ export type MoveObject = IMoveObject & IObject & IOwner & { * manage the associated domain. */ suinsRegistrations: SuinsRegistrationConnection; - version: Scalars['Int']['output']; + version: Scalars['UInt53']['output']; }; @@ -2116,6 +2440,7 @@ export type MoveObjectReceivedTransactionBlocksArgs = { filter?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + scanLimit?: InputMaybe; }; @@ -2198,6 +2523,11 @@ export type MovePackage = IObject & IOwner & { defaultSuinsName?: Maybe; /** 32-byte hash that identifies the package's contents, encoded as a Base58 string. */ digest?: Maybe; + /** + * Fetch the latest version of this package (the package with the highest `version` that shares + * this packages's original ID) + */ + latestPackage: MovePackage; /** The transitive dependencies of this package. */ linkage?: Maybe>; /** @@ -2224,12 +2554,42 @@ export type MovePackage = IObject & IOwner & { * Packages are always Immutable. */ owner?: Maybe; + /** + * Fetch another version of this package (the package that shares this package's original ID, + * but has the specified `version`). + */ + packageAtVersion?: Maybe; + /** + * Fetch all versions of this package (packages that share this package's original ID), + * optionally bounding the versions exclusively from below with `afterVersion`, or from above + * with `beforeVersion`. + */ + packageVersions: MovePackageConnection; /** The transaction block that published or upgraded this package. */ previousTransactionBlock?: Maybe; /** * The transaction blocks that sent objects to this package. * * Note that objects that have been sent to a package become inaccessible. + * + * `scanLimit` restricts the number of candidate transactions scanned when gathering a page of + * results. It is required for queries that apply more than two complex filters (on function, + * kind, sender, recipient, input object, changed object, or ids), and can be at most + * `serviceConfig.maxScanLimit`. + * + * When the scan limit is reached the page will be returned even if it has fewer than `first` + * results when paginating forward (`last` when paginating backwards). If there are more + * transactions to scan, `pageInfo.hasNextPage` (or `pageInfo.hasPreviousPage`) will be set to + * `true`, and `PageInfo.endCursor` (or `PageInfo.startCursor`) will be set to the last + * transaction that was scanned as opposed to the last (or first) transaction in the page. + * + * Requesting the next (or previous) page after this cursor will resume the search, scanning + * the next `scanLimit` many transactions in the direction of pagination, and so on until all + * transactions in the scanning range have been visited. + * + * By default, the scanning range includes all transactions known to GraphQL, but it can be + * restricted by the `after` and `before` cursors, and the `beforeCheckpoint`, + * `afterCheckpoint` and `atCheckpoint` filters. */ receivedTransactionBlocks: TransactionBlockConnection; /** @@ -2267,7 +2627,7 @@ export type MovePackage = IObject & IOwner & { suinsRegistrations: SuinsRegistrationConnection; /** The (previous) versions of this package that introduced its types. */ typeOrigins?: Maybe>; - version: Scalars['Int']['output']; + version: Scalars['UInt53']['output']; }; @@ -2348,6 +2708,28 @@ export type MovePackageObjectsArgs = { }; +/** + * A MovePackage is a kind of Move object that represents code that has been published on chain. + * It exposes information about its modules, type definitions, functions, and dependencies. + */ +export type MovePackagePackageAtVersionArgs = { + version: Scalars['Int']['input']; +}; + + +/** + * A MovePackage is a kind of Move object that represents code that has been published on chain. + * It exposes information about its modules, type definitions, functions, and dependencies. + */ +export type MovePackagePackageVersionsArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + /** * A MovePackage is a kind of Move object that represents code that has been published on chain. * It exposes information about its modules, type definitions, functions, and dependencies. @@ -2358,6 +2740,7 @@ export type MovePackageReceivedTransactionBlocksArgs = { filter?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + scanLimit?: InputMaybe; }; @@ -2384,6 +2767,20 @@ export type MovePackageSuinsRegistrationsArgs = { last?: InputMaybe; }; +/** Filter for paginating `MovePackage`s that were created within a range of checkpoints. */ +export type MovePackageCheckpointFilter = { + /** + * Fetch packages that were published strictly after this checkpoint. Omitting this fetches + * packages published since genesis. + */ + afterCheckpoint?: InputMaybe; + /** + * Fetch packages that were published strictly before this checkpoint. Omitting this fetches + * packages published up to the latest checkpoint (inclusive). + */ + beforeCheckpoint?: InputMaybe; +}; + export type MovePackageConnection = { __typename?: 'MovePackageConnection'; /** A list of edges. */ @@ -2403,8 +2800,22 @@ export type MovePackageEdge = { node: MovePackage; }; -/** Description of a type, defined in a Move module. */ -export type MoveStruct = { +/** Filter for paginating versions of a given `MovePackage`. */ +export type MovePackageVersionFilter = { + /** + * Fetch versions of this package that are strictly newer than this version. Omitting this + * fetches versions since the original version. + */ + afterVersion?: InputMaybe; + /** + * Fetch versions of this package that are strictly older than this version. Omitting this + * fetches versions up to the latest version (inclusive). + */ + beforeVersion?: InputMaybe; +}; + +/** Description of a struct type, defined in a Move module. */ +export type MoveStruct = IMoveDatatype & { __typename?: 'MoveStruct'; /** Abilities this struct has. */ abilities?: Maybe>; @@ -2453,10 +2864,13 @@ export type MoveStructTypeParameter = { /** Represents concrete types (no type parameters, no references). */ export type MoveType = { __typename?: 'MoveType'; - /** The abilities this concrete type has. */ - abilities: Array; - /** Structured representation of the "shape" of values that match this type. */ - layout: Scalars['MoveTypeLayout']['output']; + /** The abilities this concrete type has. Returns no abilities if the type is invalid. */ + abilities?: Maybe>; + /** + * Structured representation of the "shape" of values that match this type. May return no + * layout if the type is invalid. + */ + layout?: Maybe; /** Flat representation of the type signature, as a displayable string. */ repr: Scalars['String']['output']; /** Structured representation of the type signature. */ @@ -2605,7 +3019,28 @@ export type Object = IObject & IOwner & { owner?: Maybe; /** The transaction block that created this version of the object. */ previousTransactionBlock?: Maybe; - /** The transaction blocks that sent objects to this object. */ + /** + * The transaction blocks that sent objects to this object. + * + * `scanLimit` restricts the number of candidate transactions scanned when gathering a page of + * results. It is required for queries that apply more than two complex filters (on function, + * kind, sender, recipient, input object, changed object, or ids), and can be at most + * `serviceConfig.maxScanLimit`. + * + * When the scan limit is reached the page will be returned even if it has fewer than `first` + * results when paginating forward (`last` when paginating backwards). If there are more + * transactions to scan, `pageInfo.hasNextPage` (or `pageInfo.hasPreviousPage`) will be set to + * `true`, and `PageInfo.endCursor` (or `PageInfo.startCursor`) will be set to the last + * transaction that was scanned as opposed to the last (or first) transaction in the page. + * + * Requesting the next (or previous) page after this cursor will resume the search, scanning + * the next `scanLimit` many transactions in the direction of pagination, and so on until all + * transactions in the scanning range have been visited. + * + * By default, the scanning range includes all transactions known to GraphQL, but it can be + * restricted by the `after` and `before` cursors, and the `beforeCheckpoint`, + * `afterCheckpoint` and `atCheckpoint` filters. + */ receivedTransactionBlocks: TransactionBlockConnection; /** The `0x3::staking_pool::StakedSui` objects owned by this object. */ stakedSuis: StakedSuiConnection; @@ -2629,7 +3064,7 @@ export type Object = IObject & IOwner & { * manage the associated domain. */ suinsRegistrations: SuinsRegistrationConnection; - version: Scalars['Int']['output']; + version: Scalars['UInt53']['output']; }; @@ -2738,6 +3173,7 @@ export type ObjectReceivedTransactionBlocksArgs = { filter?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + scanLimit?: InputMaybe; }; @@ -2835,11 +3271,8 @@ export type ObjectFilter = { /** Filter for live objects by their current owners. */ owner?: InputMaybe; /** - * This field is used to specify the type of objects that should be included in the query - * results. - * - * Objects can be filtered by their type's package, package::module, or their fully qualified - * type name. + * Filter objects by their type's `package`, `package::module`, or their fully qualified type + * name. * * Generic types can be queried by either the generic type name, e.g. `0x2::coin::Coin`, or by * the full type name, such as `0x2::coin::Coin<0x2::sui::SUI>`. @@ -2849,7 +3282,7 @@ export type ObjectFilter = { export type ObjectKey = { objectId: Scalars['SuiAddress']['input']; - version: Scalars['Int']['input']; + version: Scalars['UInt53']['input']; }; export enum ObjectKind { @@ -2859,12 +3292,7 @@ export enum ObjectKind { * The object is loaded from serialized data, such as the contents of a transaction that hasn't * been indexed yet. */ - NotIndexed = 'NOT_INDEXED', - /** - * The object is deleted or wrapped and only partial information can be loaded from the - * indexer. - */ - WrappedOrDeleted = 'WRAPPED_OR_DELETED' + NotIndexed = 'NOT_INDEXED' } /** The object's owner type: Immutable, Shared, Parent, or Address. */ @@ -2876,7 +3304,7 @@ export type ObjectRef = { /** Digest of the object. */ digest: Scalars['String']['input']; /** Version or sequence number of the object. */ - version: Scalars['Int']['input']; + version: Scalars['UInt53']['input']; }; /** @@ -2904,7 +3332,7 @@ export type OwnedOrImmutable = { /** The object at this version. May not be available due to pruning. */ object?: Maybe; /** Version of the object being read. */ - version: Scalars['Int']['output']; + version: Scalars['UInt53']['output']; }; /** @@ -3101,12 +3529,14 @@ export type PageInfo = { /** * If the object's owner is a Parent, this object is part of a dynamic field (it is the value of - * the dynamic field, or the intermediate Field object itself). Also note that if the owner - * is a parent, then it's guaranteed to be an object. + * the dynamic field, or the intermediate Field object itself), and it is owned by another object. + * + * Although its owner is guaranteed to be an object, it is exposed as an Owner, as the parent + * object could be wrapped and therefore not directly accessible. */ export type Parent = { __typename?: 'Parent'; - parent?: Maybe; + parent?: Maybe; }; /** A single transaction, or command, in the programmable transaction block. */ @@ -3207,7 +3637,7 @@ export type ProtocolConfigs = { * The protocol is not required to change on every epoch boundary, so the protocol version * tracks which change to the protocol these configs are from. */ - protocolVersion: Scalars['Int']['output']; + protocolVersion: Scalars['UInt53']['output']; }; @@ -3298,8 +3728,20 @@ export type Query = { dryRunTransactionBlock: DryRunResult; /** Fetch epoch information by ID (defaults to the latest epoch). */ epoch?: Maybe; - /** The events that exist in the network. */ + epochs: EpochConnection; + /** + * Query events that are emitted in the network. + * We currently do not support filtering by emitting module and event type + * at the same time so if both are provided in one filter, the query will error. + */ events: EventConnection; + /** + * The latest version of the package at `address`. + * + * This corresponds to the package with the highest `version` that shares its original ID with + * the package at `address`. + */ + latestPackage?: Maybe; /** * The object corresponding to the given address at the (optionally) given version. * When no version is given, the latest version is returned. @@ -3307,7 +3749,53 @@ export type Query = { object?: Maybe; /** The objects that exist in the network. */ objects: ObjectConnection; + /** + * Look up an Owner by its SuiAddress. + * + * `rootVersion` represents the version of the root object in some nested chain of dynamic + * fields. It allows consistent historical queries for the case of wrapped objects, which don't + * have a version. For example, if querying the dynamic field of a table wrapped in a parent + * object, passing the parent object's version here will ensure we get the dynamic field's + * state at the moment that parent's version was created. + * + * Also, if this Owner is an object itself, `rootVersion` will be used to bound its version + * from above when querying `Owner.asObject`. This can be used, for example, to get the + * contents of a dynamic object field when its parent was at `rootVersion`. + * + * If `rootVersion` is omitted, dynamic fields will be from a consistent snapshot of the Sui + * state at the latest checkpoint known to the GraphQL RPC. Similarly, `Owner.asObject` will + * return the object's version at the latest checkpoint. + */ owner?: Maybe; + /** + * The package corresponding to the given address (at the optionally given version). + * + * When no version is given, the package is loaded directly from the address given. Otherwise, + * the address is translated before loading to point to the package whose original ID matches + * the package at `address`, but whose version is `version`. For non-system packages, this + * might result in a different address than `address` because different versions of a package, + * introduced by upgrades, exist at distinct addresses. + * + * Note that this interpretation of `version` is different from a historical object read (the + * interpretation of `version` for the `object` query). + */ + package?: Maybe; + /** Fetch a package by its name (using dot move service) */ + packageByName?: Maybe; + /** + * Fetch all versions of package at `address` (packages that share this package's original ID), + * optionally bounding the versions exclusively from below with `afterVersion`, or from above + * with `beforeVersion`. + */ + packageVersions: MovePackageConnection; + /** + * The Move packages that exist in the network, optionally filtered to be strictly before + * `beforeCheckpoint` and/or strictly after `afterCheckpoint`. + * + * This query returns all versions of a given user package that appear between the specified + * checkpoints, but only records the latest versions of system packages. + */ + packages: MovePackageConnection; /** * Fetch the protocol config by protocol version (defaults to the latest protocol * version known to the GraphQL service). @@ -3319,13 +3807,36 @@ export type Query = { serviceConfig: ServiceConfig; /** Fetch a transaction block by its transaction digest. */ transactionBlock?: Maybe; - /** The transaction blocks that exist in the network. */ + /** + * The transaction blocks that exist in the network. + * + * `scanLimit` restricts the number of candidate transactions scanned when gathering a page of + * results. It is required for queries that apply more than two complex filters (on function, + * kind, sender, recipient, input object, changed object, or ids), and can be at most + * `serviceConfig.maxScanLimit`. + * + * When the scan limit is reached the page will be returned even if it has fewer than `first` + * results when paginating forward (`last` when paginating backwards). If there are more + * transactions to scan, `pageInfo.hasNextPage` (or `pageInfo.hasPreviousPage`) will be set to + * `true`, and `PageInfo.endCursor` (or `PageInfo.startCursor`) will be set to the last + * transaction that was scanned as opposed to the last (or first) transaction in the page. + * + * Requesting the next (or previous) page after this cursor will resume the search, scanning + * the next `scanLimit` many transactions in the direction of pagination, and so on until all + * transactions in the scanning range have been visited. + * + * By default, the scanning range includes all transactions known to GraphQL, but it can be + * restricted by the `after` and `before` cursors, and the `beforeCheckpoint`, + * `afterCheckpoint` and `atCheckpoint` filters. + */ transactionBlocks: TransactionBlockConnection; /** * Fetch a structured representation of a concrete type, including its layout information. * Fails if the type is malformed. */ type: MoveType; + /** Fetch a type that includes dot move service names in it. */ + typeByName: MoveType; /** * Verify a zkLogin signature based on the provided transaction or personal message * based on current epoch, chain id, and latest JWKs fetched on-chain. If the @@ -3383,7 +3894,15 @@ export type QueryDryRunTransactionBlockArgs = { export type QueryEpochArgs = { - id?: InputMaybe; + id?: InputMaybe; +}; + + +export type QueryEpochsArgs = { + after?: InputMaybe; + before?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; }; @@ -3396,9 +3915,14 @@ export type QueryEventsArgs = { }; +export type QueryLatestPackageArgs = { + address: Scalars['SuiAddress']['input']; +}; + + export type QueryObjectArgs = { address: Scalars['SuiAddress']['input']; - version?: InputMaybe; + version?: InputMaybe; }; @@ -3413,11 +3937,42 @@ export type QueryObjectsArgs = { export type QueryOwnerArgs = { address: Scalars['SuiAddress']['input']; + rootVersion?: InputMaybe; +}; + + +export type QueryPackageArgs = { + address: Scalars['SuiAddress']['input']; + version?: InputMaybe; +}; + + +export type QueryPackageByNameArgs = { + name: Scalars['String']['input']; +}; + + +export type QueryPackageVersionsArgs = { + address: Scalars['SuiAddress']['input']; + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; +}; + + +export type QueryPackagesArgs = { + after?: InputMaybe; + before?: InputMaybe; + filter?: InputMaybe; + first?: InputMaybe; + last?: InputMaybe; }; export type QueryProtocolConfigArgs = { - protocolVersion?: InputMaybe; + protocolVersion?: InputMaybe; }; @@ -3437,6 +3992,7 @@ export type QueryTransactionBlocksArgs = { filter?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + scanLimit?: InputMaybe; }; @@ -3445,6 +4001,11 @@ export type QueryTypeArgs = { }; +export type QueryTypeByNameArgs = { + name: Scalars['String']['input']; +}; + + export type QueryVerifyZkloginSignatureArgs = { author: Scalars['SuiAddress']['input']; bytes: Scalars['Base64']['input']; @@ -3466,9 +4027,9 @@ export type RandomnessStateUpdateTransaction = { /** Updated random bytes, encoded as Base64. */ randomBytes: Scalars['Base64']['output']; /** The initial version the randomness object was shared at. */ - randomnessObjInitialSharedVersion: Scalars['Int']['output']; + randomnessObjInitialSharedVersion: Scalars['UInt53']['output']; /** Randomness round of the update. */ - randomnessRound: Scalars['Int']['output']; + randomnessRound: Scalars['UInt53']['output']; }; /** A Move object that can be received in this transaction. */ @@ -3484,7 +4045,7 @@ export type Receiving = { /** The object at this version. May not be available due to pruning. */ object?: Maybe; /** Version of the object being read. */ - version: Scalars['Int']['output']; + version: Scalars['UInt53']['output']; }; /** The result of another transaction command. */ @@ -3527,7 +4088,7 @@ export type ServiceConfig = { * Maximum estimated cost of a database query used to serve a GraphQL request. This is * measured in the same units that the database uses in EXPLAIN queries. */ - maxDbQueryCost: Scalars['BigInt']['output']; + maxDbQueryCost: Scalars['Int']['output']; /** Maximum nesting allowed in struct fields when calculating the layout of a single Move Type. */ maxMoveValueDepth: Scalars['Int']['output']; /** @@ -3549,8 +4110,25 @@ export type ServiceConfig = { maxQueryDepth: Scalars['Int']['output']; /** The maximum number of nodes (field names) the service will accept in a single query. */ maxQueryNodes: Scalars['Int']['output']; - /** Maximum length of a query payload string. */ + /** + * The maximum bytes allowed for the JSON object in the request body of a GraphQL query, for + * the read part of the query. + * In case of mutations or dryRunTransactionBlocks the txBytes and signatures are not + * included in this limit. + */ maxQueryPayloadSize: Scalars['Int']['output']; + /** Maximum number of candidates to scan when gathering a page of results. */ + maxScanLimit: Scalars['Int']['output']; + /** Maximum number of transaction ids that can be passed to a `TransactionBlockFilter`. */ + maxTransactionIds: Scalars['Int']['output']; + /** + * The maximum bytes allowed for the `txBytes` and `signatures` fields of the GraphQL mutation + * `executeTransactionBlock` node, or for the `txBytes` of a `dryRunTransactionBlock`. + * + * It is the value of the maximum transaction bytes (including the signatures) allowed by the + * protocol, plus the Base64 overhead (roughly 1/3 of the original string). + */ + maxTransactionPayloadSize: Scalars['Int']['output']; /** Maximum nesting allowed in type arguments in Move Types resolved by this service. */ maxTypeArgumentDepth: Scalars['Int']['output']; /** @@ -3563,7 +4141,14 @@ export type ServiceConfig = { * Move Type. */ maxTypeNodes: Scalars['Int']['output']; - /** Maximum time in milliseconds that will be spent to serve one request. */ + /** + * Maximum time in milliseconds spent waiting for a response from fullnode after issuing a + * a transaction to execute. Note that the transaction may still succeed even in the case of a + * timeout. Transactions are idempotent, so a transaction that times out should be resubmitted + * until the network returns a definite response (success or failure, not timeout). + */ + mutationTimeoutMs: Scalars['Int']['output']; + /** Maximum time in milliseconds that will be spent to serve one query request. */ requestTimeoutMs: Scalars['Int']['output']; }; @@ -3579,7 +4164,7 @@ export type ServiceConfigIsEnabledArgs = { */ export type Shared = { __typename?: 'Shared'; - initialSharedVersion: Scalars['Int']['output']; + initialSharedVersion: Scalars['UInt53']['output']; }; /** A Move object that's shared. */ @@ -3587,7 +4172,7 @@ export type SharedInput = { __typename?: 'SharedInput'; address: Scalars['SuiAddress']['output']; /** The version that this this object was shared at. */ - initialSharedVersion: Scalars['Int']['output']; + initialSharedVersion: Scalars['UInt53']['output']; /** * Controls whether the transaction block can reference the shared object as a mutable * reference or by value. This has implications for scheduling: Transactions that just read @@ -3598,6 +4183,15 @@ export type SharedInput = { mutable: Scalars['Boolean']['output']; }; +/** The transaction accpeted a shared object as input, but its execution was cancelled. */ +export type SharedObjectCancelled = { + __typename?: 'SharedObjectCancelled'; + /** ID of the shared object. */ + address: Scalars['SuiAddress']['output']; + /** The assigned shared object version. It is a special version indicating transaction cancellation reason. */ + version: Scalars['UInt53']['output']; +}; + /** * The transaction accepted a shared object as input, but it was deleted before the transaction * executed. @@ -3615,7 +4209,7 @@ export type SharedObjectDelete = { * The version of the shared object that was assigned to this transaction during by consensus, * during sequencing. */ - version: Scalars['Int']['output']; + version: Scalars['UInt53']['output']; }; /** The transaction accepted a shared object as input, but only to read it. */ @@ -3631,7 +4225,7 @@ export type SharedObjectRead = { /** The object at this version. May not be available due to pruning. */ object?: Maybe; /** Version of the object being read. */ - version: Scalars['Int']['output']; + version: Scalars['UInt53']['output']; }; /** @@ -3775,7 +4369,28 @@ export type StakedSui = IMoveObject & IObject & IOwner & { previousTransactionBlock?: Maybe; /** The SUI that was initially staked. */ principal?: Maybe; - /** The transaction blocks that sent objects to this object. */ + /** + * The transaction blocks that sent objects to this object. + * + * `scanLimit` restricts the number of candidate transactions scanned when gathering a page of + * results. It is required for queries that apply more than two complex filters (on function, + * kind, sender, recipient, input object, changed object, or ids), and can be at most + * `serviceConfig.maxScanLimit`. + * + * When the scan limit is reached the page will be returned even if it has fewer than `first` + * results when paginating forward (`last` when paginating backwards). If there are more + * transactions to scan, `pageInfo.hasNextPage` (or `pageInfo.hasPreviousPage`) will be set to + * `true`, and `PageInfo.endCursor` (or `PageInfo.startCursor`) will be set to the last + * transaction that was scanned as opposed to the last (or first) transaction in the page. + * + * Requesting the next (or previous) page after this cursor will resume the search, scanning + * the next `scanLimit` many transactions in the direction of pagination, and so on until all + * transactions in the scanning range have been visited. + * + * By default, the scanning range includes all transactions known to GraphQL, but it can be + * restricted by the `after` and `before` cursors, and the `beforeCheckpoint`, + * `afterCheckpoint` and `atCheckpoint` filters. + */ receivedTransactionBlocks: TransactionBlockConnection; /** The epoch at which this object was requested to join a stake pool. */ requestedEpoch?: Maybe; @@ -3803,7 +4418,7 @@ export type StakedSui = IMoveObject & IObject & IOwner & { * manage the associated domain. */ suinsRegistrations: SuinsRegistrationConnection; - version: Scalars['Int']['output']; + version: Scalars['UInt53']['output']; }; @@ -3876,6 +4491,7 @@ export type StakedSuiReceivedTransactionBlocksArgs = { filter?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + scanLimit?: InputMaybe; }; @@ -4004,7 +4620,28 @@ export type SuinsRegistration = IMoveObject & IObject & IOwner & { owner?: Maybe; /** The transaction block that created this version of the object. */ previousTransactionBlock?: Maybe; - /** The transaction blocks that sent objects to this object. */ + /** + * The transaction blocks that sent objects to this object. + * + * `scanLimit` restricts the number of candidate transactions scanned when gathering a page of + * results. It is required for queries that apply more than two complex filters (on function, + * kind, sender, recipient, input object, changed object, or ids), and can be at most + * `serviceConfig.maxScanLimit`. + * + * When the scan limit is reached the page will be returned even if it has fewer than `first` + * results when paginating forward (`last` when paginating backwards). If there are more + * transactions to scan, `pageInfo.hasNextPage` (or `pageInfo.hasPreviousPage`) will be set to + * `true`, and `PageInfo.endCursor` (or `PageInfo.startCursor`) will be set to the last + * transaction that was scanned as opposed to the last (or first) transaction in the page. + * + * Requesting the next (or previous) page after this cursor will resume the search, scanning + * the next `scanLimit` many transactions in the direction of pagination, and so on until all + * transactions in the scanning range have been visited. + * + * By default, the scanning range includes all transactions known to GraphQL, but it can be + * restricted by the `after` and `before` cursors, and the `beforeCheckpoint`, + * `afterCheckpoint` and `atCheckpoint` filters. + */ receivedTransactionBlocks: TransactionBlockConnection; /** The `0x3::staking_pool::StakedSui` objects owned by this object. */ stakedSuis: StakedSuiConnection; @@ -4028,7 +4665,7 @@ export type SuinsRegistration = IMoveObject & IObject & IOwner & { * manage the associated domain. */ suinsRegistrations: SuinsRegistrationConnection; - version: Scalars['Int']['output']; + version: Scalars['UInt53']['output']; }; @@ -4092,6 +4729,7 @@ export type SuinsRegistrationReceivedTransactionBlocksArgs = { filter?: InputMaybe; first?: InputMaybe; last?: InputMaybe; + scanLimit?: InputMaybe; }; @@ -4141,7 +4779,7 @@ export type SystemParameters = { /** Minimum stake needed to become a new validator. */ minValidatorJoiningStake?: Maybe; /** The epoch at which stake subsidies start being paid out. */ - stakeSubsidyStartEpoch?: Maybe; + stakeSubsidyStartEpoch?: Maybe; /** * The number of epochs that a validator has to recover from having less than * `validatorLowStakeThreshold` stake. @@ -4239,7 +4877,11 @@ export type TransactionBlockEffects = { dependencies: DependencyConnection; /** The epoch this transaction was finalized in. */ epoch?: Maybe; - /** The reason for a transaction failure, if it did fail. */ + /** + * The reason for a transaction failure, if it did fail. + * If the error is a Move abort, the error message will be resolved to a human-readable form if + * possible, otherwise it will fall back to displaying the abort code and location. + */ errors?: Maybe; /** Events emitted by this transaction block. */ events: EventConnection; @@ -4249,7 +4891,7 @@ export type TransactionBlockEffects = { * The latest version of all objects (apart from packages) that have been created or modified * by this transaction, immediately following this transaction. */ - lamportVersion: Scalars['Int']['output']; + lamportVersion: Scalars['UInt53']['output']; /** The effect this transaction had on objects on-chain. */ objectChanges: ObjectChangeConnection; /** Whether the transaction executed successfully or not. */ @@ -4308,16 +4950,43 @@ export type TransactionBlockEffectsUnchangedSharedObjectsArgs = { }; export type TransactionBlockFilter = { - afterCheckpoint?: InputMaybe; - atCheckpoint?: InputMaybe; - beforeCheckpoint?: InputMaybe; + /** + * Limit to transactions that interacted with the given address. The address could be a + * sender, sponsor, or recipient of the transaction. + */ + affectedAddress?: InputMaybe; + /** Limit to transactions that occured strictly after the given checkpoint. */ + afterCheckpoint?: InputMaybe; + /** Limit to transactions in the given checkpoint. */ + atCheckpoint?: InputMaybe; + /** Limit to transaction that occured strictly before the given checkpoint. */ + beforeCheckpoint?: InputMaybe; + /** + * Limit to transactions that output a versioon of this object. NOTE: this input filter has + * been deprecated in favor of `affectedObject` which offers an easier to understand behavor. + * + * This filter will be removed with 1.36.0 (2024-10-14), or at least one release after + * `affectedObject` is introduced, whichever is later. + */ changedObject?: InputMaybe; + /** + * Filter transactions by move function called. Calls can be filtered by the `package`, + * `package::module`, or the `package::module::name` of their function. + */ function?: InputMaybe; + /** + * Limit to transactions that accepted the given object as an input. NOTE: this input filter + * has been deprecated in favor of `affectedObject` which offers an easier to under behavior. + * + * This filter will be removed with 1.36.0 (2024-10-14), or at least one release after + * `affectedObject` is introduced, whichever is later. + */ inputObject?: InputMaybe; /** An input filter selecting for either system or programmable transactions. */ kind?: InputMaybe; - recvAddress?: InputMaybe; - signAddress?: InputMaybe; + /** Limit to transactions that were sent by the given address. */ + sentAddress?: InputMaybe; + /** Select transactions by their digest. */ transactionIds?: InputMaybe>; }; @@ -4364,9 +5033,9 @@ export type TransactionInputEdge = { * to the sender. */ export type TransactionMetadata = { - gasBudget?: InputMaybe; + gasBudget?: InputMaybe; gasObjects?: InputMaybe>; - gasPrice?: InputMaybe; + gasPrice?: InputMaybe; gasSponsor?: InputMaybe; sender?: InputMaybe; }; @@ -4400,7 +5069,7 @@ export type TypeOrigin = { * the shared object as input, consensus must schedule it and pick the version that is actually * used. */ -export type UnchangedSharedObject = SharedObjectDelete | SharedObjectRead; +export type UnchangedSharedObject = SharedObjectCancelled | SharedObjectDelete | SharedObjectRead; export type UnchangedSharedObjectConnection = { __typename?: 'UnchangedSharedObjectConnection'; @@ -4447,7 +5116,7 @@ export type Validator = { * The number of epochs for which this validator has been below the * low stake threshold. */ - atRisk?: Maybe; + atRisk?: Maybe; /** The fee charged by the validator for staking services. */ commissionRate?: Maybe; /** Validator's set of credentials such as public keys, network addresses and others. */ @@ -4461,7 +5130,7 @@ export type Validator = { */ exchangeRates?: Maybe; /** Number of exchange rates in the table. */ - exchangeRatesSize?: Maybe; + exchangeRatesSize?: Maybe; /** * A wrapped object containing the validator's exchange rates. This is a table from epoch * number to `PoolTokenExchangeRate` value. The exchange rate is used to determine the amount @@ -4512,7 +5181,7 @@ export type Validator = { */ stakingPool?: Maybe; /** The epoch at which this pool became active. */ - stakingPoolActivationEpoch?: Maybe; + stakingPoolActivationEpoch?: Maybe; /** The ID of this validator's `0x3::staking_pool::StakingPool`. */ stakingPoolId: Scalars['SuiAddress']['output']; /** The total number of SUI tokens in this pool. */ @@ -4629,7 +5298,7 @@ export type GetCheckpointQueryVariables = Exact<{ }>; -export type GetCheckpointQuery = { __typename?: 'Query', checkpoint?: { __typename?: 'Checkpoint', digest: string, networkTotalTransactions?: number | null, previousCheckpointDigest?: string | null, sequenceNumber: number, timestamp: any, validatorSignatures: any, epoch?: { __typename?: 'Epoch', epochId: number } | null, rollingGasSummary?: { __typename?: 'GasCostSummary', computationCost?: any | null, storageCost?: any | null, storageRebate?: any | null, nonRefundableStorageFee?: any | null } | null, transactionBlocks: { __typename?: 'TransactionBlockConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'TransactionBlock', digest?: string | null }> }, endOfEpoch: { __typename?: 'TransactionBlockConnection', nodes: Array<{ __typename?: 'TransactionBlock', kind?: { __typename: 'AuthenticatorStateUpdateTransaction' } | { __typename: 'ChangeEpochTransaction' } | { __typename: 'ConsensusCommitPrologueTransaction' } | { __typename: 'EndOfEpochTransaction', transactions: { __typename?: 'EndOfEpochTransactionKindConnection', nodes: Array<{ __typename: 'AuthenticatorStateCreateTransaction' } | { __typename: 'AuthenticatorStateExpireTransaction' } | { __typename: 'ChangeEpochTransaction', epoch?: { __typename?: 'Epoch', epochId: number, validatorSet?: { __typename?: 'ValidatorSet', activeValidators: { __typename?: 'ValidatorConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Validator', votingPower?: number | null, credentials?: { __typename?: 'ValidatorCredentials', protocolPubKey?: any | null } | null }> } } | null, protocolConfigs: { __typename?: 'ProtocolConfigs', protocolVersion: number } } | null } | { __typename: 'CoinDenyListStateCreateTransaction' } | { __typename: 'RandomnessStateCreateTransaction' }> } } | { __typename: 'GenesisTransaction' } | { __typename: 'ProgrammableTransactionBlock' } | { __typename: 'RandomnessStateUpdateTransaction' } | null }> } } | null }; +export type GetCheckpointQuery = { __typename?: 'Query', checkpoint?: { __typename?: 'Checkpoint', digest: string, networkTotalTransactions?: any | null, previousCheckpointDigest?: string | null, sequenceNumber: any, timestamp: any, validatorSignatures: any, epoch?: { __typename?: 'Epoch', epochId: any } | null, rollingGasSummary?: { __typename?: 'GasCostSummary', computationCost?: any | null, storageCost?: any | null, storageRebate?: any | null, nonRefundableStorageFee?: any | null } | null, transactionBlocks: { __typename?: 'TransactionBlockConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'TransactionBlock', digest?: string | null }> }, endOfEpoch: { __typename?: 'TransactionBlockConnection', nodes: Array<{ __typename?: 'TransactionBlock', kind?: { __typename: 'AuthenticatorStateUpdateTransaction' } | { __typename: 'ChangeEpochTransaction' } | { __typename: 'ConsensusCommitPrologueTransaction' } | { __typename: 'EndOfEpochTransaction', transactions: { __typename?: 'EndOfEpochTransactionKindConnection', nodes: Array<{ __typename: 'AuthenticatorStateCreateTransaction' } | { __typename: 'AuthenticatorStateExpireTransaction' } | { __typename: 'BridgeCommitteeInitTransaction' } | { __typename: 'BridgeStateCreateTransaction' } | { __typename: 'ChangeEpochTransaction', epoch?: { __typename?: 'Epoch', epochId: any, validatorSet?: { __typename?: 'ValidatorSet', activeValidators: { __typename?: 'ValidatorConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Validator', votingPower?: number | null, credentials?: { __typename?: 'ValidatorCredentials', protocolPubKey?: any | null } | null }> } } | null, protocolConfigs: { __typename?: 'ProtocolConfigs', protocolVersion: any } } | null } | { __typename: 'CoinDenyListStateCreateTransaction' } | { __typename: 'RandomnessStateCreateTransaction' }> } } | { __typename: 'GenesisTransaction' } | { __typename: 'ProgrammableTransactionBlock' } | { __typename: 'RandomnessStateUpdateTransaction' } | null }> } } | null }; export type GetCheckpointsQueryVariables = Exact<{ first?: InputMaybe; @@ -4639,7 +5308,7 @@ export type GetCheckpointsQueryVariables = Exact<{ }>; -export type GetCheckpointsQuery = { __typename?: 'Query', checkpoints: { __typename?: 'CheckpointConnection', pageInfo: { __typename?: 'PageInfo', startCursor?: string | null, endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean }, nodes: Array<{ __typename?: 'Checkpoint', digest: string, networkTotalTransactions?: number | null, previousCheckpointDigest?: string | null, sequenceNumber: number, timestamp: any, validatorSignatures: any, epoch?: { __typename?: 'Epoch', epochId: number } | null, rollingGasSummary?: { __typename?: 'GasCostSummary', computationCost?: any | null, storageCost?: any | null, storageRebate?: any | null, nonRefundableStorageFee?: any | null } | null, transactionBlocks: { __typename?: 'TransactionBlockConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'TransactionBlock', digest?: string | null }> }, endOfEpoch: { __typename?: 'TransactionBlockConnection', nodes: Array<{ __typename?: 'TransactionBlock', kind?: { __typename: 'AuthenticatorStateUpdateTransaction' } | { __typename: 'ChangeEpochTransaction' } | { __typename: 'ConsensusCommitPrologueTransaction' } | { __typename: 'EndOfEpochTransaction', transactions: { __typename?: 'EndOfEpochTransactionKindConnection', nodes: Array<{ __typename: 'AuthenticatorStateCreateTransaction' } | { __typename: 'AuthenticatorStateExpireTransaction' } | { __typename: 'ChangeEpochTransaction', epoch?: { __typename?: 'Epoch', epochId: number, validatorSet?: { __typename?: 'ValidatorSet', activeValidators: { __typename?: 'ValidatorConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Validator', votingPower?: number | null, credentials?: { __typename?: 'ValidatorCredentials', protocolPubKey?: any | null } | null }> } } | null, protocolConfigs: { __typename?: 'ProtocolConfigs', protocolVersion: number } } | null } | { __typename: 'CoinDenyListStateCreateTransaction' } | { __typename: 'RandomnessStateCreateTransaction' }> } } | { __typename: 'GenesisTransaction' } | { __typename: 'ProgrammableTransactionBlock' } | { __typename: 'RandomnessStateUpdateTransaction' } | null }> } }> } }; +export type GetCheckpointsQuery = { __typename?: 'Query', checkpoints: { __typename?: 'CheckpointConnection', pageInfo: { __typename?: 'PageInfo', startCursor?: string | null, endCursor?: string | null, hasNextPage: boolean, hasPreviousPage: boolean }, nodes: Array<{ __typename?: 'Checkpoint', digest: string, networkTotalTransactions?: any | null, previousCheckpointDigest?: string | null, sequenceNumber: any, timestamp: any, validatorSignatures: any, epoch?: { __typename?: 'Epoch', epochId: any } | null, rollingGasSummary?: { __typename?: 'GasCostSummary', computationCost?: any | null, storageCost?: any | null, storageRebate?: any | null, nonRefundableStorageFee?: any | null } | null, transactionBlocks: { __typename?: 'TransactionBlockConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'TransactionBlock', digest?: string | null }> }, endOfEpoch: { __typename?: 'TransactionBlockConnection', nodes: Array<{ __typename?: 'TransactionBlock', kind?: { __typename: 'AuthenticatorStateUpdateTransaction' } | { __typename: 'ChangeEpochTransaction' } | { __typename: 'ConsensusCommitPrologueTransaction' } | { __typename: 'EndOfEpochTransaction', transactions: { __typename?: 'EndOfEpochTransactionKindConnection', nodes: Array<{ __typename: 'AuthenticatorStateCreateTransaction' } | { __typename: 'AuthenticatorStateExpireTransaction' } | { __typename: 'BridgeCommitteeInitTransaction' } | { __typename: 'BridgeStateCreateTransaction' } | { __typename: 'ChangeEpochTransaction', epoch?: { __typename?: 'Epoch', epochId: any, validatorSet?: { __typename?: 'ValidatorSet', activeValidators: { __typename?: 'ValidatorConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Validator', votingPower?: number | null, credentials?: { __typename?: 'ValidatorCredentials', protocolPubKey?: any | null } | null }> } } | null, protocolConfigs: { __typename?: 'ProtocolConfigs', protocolVersion: any } } | null } | { __typename: 'CoinDenyListStateCreateTransaction' } | { __typename: 'RandomnessStateCreateTransaction' }> } } | { __typename: 'GenesisTransaction' } | { __typename: 'ProgrammableTransactionBlock' } | { __typename: 'RandomnessStateUpdateTransaction' } | null }> } }> } }; export type PaginateCheckpointTransactionBlocksQueryVariables = Exact<{ id?: InputMaybe; @@ -4649,7 +5318,7 @@ export type PaginateCheckpointTransactionBlocksQueryVariables = Exact<{ export type PaginateCheckpointTransactionBlocksQuery = { __typename?: 'Query', checkpoint?: { __typename?: 'Checkpoint', transactionBlocks: { __typename?: 'TransactionBlockConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'TransactionBlock', digest?: string | null }> } } | null }; -export type Rpc_Checkpoint_FieldsFragment = { __typename?: 'Checkpoint', digest: string, networkTotalTransactions?: number | null, previousCheckpointDigest?: string | null, sequenceNumber: number, timestamp: any, validatorSignatures: any, epoch?: { __typename?: 'Epoch', epochId: number } | null, rollingGasSummary?: { __typename?: 'GasCostSummary', computationCost?: any | null, storageCost?: any | null, storageRebate?: any | null, nonRefundableStorageFee?: any | null } | null, transactionBlocks: { __typename?: 'TransactionBlockConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'TransactionBlock', digest?: string | null }> }, endOfEpoch: { __typename?: 'TransactionBlockConnection', nodes: Array<{ __typename?: 'TransactionBlock', kind?: { __typename: 'AuthenticatorStateUpdateTransaction' } | { __typename: 'ChangeEpochTransaction' } | { __typename: 'ConsensusCommitPrologueTransaction' } | { __typename: 'EndOfEpochTransaction', transactions: { __typename?: 'EndOfEpochTransactionKindConnection', nodes: Array<{ __typename: 'AuthenticatorStateCreateTransaction' } | { __typename: 'AuthenticatorStateExpireTransaction' } | { __typename: 'ChangeEpochTransaction', epoch?: { __typename?: 'Epoch', epochId: number, validatorSet?: { __typename?: 'ValidatorSet', activeValidators: { __typename?: 'ValidatorConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Validator', votingPower?: number | null, credentials?: { __typename?: 'ValidatorCredentials', protocolPubKey?: any | null } | null }> } } | null, protocolConfigs: { __typename?: 'ProtocolConfigs', protocolVersion: number } } | null } | { __typename: 'CoinDenyListStateCreateTransaction' } | { __typename: 'RandomnessStateCreateTransaction' }> } } | { __typename: 'GenesisTransaction' } | { __typename: 'ProgrammableTransactionBlock' } | { __typename: 'RandomnessStateUpdateTransaction' } | null }> } }; +export type Rpc_Checkpoint_FieldsFragment = { __typename?: 'Checkpoint', digest: string, networkTotalTransactions?: any | null, previousCheckpointDigest?: string | null, sequenceNumber: any, timestamp: any, validatorSignatures: any, epoch?: { __typename?: 'Epoch', epochId: any } | null, rollingGasSummary?: { __typename?: 'GasCostSummary', computationCost?: any | null, storageCost?: any | null, storageRebate?: any | null, nonRefundableStorageFee?: any | null } | null, transactionBlocks: { __typename?: 'TransactionBlockConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'TransactionBlock', digest?: string | null }> }, endOfEpoch: { __typename?: 'TransactionBlockConnection', nodes: Array<{ __typename?: 'TransactionBlock', kind?: { __typename: 'AuthenticatorStateUpdateTransaction' } | { __typename: 'ChangeEpochTransaction' } | { __typename: 'ConsensusCommitPrologueTransaction' } | { __typename: 'EndOfEpochTransaction', transactions: { __typename?: 'EndOfEpochTransactionKindConnection', nodes: Array<{ __typename: 'AuthenticatorStateCreateTransaction' } | { __typename: 'AuthenticatorStateExpireTransaction' } | { __typename: 'BridgeCommitteeInitTransaction' } | { __typename: 'BridgeStateCreateTransaction' } | { __typename: 'ChangeEpochTransaction', epoch?: { __typename?: 'Epoch', epochId: any, validatorSet?: { __typename?: 'ValidatorSet', activeValidators: { __typename?: 'ValidatorConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Validator', votingPower?: number | null, credentials?: { __typename?: 'ValidatorCredentials', protocolPubKey?: any | null } | null }> } } | null, protocolConfigs: { __typename?: 'ProtocolConfigs', protocolVersion: any } } | null } | { __typename: 'CoinDenyListStateCreateTransaction' } | { __typename: 'RandomnessStateCreateTransaction' }> } } | { __typename: 'GenesisTransaction' } | { __typename: 'ProgrammableTransactionBlock' } | { __typename: 'RandomnessStateUpdateTransaction' } | null }> } }; export type DevInspectTransactionBlockQueryVariables = Exact<{ txBytes: Scalars['String']['input']; @@ -4664,7 +5333,7 @@ export type DevInspectTransactionBlockQueryVariables = Exact<{ }>; -export type DevInspectTransactionBlockQuery = { __typename?: 'Query', dryRunTransactionBlock: { __typename?: 'DryRunResult', error?: string | null, results?: Array<{ __typename?: 'DryRunEffect', mutatedReferences?: Array<{ __typename?: 'DryRunMutation', bcs: any, input: { __typename: 'GasCoin' } | { __typename: 'Input', inputIndex: number } | { __typename: 'Result', cmd: number, resultIndex?: number | null }, type: { __typename?: 'MoveType', repr: string } }> | null, returnValues?: Array<{ __typename?: 'DryRunReturn', bcs: any, type: { __typename?: 'MoveType', repr: string } }> | null }> | null, transaction?: { __typename?: 'TransactionBlock', digest?: string | null, signatures?: Array | null, rawTransaction?: any | null, sender?: { __typename?: 'Address', address: any } | null, effects?: { __typename?: 'TransactionBlockEffects', bcs?: any, timestamp?: any | null, events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', json: any, bcs: any, timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, type: { __typename?: 'MoveType', repr: string } }> }, checkpoint?: { __typename?: 'Checkpoint', sequenceNumber: number } | null, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: number, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null } | null } }; +export type DevInspectTransactionBlockQuery = { __typename?: 'Query', dryRunTransactionBlock: { __typename?: 'DryRunResult', error?: string | null, results?: Array<{ __typename?: 'DryRunEffect', mutatedReferences?: Array<{ __typename?: 'DryRunMutation', bcs: any, input: { __typename: 'GasCoin' } | { __typename: 'Input', inputIndex: number } | { __typename: 'Result', cmd: number, resultIndex?: number | null }, type: { __typename?: 'MoveType', repr: string } }> | null, returnValues?: Array<{ __typename?: 'DryRunReturn', bcs: any, type: { __typename?: 'MoveType', repr: string } }> | null }> | null, transaction?: { __typename?: 'TransactionBlock', digest?: string | null, signatures?: Array | null, rawTransaction?: any | null, sender?: { __typename?: 'Address', address: any } | null, effects?: { __typename?: 'TransactionBlockEffects', bcs?: any, timestamp?: any | null, events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, contents: { __typename?: 'MoveValue', json: any, bcs: any, type: { __typename?: 'MoveType', repr: string } } }> }, checkpoint?: { __typename?: 'Checkpoint', sequenceNumber: any } | null, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: any, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null } | null } }; export type DryRunTransactionBlockQueryVariables = Exact<{ txBytes: Scalars['String']['input']; @@ -4678,7 +5347,7 @@ export type DryRunTransactionBlockQueryVariables = Exact<{ }>; -export type DryRunTransactionBlockQuery = { __typename?: 'Query', dryRunTransactionBlock: { __typename?: 'DryRunResult', error?: string | null, transaction?: { __typename?: 'TransactionBlock', digest?: string | null, signatures?: Array | null, rawTransaction?: any | null, sender?: { __typename?: 'Address', address: any } | null, effects?: { __typename?: 'TransactionBlockEffects', bcs?: any, timestamp?: any | null, events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', json: any, bcs: any, timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, type: { __typename?: 'MoveType', repr: string } }> }, checkpoint?: { __typename?: 'Checkpoint', sequenceNumber: number } | null, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: number, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null } | null } }; +export type DryRunTransactionBlockQuery = { __typename?: 'Query', dryRunTransactionBlock: { __typename?: 'DryRunResult', error?: string | null, transaction?: { __typename?: 'TransactionBlock', digest?: string | null, signatures?: Array | null, rawTransaction?: any | null, sender?: { __typename?: 'Address', address: any } | null, effects?: { __typename?: 'TransactionBlockEffects', bcs?: any, timestamp?: any | null, events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, contents: { __typename?: 'MoveValue', json: any, bcs: any, type: { __typename?: 'MoveType', repr: string } } }> }, checkpoint?: { __typename?: 'Checkpoint', sequenceNumber: any } | null, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: any, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null } | null } }; export type ExecuteTransactionBlockMutationVariables = Exact<{ txBytes: Scalars['String']['input']; @@ -4693,7 +5362,7 @@ export type ExecuteTransactionBlockMutationVariables = Exact<{ }>; -export type ExecuteTransactionBlockMutation = { __typename?: 'Mutation', executeTransactionBlock: { __typename?: 'ExecutionResult', errors?: Array | null, effects: { __typename?: 'TransactionBlockEffects', transactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null, signatures?: Array | null, rawTransaction?: any | null, sender?: { __typename?: 'Address', address: any } | null, effects?: { __typename?: 'TransactionBlockEffects', bcs?: any, timestamp?: any | null, events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', json: any, bcs: any, timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, type: { __typename?: 'MoveType', repr: string } }> }, checkpoint?: { __typename?: 'Checkpoint', sequenceNumber: number } | null, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: number, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null } | null } } }; +export type ExecuteTransactionBlockMutation = { __typename?: 'Mutation', executeTransactionBlock: { __typename?: 'ExecutionResult', errors?: Array | null, effects: { __typename?: 'TransactionBlockEffects', transactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null, signatures?: Array | null, rawTransaction?: any | null, sender?: { __typename?: 'Address', address: any } | null, effects?: { __typename?: 'TransactionBlockEffects', bcs?: any, timestamp?: any | null, events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, contents: { __typename?: 'MoveValue', json: any, bcs: any, type: { __typename?: 'MoveType', repr: string } } }> }, checkpoint?: { __typename?: 'Checkpoint', sequenceNumber: any } | null, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: any, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null } | null } } }; export type GetAllBalancesQueryVariables = Exact<{ owner: Scalars['SuiAddress']['input']; @@ -4702,7 +5371,7 @@ export type GetAllBalancesQueryVariables = Exact<{ }>; -export type GetAllBalancesQuery = { __typename?: 'Query', address?: { __typename?: 'Address', balances: { __typename?: 'BalanceConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Balance', coinObjectCount?: number | null, totalBalance?: any | null, coinType: { __typename?: 'MoveType', repr: string } }> } } | null }; +export type GetAllBalancesQuery = { __typename?: 'Query', address?: { __typename?: 'Address', balances: { __typename?: 'BalanceConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Balance', coinObjectCount?: any | null, totalBalance?: any | null, coinType: { __typename?: 'MoveType', repr: string } }> } } | null }; export type GetBalanceQueryVariables = Exact<{ owner: Scalars['SuiAddress']['input']; @@ -4710,7 +5379,7 @@ export type GetBalanceQueryVariables = Exact<{ }>; -export type GetBalanceQuery = { __typename?: 'Query', address?: { __typename?: 'Address', balance?: { __typename?: 'Balance', coinObjectCount?: number | null, totalBalance?: any | null, coinType: { __typename?: 'MoveType', repr: string } } | null } | null }; +export type GetBalanceQuery = { __typename?: 'Query', address?: { __typename?: 'Address', balance?: { __typename?: 'Balance', coinObjectCount?: any | null, totalBalance?: any | null, coinType: { __typename?: 'MoveType', repr: string } } | null } | null }; export type GetChainIdentifierQueryVariables = Exact<{ [key: string]: never; }>; @@ -4732,30 +5401,30 @@ export type GetCoinsQueryVariables = Exact<{ }>; -export type GetCoinsQuery = { __typename?: 'Query', address?: { __typename?: 'Address', address: any, coins: { __typename?: 'CoinConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Coin', coinBalance?: any | null, address: any, version: number, digest?: string | null, contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null, previousTransactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null } | null }> } } | null }; +export type GetCoinsQuery = { __typename?: 'Query', address?: { __typename?: 'Address', address: any, coins: { __typename?: 'CoinConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Coin', coinBalance?: any | null, address: any, version: any, digest?: string | null, contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null, previousTransactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null } | null }> } } | null }; export type GetCommitteeInfoQueryVariables = Exact<{ - epochId?: InputMaybe; + epochId?: InputMaybe; after?: InputMaybe; }>; -export type GetCommitteeInfoQuery = { __typename?: 'Query', epoch?: { __typename?: 'Epoch', epochId: number, validatorSet?: { __typename?: 'ValidatorSet', activeValidators: { __typename?: 'ValidatorConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Validator', votingPower?: number | null, credentials?: { __typename?: 'ValidatorCredentials', protocolPubKey?: any | null } | null }> } } | null } | null }; +export type GetCommitteeInfoQuery = { __typename?: 'Query', epoch?: { __typename?: 'Epoch', epochId: any, validatorSet?: { __typename?: 'ValidatorSet', activeValidators: { __typename?: 'ValidatorConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Validator', votingPower?: number | null, credentials?: { __typename?: 'ValidatorCredentials', protocolPubKey?: any | null } | null }> } } | null } | null }; export type GetCurrentEpochQueryVariables = Exact<{ [key: string]: never; }>; -export type GetCurrentEpochQuery = { __typename?: 'Query', epoch?: { __typename?: 'Epoch', epochId: number, totalTransactions?: number | null, startTimestamp: any, endTimestamp?: any | null, referenceGasPrice?: any | null, validatorSet?: { __typename?: 'ValidatorSet', activeValidators: { __typename?: 'ValidatorConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Validator', atRisk?: number | null, commissionRate?: number | null, exchangeRatesSize?: number | null, description?: string | null, gasPrice?: any | null, imageUrl?: string | null, name?: string | null, nextEpochCommissionRate?: number | null, nextEpochGasPrice?: any | null, nextEpochStake?: any | null, pendingPoolTokenWithdraw?: any | null, pendingStake?: any | null, pendingTotalSuiWithdraw?: any | null, poolTokenBalance?: any | null, projectUrl?: string | null, rewardsPool?: any | null, stakingPoolActivationEpoch?: number | null, stakingPoolSuiBalance?: any | null, votingPower?: number | null, exchangeRates?: { __typename?: 'MoveObject', address: any, contents?: { __typename?: 'MoveValue', json: any } | null } | null, credentials?: { __typename?: 'ValidatorCredentials', netAddress?: string | null, networkPubKey?: any | null, p2PAddress?: string | null, primaryAddress?: string | null, workerPubKey?: any | null, workerAddress?: string | null, proofOfPossession?: any | null, protocolPubKey?: any | null } | null, nextEpochCredentials?: { __typename?: 'ValidatorCredentials', netAddress?: string | null, networkPubKey?: any | null, p2PAddress?: string | null, primaryAddress?: string | null, workerPubKey?: any | null, workerAddress?: string | null, proofOfPossession?: any | null, protocolPubKey?: any | null } | null, operationCap?: { __typename?: 'MoveObject', address: any } | null, stakingPool?: { __typename?: 'MoveObject', address: any } | null, address: { __typename?: 'Address', address: any } }> } } | null, firstCheckpoint: { __typename?: 'CheckpointConnection', nodes: Array<{ __typename?: 'Checkpoint', sequenceNumber: number }> } } | null }; +export type GetCurrentEpochQuery = { __typename?: 'Query', epoch?: { __typename?: 'Epoch', epochId: any, totalTransactions?: any | null, startTimestamp: any, endTimestamp?: any | null, referenceGasPrice?: any | null, validatorSet?: { __typename?: 'ValidatorSet', activeValidators: { __typename?: 'ValidatorConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Validator', atRisk?: any | null, commissionRate?: number | null, exchangeRatesSize?: any | null, description?: string | null, gasPrice?: any | null, imageUrl?: string | null, name?: string | null, nextEpochCommissionRate?: number | null, nextEpochGasPrice?: any | null, nextEpochStake?: any | null, pendingPoolTokenWithdraw?: any | null, pendingStake?: any | null, pendingTotalSuiWithdraw?: any | null, poolTokenBalance?: any | null, projectUrl?: string | null, rewardsPool?: any | null, stakingPoolActivationEpoch?: any | null, stakingPoolSuiBalance?: any | null, votingPower?: number | null, exchangeRates?: { __typename?: 'MoveObject', address: any, contents?: { __typename?: 'MoveValue', json: any } | null } | null, credentials?: { __typename?: 'ValidatorCredentials', netAddress?: string | null, networkPubKey?: any | null, p2PAddress?: string | null, primaryAddress?: string | null, workerPubKey?: any | null, workerAddress?: string | null, proofOfPossession?: any | null, protocolPubKey?: any | null } | null, nextEpochCredentials?: { __typename?: 'ValidatorCredentials', netAddress?: string | null, networkPubKey?: any | null, p2PAddress?: string | null, primaryAddress?: string | null, workerPubKey?: any | null, workerAddress?: string | null, proofOfPossession?: any | null, protocolPubKey?: any | null } | null, operationCap?: { __typename?: 'MoveObject', address: any } | null, stakingPool?: { __typename?: 'MoveObject', address: any } | null, address: { __typename?: 'Address', address: any } }> } } | null, firstCheckpoint: { __typename?: 'CheckpointConnection', nodes: Array<{ __typename?: 'Checkpoint', sequenceNumber: any }> } } | null }; export type PaginateEpochValidatorsQueryVariables = Exact<{ - id: Scalars['Int']['input']; + id: Scalars['UInt53']['input']; after?: InputMaybe; }>; -export type PaginateEpochValidatorsQuery = { __typename?: 'Query', epoch?: { __typename?: 'Epoch', validatorSet?: { __typename?: 'ValidatorSet', activeValidators: { __typename?: 'ValidatorConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Validator', atRisk?: number | null, commissionRate?: number | null, exchangeRatesSize?: number | null, description?: string | null, gasPrice?: any | null, imageUrl?: string | null, name?: string | null, nextEpochCommissionRate?: number | null, nextEpochGasPrice?: any | null, nextEpochStake?: any | null, pendingPoolTokenWithdraw?: any | null, pendingStake?: any | null, pendingTotalSuiWithdraw?: any | null, poolTokenBalance?: any | null, projectUrl?: string | null, rewardsPool?: any | null, stakingPoolActivationEpoch?: number | null, stakingPoolSuiBalance?: any | null, votingPower?: number | null, exchangeRates?: { __typename?: 'MoveObject', address: any, contents?: { __typename?: 'MoveValue', json: any } | null } | null, credentials?: { __typename?: 'ValidatorCredentials', netAddress?: string | null, networkPubKey?: any | null, p2PAddress?: string | null, primaryAddress?: string | null, workerPubKey?: any | null, workerAddress?: string | null, proofOfPossession?: any | null, protocolPubKey?: any | null } | null, nextEpochCredentials?: { __typename?: 'ValidatorCredentials', netAddress?: string | null, networkPubKey?: any | null, p2PAddress?: string | null, primaryAddress?: string | null, workerPubKey?: any | null, workerAddress?: string | null, proofOfPossession?: any | null, protocolPubKey?: any | null } | null, operationCap?: { __typename?: 'MoveObject', address: any } | null, stakingPool?: { __typename?: 'MoveObject', address: any } | null, address: { __typename?: 'Address', address: any } }> } } | null } | null }; +export type PaginateEpochValidatorsQuery = { __typename?: 'Query', epoch?: { __typename?: 'Epoch', validatorSet?: { __typename?: 'ValidatorSet', activeValidators: { __typename?: 'ValidatorConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Validator', atRisk?: any | null, commissionRate?: number | null, exchangeRatesSize?: any | null, description?: string | null, gasPrice?: any | null, imageUrl?: string | null, name?: string | null, nextEpochCommissionRate?: number | null, nextEpochGasPrice?: any | null, nextEpochStake?: any | null, pendingPoolTokenWithdraw?: any | null, pendingStake?: any | null, pendingTotalSuiWithdraw?: any | null, poolTokenBalance?: any | null, projectUrl?: string | null, rewardsPool?: any | null, stakingPoolActivationEpoch?: any | null, stakingPoolSuiBalance?: any | null, votingPower?: number | null, exchangeRates?: { __typename?: 'MoveObject', address: any, contents?: { __typename?: 'MoveValue', json: any } | null } | null, credentials?: { __typename?: 'ValidatorCredentials', netAddress?: string | null, networkPubKey?: any | null, p2PAddress?: string | null, primaryAddress?: string | null, workerPubKey?: any | null, workerAddress?: string | null, proofOfPossession?: any | null, protocolPubKey?: any | null } | null, nextEpochCredentials?: { __typename?: 'ValidatorCredentials', netAddress?: string | null, networkPubKey?: any | null, p2PAddress?: string | null, primaryAddress?: string | null, workerPubKey?: any | null, workerAddress?: string | null, proofOfPossession?: any | null, protocolPubKey?: any | null } | null, operationCap?: { __typename?: 'MoveObject', address: any } | null, stakingPool?: { __typename?: 'MoveObject', address: any } | null, address: { __typename?: 'Address', address: any } }> } } | null } | null }; -export type Rpc_Validator_FieldsFragment = { __typename?: 'Validator', atRisk?: number | null, commissionRate?: number | null, exchangeRatesSize?: number | null, description?: string | null, gasPrice?: any | null, imageUrl?: string | null, name?: string | null, nextEpochCommissionRate?: number | null, nextEpochGasPrice?: any | null, nextEpochStake?: any | null, pendingPoolTokenWithdraw?: any | null, pendingStake?: any | null, pendingTotalSuiWithdraw?: any | null, poolTokenBalance?: any | null, projectUrl?: string | null, rewardsPool?: any | null, stakingPoolActivationEpoch?: number | null, stakingPoolSuiBalance?: any | null, votingPower?: number | null, exchangeRates?: { __typename?: 'MoveObject', address: any, contents?: { __typename?: 'MoveValue', json: any } | null } | null, credentials?: { __typename?: 'ValidatorCredentials', netAddress?: string | null, networkPubKey?: any | null, p2PAddress?: string | null, primaryAddress?: string | null, workerPubKey?: any | null, workerAddress?: string | null, proofOfPossession?: any | null, protocolPubKey?: any | null } | null, nextEpochCredentials?: { __typename?: 'ValidatorCredentials', netAddress?: string | null, networkPubKey?: any | null, p2PAddress?: string | null, primaryAddress?: string | null, workerPubKey?: any | null, workerAddress?: string | null, proofOfPossession?: any | null, protocolPubKey?: any | null } | null, operationCap?: { __typename?: 'MoveObject', address: any } | null, stakingPool?: { __typename?: 'MoveObject', address: any } | null, address: { __typename?: 'Address', address: any } }; +export type Rpc_Validator_FieldsFragment = { __typename?: 'Validator', atRisk?: any | null, commissionRate?: number | null, exchangeRatesSize?: any | null, description?: string | null, gasPrice?: any | null, imageUrl?: string | null, name?: string | null, nextEpochCommissionRate?: number | null, nextEpochGasPrice?: any | null, nextEpochStake?: any | null, pendingPoolTokenWithdraw?: any | null, pendingStake?: any | null, pendingTotalSuiWithdraw?: any | null, poolTokenBalance?: any | null, projectUrl?: string | null, rewardsPool?: any | null, stakingPoolActivationEpoch?: any | null, stakingPoolSuiBalance?: any | null, votingPower?: number | null, exchangeRates?: { __typename?: 'MoveObject', address: any, contents?: { __typename?: 'MoveValue', json: any } | null } | null, credentials?: { __typename?: 'ValidatorCredentials', netAddress?: string | null, networkPubKey?: any | null, p2PAddress?: string | null, primaryAddress?: string | null, workerPubKey?: any | null, workerAddress?: string | null, proofOfPossession?: any | null, protocolPubKey?: any | null } | null, nextEpochCredentials?: { __typename?: 'ValidatorCredentials', netAddress?: string | null, networkPubKey?: any | null, p2PAddress?: string | null, primaryAddress?: string | null, workerPubKey?: any | null, workerAddress?: string | null, proofOfPossession?: any | null, protocolPubKey?: any | null } | null, operationCap?: { __typename?: 'MoveObject', address: any } | null, stakingPool?: { __typename?: 'MoveObject', address: any } | null, address: { __typename?: 'Address', address: any } }; export type Rpc_Credential_FieldsFragment = { __typename?: 'ValidatorCredentials', netAddress?: string | null, networkPubKey?: any | null, p2PAddress?: string | null, primaryAddress?: string | null, workerPubKey?: any | null, workerAddress?: string | null, proofOfPossession?: any | null, protocolPubKey?: any | null }; @@ -4764,7 +5433,7 @@ export type GetTypeLayoutQueryVariables = Exact<{ }>; -export type GetTypeLayoutQuery = { __typename?: 'Query', type: { __typename?: 'MoveType', layout: any } }; +export type GetTypeLayoutQuery = { __typename?: 'Query', type: { __typename?: 'MoveType', layout?: any | null } }; export type GetDynamicFieldObjectQueryVariables = Exact<{ parentId: Scalars['SuiAddress']['input']; @@ -4772,7 +5441,7 @@ export type GetDynamicFieldObjectQueryVariables = Exact<{ }>; -export type GetDynamicFieldObjectQuery = { __typename?: 'Query', owner?: { __typename?: 'Owner', dynamicObjectField?: { __typename?: 'DynamicField', value?: { __typename: 'MoveObject', owner?: { __typename: 'AddressOwner' } | { __typename: 'Immutable' } | { __typename: 'Parent', parent?: { __typename?: 'Object', address: any, digest?: string | null, version: number, storageRebate?: any | null, owner?: { __typename: 'AddressOwner' } | { __typename: 'Immutable' } | { __typename: 'Parent', parent?: { __typename?: 'Object', address: any } | null } | { __typename: 'Shared' } | null, previousTransactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null } | null, asMoveObject?: { __typename?: 'MoveObject', hasPublicTransfer: boolean, contents?: { __typename?: 'MoveValue', data: any, type: { __typename?: 'MoveType', repr: string, layout: any } } | null } | null } | null } | { __typename: 'Shared' } | null } | { __typename: 'MoveValue' } | null } | null } | null }; +export type GetDynamicFieldObjectQuery = { __typename?: 'Query', owner?: { __typename?: 'Owner', dynamicObjectField?: { __typename?: 'DynamicField', value?: { __typename: 'MoveObject', owner?: { __typename: 'AddressOwner' } | { __typename: 'Immutable' } | { __typename: 'Parent', parent?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any, digest?: string | null, version: any, storageRebate?: any | null, owner?: { __typename: 'AddressOwner' } | { __typename: 'Immutable' } | { __typename: 'Parent', parent?: { __typename?: 'Owner', address: any } | null } | { __typename: 'Shared' } | null, previousTransactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null } | null, asMoveObject?: { __typename?: 'MoveObject', hasPublicTransfer: boolean, contents?: { __typename?: 'MoveValue', data: any, type: { __typename?: 'MoveType', repr: string, layout?: any | null } } | null } | null } | null } | null } | { __typename: 'Shared' } | null } | { __typename: 'MoveValue' } | null } | null } | null }; export type GetDynamicFieldsQueryVariables = Exact<{ parentId: Scalars['SuiAddress']['input']; @@ -4781,17 +5450,17 @@ export type GetDynamicFieldsQueryVariables = Exact<{ }>; -export type GetDynamicFieldsQuery = { __typename?: 'Query', owner?: { __typename?: 'Owner', dynamicFields: { __typename?: 'DynamicFieldConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'DynamicField', name?: { __typename?: 'MoveValue', bcs: any, json: any, type: { __typename?: 'MoveType', layout: any, repr: string } } | null, value?: { __typename: 'MoveObject', address: any, digest?: string | null, version: number, contents?: { __typename?: 'MoveValue', json: any, type: { __typename?: 'MoveType', repr: string } } | null } | { __typename: 'MoveValue', json: any, type: { __typename?: 'MoveType', repr: string } } | null }> } } | null }; +export type GetDynamicFieldsQuery = { __typename?: 'Query', owner?: { __typename?: 'Owner', dynamicFields: { __typename?: 'DynamicFieldConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'DynamicField', name?: { __typename?: 'MoveValue', bcs: any, json: any, type: { __typename?: 'MoveType', layout?: any | null, repr: string } } | null, value?: { __typename: 'MoveObject', address: any, digest?: string | null, version: any, contents?: { __typename?: 'MoveValue', json: any, type: { __typename?: 'MoveType', repr: string } } | null } | { __typename: 'MoveValue', json: any, type: { __typename?: 'MoveType', repr: string } } | null }> } } | null }; export type GetLatestCheckpointSequenceNumberQueryVariables = Exact<{ [key: string]: never; }>; -export type GetLatestCheckpointSequenceNumberQuery = { __typename?: 'Query', checkpoint?: { __typename?: 'Checkpoint', sequenceNumber: number } | null }; +export type GetLatestCheckpointSequenceNumberQuery = { __typename?: 'Query', checkpoint?: { __typename?: 'Checkpoint', sequenceNumber: any } | null }; export type GetLatestSuiSystemStateQueryVariables = Exact<{ [key: string]: never; }>; -export type GetLatestSuiSystemStateQuery = { __typename?: 'Query', epoch?: { __typename?: 'Epoch', epochId: number, startTimestamp: any, endTimestamp?: any | null, referenceGasPrice?: any | null, systemStateVersion?: number | null, safeMode?: { __typename?: 'SafeMode', enabled?: boolean | null, gasSummary?: { __typename?: 'GasCostSummary', computationCost?: any | null, nonRefundableStorageFee?: any | null, storageCost?: any | null, storageRebate?: any | null } | null } | null, systemStakeSubsidy?: { __typename?: 'StakeSubsidy', balance?: any | null, currentDistributionAmount?: any | null, decreaseRate?: number | null, distributionCounter?: number | null, periodLength?: number | null } | null, storageFund?: { __typename?: 'StorageFund', nonRefundableBalance?: any | null, totalObjectStorageRebates?: any | null } | null, systemParameters?: { __typename?: 'SystemParameters', minValidatorCount?: number | null, maxValidatorCount?: number | null, minValidatorJoiningStake?: any | null, durationMs?: any | null, validatorLowStakeThreshold?: any | null, validatorLowStakeGracePeriod?: any | null, validatorVeryLowStakeThreshold?: any | null, stakeSubsidyStartEpoch?: number | null } | null, protocolConfigs: { __typename?: 'ProtocolConfigs', protocolVersion: number }, validatorSet?: { __typename?: 'ValidatorSet', inactivePoolsSize?: number | null, pendingActiveValidatorsSize?: number | null, stakingPoolMappingsSize?: number | null, validatorCandidatesSize?: number | null, pendingRemovals?: Array | null, totalStake?: any | null, stakingPoolMappingsId?: any | null, pendingActiveValidatorsId?: any | null, validatorCandidatesId?: any | null, inactivePoolsId?: any | null, activeValidators: { __typename?: 'ValidatorConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Validator', atRisk?: number | null, commissionRate?: number | null, exchangeRatesSize?: number | null, description?: string | null, gasPrice?: any | null, imageUrl?: string | null, name?: string | null, nextEpochCommissionRate?: number | null, nextEpochGasPrice?: any | null, nextEpochStake?: any | null, pendingPoolTokenWithdraw?: any | null, pendingStake?: any | null, pendingTotalSuiWithdraw?: any | null, poolTokenBalance?: any | null, projectUrl?: string | null, rewardsPool?: any | null, stakingPoolActivationEpoch?: number | null, stakingPoolSuiBalance?: any | null, votingPower?: number | null, exchangeRates?: { __typename?: 'MoveObject', address: any, contents?: { __typename?: 'MoveValue', json: any } | null } | null, credentials?: { __typename?: 'ValidatorCredentials', netAddress?: string | null, networkPubKey?: any | null, p2PAddress?: string | null, primaryAddress?: string | null, workerPubKey?: any | null, workerAddress?: string | null, proofOfPossession?: any | null, protocolPubKey?: any | null } | null, nextEpochCredentials?: { __typename?: 'ValidatorCredentials', netAddress?: string | null, networkPubKey?: any | null, p2PAddress?: string | null, primaryAddress?: string | null, workerPubKey?: any | null, workerAddress?: string | null, proofOfPossession?: any | null, protocolPubKey?: any | null } | null, operationCap?: { __typename?: 'MoveObject', address: any } | null, stakingPool?: { __typename?: 'MoveObject', address: any } | null, address: { __typename?: 'Address', address: any } }> } } | null } | null }; +export type GetLatestSuiSystemStateQuery = { __typename?: 'Query', epoch?: { __typename?: 'Epoch', epochId: any, startTimestamp: any, endTimestamp?: any | null, referenceGasPrice?: any | null, systemStateVersion?: any | null, safeMode?: { __typename?: 'SafeMode', enabled?: boolean | null, gasSummary?: { __typename?: 'GasCostSummary', computationCost?: any | null, nonRefundableStorageFee?: any | null, storageCost?: any | null, storageRebate?: any | null } | null } | null, systemStakeSubsidy?: { __typename?: 'StakeSubsidy', balance?: any | null, currentDistributionAmount?: any | null, decreaseRate?: number | null, distributionCounter?: number | null, periodLength?: number | null } | null, storageFund?: { __typename?: 'StorageFund', nonRefundableBalance?: any | null, totalObjectStorageRebates?: any | null } | null, systemParameters?: { __typename?: 'SystemParameters', minValidatorCount?: number | null, maxValidatorCount?: number | null, minValidatorJoiningStake?: any | null, durationMs?: any | null, validatorLowStakeThreshold?: any | null, validatorLowStakeGracePeriod?: any | null, validatorVeryLowStakeThreshold?: any | null, stakeSubsidyStartEpoch?: any | null } | null, protocolConfigs: { __typename?: 'ProtocolConfigs', protocolVersion: any }, validatorSet?: { __typename?: 'ValidatorSet', inactivePoolsSize?: number | null, pendingActiveValidatorsSize?: number | null, stakingPoolMappingsSize?: number | null, validatorCandidatesSize?: number | null, pendingRemovals?: Array | null, totalStake?: any | null, stakingPoolMappingsId?: any | null, pendingActiveValidatorsId?: any | null, validatorCandidatesId?: any | null, inactivePoolsId?: any | null, activeValidators: { __typename?: 'ValidatorConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Validator', atRisk?: any | null, commissionRate?: number | null, exchangeRatesSize?: any | null, description?: string | null, gasPrice?: any | null, imageUrl?: string | null, name?: string | null, nextEpochCommissionRate?: number | null, nextEpochGasPrice?: any | null, nextEpochStake?: any | null, pendingPoolTokenWithdraw?: any | null, pendingStake?: any | null, pendingTotalSuiWithdraw?: any | null, poolTokenBalance?: any | null, projectUrl?: string | null, rewardsPool?: any | null, stakingPoolActivationEpoch?: any | null, stakingPoolSuiBalance?: any | null, votingPower?: number | null, exchangeRates?: { __typename?: 'MoveObject', address: any, contents?: { __typename?: 'MoveValue', json: any } | null } | null, credentials?: { __typename?: 'ValidatorCredentials', netAddress?: string | null, networkPubKey?: any | null, p2PAddress?: string | null, primaryAddress?: string | null, workerPubKey?: any | null, workerAddress?: string | null, proofOfPossession?: any | null, protocolPubKey?: any | null } | null, nextEpochCredentials?: { __typename?: 'ValidatorCredentials', netAddress?: string | null, networkPubKey?: any | null, p2PAddress?: string | null, primaryAddress?: string | null, workerPubKey?: any | null, workerAddress?: string | null, proofOfPossession?: any | null, protocolPubKey?: any | null } | null, operationCap?: { __typename?: 'MoveObject', address: any } | null, stakingPool?: { __typename?: 'MoveObject', address: any } | null, address: { __typename?: 'Address', address: any } }> } } | null } | null }; export type GetMoveFunctionArgTypesQueryVariables = Exact<{ packageId: Scalars['SuiAddress']['input']; @@ -4857,11 +5526,11 @@ export type GetNormalizedMoveStructQuery = { __typename?: 'Query', object?: { __ export type Rpc_Move_Struct_FieldsFragment = { __typename?: 'MoveStruct', name: string, abilities?: Array | null, fields?: Array<{ __typename?: 'MoveField', name: string, type?: { __typename?: 'OpenMoveType', signature: any } | null }> | null, typeParameters?: Array<{ __typename?: 'MoveStructTypeParameter', isPhantom: boolean, constraints: Array }> | null }; export type GetProtocolConfigQueryVariables = Exact<{ - protocolVersion?: InputMaybe; + protocolVersion?: InputMaybe; }>; -export type GetProtocolConfigQuery = { __typename?: 'Query', protocolConfig: { __typename?: 'ProtocolConfigs', protocolVersion: number, configs: Array<{ __typename?: 'ProtocolConfigAttr', key: string, value?: string | null }>, featureFlags: Array<{ __typename?: 'ProtocolConfigFeatureFlag', key: string, value: boolean }> } }; +export type GetProtocolConfigQuery = { __typename?: 'Query', protocolConfig: { __typename?: 'ProtocolConfigs', protocolVersion: any, configs: Array<{ __typename?: 'ProtocolConfigAttr', key: string, value?: string | null }>, featureFlags: Array<{ __typename?: 'ProtocolConfigFeatureFlag', key: string, value: boolean }> } }; export type GetReferenceGasPriceQueryVariables = Exact<{ [key: string]: never; }>; @@ -4878,12 +5547,12 @@ export type GetTotalSupplyQuery = { __typename?: 'Query', coinMetadata?: { __typ export type GetTotalTransactionBlocksQueryVariables = Exact<{ [key: string]: never; }>; -export type GetTotalTransactionBlocksQuery = { __typename?: 'Query', checkpoint?: { __typename?: 'Checkpoint', networkTotalTransactions?: number | null } | null }; +export type GetTotalTransactionBlocksQuery = { __typename?: 'Query', checkpoint?: { __typename?: 'Checkpoint', networkTotalTransactions?: any | null } | null }; export type GetValidatorsApyQueryVariables = Exact<{ [key: string]: never; }>; -export type GetValidatorsApyQuery = { __typename?: 'Query', epoch?: { __typename?: 'Epoch', epochId: number, validatorSet?: { __typename?: 'ValidatorSet', activeValidators: { __typename?: 'ValidatorConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Validator', apy?: number | null, address: { __typename?: 'Address', address: any } }> } } | null } | null }; +export type GetValidatorsApyQuery = { __typename?: 'Query', epoch?: { __typename?: 'Epoch', epochId: any, validatorSet?: { __typename?: 'ValidatorSet', activeValidators: { __typename?: 'ValidatorConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Validator', apy?: number | null, address: { __typename?: 'Address', address: any } }> } } | null } | null }; export type ResolveNameServiceAddressQueryVariables = Exact<{ domain: Scalars['String']['input']; @@ -4916,7 +5585,7 @@ export type GetOwnedObjectsQueryVariables = Exact<{ }>; -export type GetOwnedObjectsQuery = { __typename?: 'Query', address?: { __typename?: 'Address', objects: { __typename?: 'MoveObjectConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'MoveObject', bcs?: any | null, hasPublicTransfer?: boolean, storageRebate?: any | null, digest?: string | null, version: number, objectId: any, contents?: { __typename?: 'MoveValue', data: any, bcs: any, type: { __typename?: 'MoveType', repr: string, layout: any, signature: any } } | null, owner?: { __typename: 'AddressOwner', owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null } | { __typename: 'Immutable' } | { __typename: 'Parent', parent?: { __typename?: 'Object', address: any } | null } | { __typename: 'Shared', initialSharedVersion: number } | null, previousTransactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null } | null, display?: Array<{ __typename?: 'DisplayEntry', key: string, value?: string | null, error?: string | null }> | null }> } } | null }; +export type GetOwnedObjectsQuery = { __typename?: 'Query', address?: { __typename?: 'Address', objects: { __typename?: 'MoveObjectConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'MoveObject', bcs?: any | null, hasPublicTransfer?: boolean, storageRebate?: any | null, digest?: string | null, version: any, objectId: any, contents?: { __typename?: 'MoveValue', data: any, bcs: any, type: { __typename?: 'MoveType', repr: string, layout?: any | null, signature: any } } | null, owner?: { __typename: 'AddressOwner', owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null } | { __typename: 'Immutable' } | { __typename: 'Parent', parent?: { __typename?: 'Owner', address: any } | null } | { __typename: 'Shared', initialSharedVersion: any } | null, previousTransactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null } | null, display?: Array<{ __typename?: 'DisplayEntry', key: string, value?: string | null, error?: string | null }> | null }> } } | null }; export type GetObjectQueryVariables = Exact<{ id: Scalars['SuiAddress']['input']; @@ -4930,11 +5599,11 @@ export type GetObjectQueryVariables = Exact<{ }>; -export type GetObjectQuery = { __typename?: 'Query', object?: { __typename?: 'Object', version: number, storageRebate?: any | null, digest?: string | null, objectId: any, asMoveObject?: { __typename?: 'MoveObject', hasPublicTransfer: boolean, contents?: { __typename?: 'MoveValue', data: any, bcs: any, type: { __typename?: 'MoveType', repr: string, layout: any, signature: any } } | null } | null, owner?: { __typename: 'AddressOwner', owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null } | { __typename: 'Immutable' } | { __typename: 'Parent', parent?: { __typename?: 'Object', address: any } | null } | { __typename: 'Shared', initialSharedVersion: number } | null, previousTransactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null } | null, display?: Array<{ __typename?: 'DisplayEntry', key: string, value?: string | null, error?: string | null }> | null } | null }; +export type GetObjectQuery = { __typename?: 'Query', object?: { __typename?: 'Object', version: any, storageRebate?: any | null, digest?: string | null, objectId: any, asMoveObject?: { __typename?: 'MoveObject', hasPublicTransfer: boolean, contents?: { __typename?: 'MoveValue', data: any, bcs: any, type: { __typename?: 'MoveType', repr: string, layout?: any | null, signature: any } } | null } | null, owner?: { __typename: 'AddressOwner', owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null } | { __typename: 'Immutable' } | { __typename: 'Parent', parent?: { __typename?: 'Owner', address: any } | null } | { __typename: 'Shared', initialSharedVersion: any } | null, previousTransactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null } | null, display?: Array<{ __typename?: 'DisplayEntry', key: string, value?: string | null, error?: string | null }> | null } | null }; export type TryGetPastObjectQueryVariables = Exact<{ id: Scalars['SuiAddress']['input']; - version?: InputMaybe; + version?: InputMaybe; showBcs?: InputMaybe; showOwner?: InputMaybe; showPreviousTransaction?: InputMaybe; @@ -4945,7 +5614,7 @@ export type TryGetPastObjectQueryVariables = Exact<{ }>; -export type TryGetPastObjectQuery = { __typename?: 'Query', current?: { __typename?: 'Object', address: any, version: number } | null, object?: { __typename?: 'Object', version: number, storageRebate?: any | null, digest?: string | null, objectId: any, asMoveObject?: { __typename?: 'MoveObject', hasPublicTransfer: boolean, contents?: { __typename?: 'MoveValue', data: any, bcs: any, type: { __typename?: 'MoveType', repr: string, layout: any, signature: any } } | null } | null, owner?: { __typename: 'AddressOwner', owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null } | { __typename: 'Immutable' } | { __typename: 'Parent', parent?: { __typename?: 'Object', address: any } | null } | { __typename: 'Shared', initialSharedVersion: number } | null, previousTransactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null } | null, display?: Array<{ __typename?: 'DisplayEntry', key: string, value?: string | null, error?: string | null }> | null } | null }; +export type TryGetPastObjectQuery = { __typename?: 'Query', current?: { __typename?: 'Object', address: any, version: any } | null, object?: { __typename?: 'Object', version: any, storageRebate?: any | null, digest?: string | null, objectId: any, asMoveObject?: { __typename?: 'MoveObject', hasPublicTransfer: boolean, contents?: { __typename?: 'MoveValue', data: any, bcs: any, type: { __typename?: 'MoveType', repr: string, layout?: any | null, signature: any } } | null } | null, owner?: { __typename: 'AddressOwner', owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null } | { __typename: 'Immutable' } | { __typename: 'Parent', parent?: { __typename?: 'Owner', address: any } | null } | { __typename: 'Shared', initialSharedVersion: any } | null, previousTransactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null } | null, display?: Array<{ __typename?: 'DisplayEntry', key: string, value?: string | null, error?: string | null }> | null } | null }; export type MultiGetObjectsQueryVariables = Exact<{ ids: Array | Scalars['SuiAddress']['input']; @@ -4961,19 +5630,19 @@ export type MultiGetObjectsQueryVariables = Exact<{ }>; -export type MultiGetObjectsQuery = { __typename?: 'Query', objects: { __typename?: 'ObjectConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Object', version: number, storageRebate?: any | null, digest?: string | null, objectId: any, asMoveObject?: { __typename?: 'MoveObject', hasPublicTransfer: boolean, contents?: { __typename?: 'MoveValue', data: any, bcs: any, type: { __typename?: 'MoveType', repr: string, layout: any, signature: any } } | null } | null, owner?: { __typename: 'AddressOwner', owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null } | { __typename: 'Immutable' } | { __typename: 'Parent', parent?: { __typename?: 'Object', address: any } | null } | { __typename: 'Shared', initialSharedVersion: number } | null, previousTransactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null } | null, display?: Array<{ __typename?: 'DisplayEntry', key: string, value?: string | null, error?: string | null }> | null }> } }; +export type MultiGetObjectsQuery = { __typename?: 'Query', objects: { __typename?: 'ObjectConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Object', version: any, storageRebate?: any | null, digest?: string | null, objectId: any, asMoveObject?: { __typename?: 'MoveObject', hasPublicTransfer: boolean, contents?: { __typename?: 'MoveValue', data: any, bcs: any, type: { __typename?: 'MoveType', repr: string, layout?: any | null, signature: any } } | null } | null, owner?: { __typename: 'AddressOwner', owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null } | { __typename: 'Immutable' } | { __typename: 'Parent', parent?: { __typename?: 'Owner', address: any } | null } | { __typename: 'Shared', initialSharedVersion: any } | null, previousTransactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null } | null, display?: Array<{ __typename?: 'DisplayEntry', key: string, value?: string | null, error?: string | null }> | null }> } }; -export type Rpc_Object_FieldsFragment = { __typename?: 'Object', version: number, storageRebate?: any | null, digest?: string | null, objectId: any, asMoveObject?: { __typename?: 'MoveObject', hasPublicTransfer: boolean, contents?: { __typename?: 'MoveValue', data: any, bcs: any, type: { __typename?: 'MoveType', repr: string, layout: any, signature: any } } | null } | null, owner?: { __typename: 'AddressOwner', owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null } | { __typename: 'Immutable' } | { __typename: 'Parent', parent?: { __typename?: 'Object', address: any } | null } | { __typename: 'Shared', initialSharedVersion: number } | null, previousTransactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null } | null, display?: Array<{ __typename?: 'DisplayEntry', key: string, value?: string | null, error?: string | null }> | null }; +export type Rpc_Object_FieldsFragment = { __typename?: 'Object', version: any, storageRebate?: any | null, digest?: string | null, objectId: any, asMoveObject?: { __typename?: 'MoveObject', hasPublicTransfer: boolean, contents?: { __typename?: 'MoveValue', data: any, bcs: any, type: { __typename?: 'MoveType', repr: string, layout?: any | null, signature: any } } | null } | null, owner?: { __typename: 'AddressOwner', owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null } | { __typename: 'Immutable' } | { __typename: 'Parent', parent?: { __typename?: 'Owner', address: any } | null } | { __typename: 'Shared', initialSharedVersion: any } | null, previousTransactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null } | null, display?: Array<{ __typename?: 'DisplayEntry', key: string, value?: string | null, error?: string | null }> | null }; -export type Rpc_Move_Object_FieldsFragment = { __typename?: 'MoveObject', bcs?: any | null, hasPublicTransfer?: boolean, storageRebate?: any | null, digest?: string | null, version: number, objectId: any, contents?: { __typename?: 'MoveValue', data: any, bcs: any, type: { __typename?: 'MoveType', repr: string, layout: any, signature: any } } | null, owner?: { __typename: 'AddressOwner', owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null } | { __typename: 'Immutable' } | { __typename: 'Parent', parent?: { __typename?: 'Object', address: any } | null } | { __typename: 'Shared', initialSharedVersion: number } | null, previousTransactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null } | null, display?: Array<{ __typename?: 'DisplayEntry', key: string, value?: string | null, error?: string | null }> | null }; +export type Rpc_Move_Object_FieldsFragment = { __typename?: 'MoveObject', bcs?: any | null, hasPublicTransfer?: boolean, storageRebate?: any | null, digest?: string | null, version: any, objectId: any, contents?: { __typename?: 'MoveValue', data: any, bcs: any, type: { __typename?: 'MoveType', repr: string, layout?: any | null, signature: any } } | null, owner?: { __typename: 'AddressOwner', owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null } | { __typename: 'Immutable' } | { __typename: 'Parent', parent?: { __typename?: 'Owner', address: any } | null } | { __typename: 'Shared', initialSharedVersion: any } | null, previousTransactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null } | null, display?: Array<{ __typename?: 'DisplayEntry', key: string, value?: string | null, error?: string | null }> | null }; type Rpc_Object_Owner_Fields_AddressOwner_Fragment = { __typename: 'AddressOwner', owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }; type Rpc_Object_Owner_Fields_Immutable_Fragment = { __typename: 'Immutable' }; -type Rpc_Object_Owner_Fields_Parent_Fragment = { __typename: 'Parent', parent?: { __typename?: 'Object', address: any } | null }; +type Rpc_Object_Owner_Fields_Parent_Fragment = { __typename: 'Parent', parent?: { __typename?: 'Owner', address: any } | null }; -type Rpc_Object_Owner_Fields_Shared_Fragment = { __typename: 'Shared', initialSharedVersion: number }; +type Rpc_Object_Owner_Fields_Shared_Fragment = { __typename: 'Shared', initialSharedVersion: any }; export type Rpc_Object_Owner_FieldsFragment = Rpc_Object_Owner_Fields_AddressOwner_Fragment | Rpc_Object_Owner_Fields_Immutable_Fragment | Rpc_Object_Owner_Fields_Parent_Fragment | Rpc_Object_Owner_Fields_Shared_Fragment; @@ -4986,9 +5655,9 @@ export type QueryEventsQueryVariables = Exact<{ }>; -export type QueryEventsQuery = { __typename?: 'Query', events: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, endCursor?: string | null, startCursor?: string | null }, nodes: Array<{ __typename?: 'Event', json: any, bcs: any, timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, type: { __typename?: 'MoveType', repr: string } }> } }; +export type QueryEventsQuery = { __typename?: 'Query', events: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, endCursor?: string | null, startCursor?: string | null }, nodes: Array<{ __typename?: 'Event', timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, contents: { __typename?: 'MoveValue', json: any, bcs: any, type: { __typename?: 'MoveType', repr: string } } }> } }; -export type Rpc_Events_FieldsFragment = { __typename?: 'Event', json: any, bcs: any, timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, type: { __typename?: 'MoveType', repr: string } }; +export type Rpc_Events_FieldsFragment = { __typename?: 'Event', timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, contents: { __typename?: 'MoveValue', json: any, bcs: any, type: { __typename?: 'MoveType', repr: string } } }; export type GetStakesQueryVariables = Exact<{ owner: Scalars['SuiAddress']['input']; @@ -4997,7 +5666,7 @@ export type GetStakesQueryVariables = Exact<{ }>; -export type GetStakesQuery = { __typename?: 'Query', address?: { __typename?: 'Address', stakedSuis: { __typename?: 'StakedSuiConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'StakedSui', principal?: any | null, stakeStatus: StakeStatus, address: any, estimatedReward?: any | null, activatedEpoch?: { __typename?: 'Epoch', epochId: number, referenceGasPrice?: any | null } | null, requestedEpoch?: { __typename?: 'Epoch', epochId: number } | null, contents?: { __typename?: 'MoveValue', json: any } | null }> } } | null }; +export type GetStakesQuery = { __typename?: 'Query', address?: { __typename?: 'Address', stakedSuis: { __typename?: 'StakedSuiConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'StakedSui', principal?: any | null, stakeStatus: StakeStatus, address: any, estimatedReward?: any | null, activatedEpoch?: { __typename?: 'Epoch', epochId: any, referenceGasPrice?: any | null } | null, requestedEpoch?: { __typename?: 'Epoch', epochId: any } | null, contents?: { __typename?: 'MoveValue', json: any } | null }> } } | null }; export type GetStakesByIdsQueryVariables = Exact<{ ids: Array | Scalars['SuiAddress']['input']; @@ -5006,9 +5675,9 @@ export type GetStakesByIdsQueryVariables = Exact<{ }>; -export type GetStakesByIdsQuery = { __typename?: 'Query', objects: { __typename?: 'ObjectConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', asStakedSui?: { __typename?: 'StakedSui', principal?: any | null, stakeStatus: StakeStatus, address: any, estimatedReward?: any | null, activatedEpoch?: { __typename?: 'Epoch', epochId: number, referenceGasPrice?: any | null } | null, requestedEpoch?: { __typename?: 'Epoch', epochId: number } | null, contents?: { __typename?: 'MoveValue', json: any } | null } | null } | null }> } }; +export type GetStakesByIdsQuery = { __typename?: 'Query', objects: { __typename?: 'ObjectConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', asStakedSui?: { __typename?: 'StakedSui', principal?: any | null, stakeStatus: StakeStatus, address: any, estimatedReward?: any | null, activatedEpoch?: { __typename?: 'Epoch', epochId: any, referenceGasPrice?: any | null } | null, requestedEpoch?: { __typename?: 'Epoch', epochId: any } | null, contents?: { __typename?: 'MoveValue', json: any } | null } | null } | null }> } }; -export type Rpc_Stake_FieldsFragment = { __typename?: 'StakedSui', principal?: any | null, stakeStatus: StakeStatus, address: any, estimatedReward?: any | null, activatedEpoch?: { __typename?: 'Epoch', epochId: number, referenceGasPrice?: any | null } | null, requestedEpoch?: { __typename?: 'Epoch', epochId: number } | null, contents?: { __typename?: 'MoveValue', json: any } | null }; +export type Rpc_Stake_FieldsFragment = { __typename?: 'StakedSui', principal?: any | null, stakeStatus: StakeStatus, address: any, estimatedReward?: any | null, activatedEpoch?: { __typename?: 'Epoch', epochId: any, referenceGasPrice?: any | null } | null, requestedEpoch?: { __typename?: 'Epoch', epochId: any } | null, contents?: { __typename?: 'MoveValue', json: any } | null }; export type QueryTransactionBlocksQueryVariables = Exact<{ first?: InputMaybe; @@ -5026,7 +5695,7 @@ export type QueryTransactionBlocksQueryVariables = Exact<{ }>; -export type QueryTransactionBlocksQuery = { __typename?: 'Query', transactionBlocks: { __typename?: 'TransactionBlockConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null, endCursor?: string | null }, nodes: Array<{ __typename?: 'TransactionBlock', digest?: string | null, signatures?: Array | null, rawTransaction?: any | null, sender?: { __typename?: 'Address', address: any } | null, effects?: { __typename?: 'TransactionBlockEffects', bcs?: any, timestamp?: any | null, events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', json: any, bcs: any, timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, type: { __typename?: 'MoveType', repr: string } }> }, checkpoint?: { __typename?: 'Checkpoint', sequenceNumber: number } | null, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: number, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null }> } }; +export type QueryTransactionBlocksQuery = { __typename?: 'Query', transactionBlocks: { __typename?: 'TransactionBlockConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null, endCursor?: string | null }, nodes: Array<{ __typename?: 'TransactionBlock', digest?: string | null, signatures?: Array | null, rawTransaction?: any | null, sender?: { __typename?: 'Address', address: any } | null, effects?: { __typename?: 'TransactionBlockEffects', bcs?: any, timestamp?: any | null, events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, contents: { __typename?: 'MoveValue', json: any, bcs: any, type: { __typename?: 'MoveType', repr: string } } }> }, checkpoint?: { __typename?: 'Checkpoint', sequenceNumber: any } | null, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: any, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null }> } }; export type GetTransactionBlockQueryVariables = Exact<{ digest: Scalars['String']['input']; @@ -5040,7 +5709,7 @@ export type GetTransactionBlockQueryVariables = Exact<{ }>; -export type GetTransactionBlockQuery = { __typename?: 'Query', transactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null, signatures?: Array | null, rawTransaction?: any | null, sender?: { __typename?: 'Address', address: any } | null, effects?: { __typename?: 'TransactionBlockEffects', bcs?: any, timestamp?: any | null, events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', json: any, bcs: any, timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, type: { __typename?: 'MoveType', repr: string } }> }, checkpoint?: { __typename?: 'Checkpoint', sequenceNumber: number } | null, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: number, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null } | null }; +export type GetTransactionBlockQuery = { __typename?: 'Query', transactionBlock?: { __typename?: 'TransactionBlock', digest?: string | null, signatures?: Array | null, rawTransaction?: any | null, sender?: { __typename?: 'Address', address: any } | null, effects?: { __typename?: 'TransactionBlockEffects', bcs?: any, timestamp?: any | null, events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, contents: { __typename?: 'MoveValue', json: any, bcs: any, type: { __typename?: 'MoveType', repr: string } } }> }, checkpoint?: { __typename?: 'Checkpoint', sequenceNumber: any } | null, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: any, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null } | null }; export type MultiGetTransactionBlocksQueryVariables = Exact<{ digests: Array | Scalars['String']['input']; @@ -5056,7 +5725,7 @@ export type MultiGetTransactionBlocksQueryVariables = Exact<{ }>; -export type MultiGetTransactionBlocksQuery = { __typename?: 'Query', transactionBlocks: { __typename?: 'TransactionBlockConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null, endCursor?: string | null }, nodes: Array<{ __typename?: 'TransactionBlock', digest?: string | null, signatures?: Array | null, rawTransaction?: any | null, sender?: { __typename?: 'Address', address: any } | null, effects?: { __typename?: 'TransactionBlockEffects', bcs?: any, timestamp?: any | null, events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', json: any, bcs: any, timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, type: { __typename?: 'MoveType', repr: string } }> }, checkpoint?: { __typename?: 'Checkpoint', sequenceNumber: number } | null, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: number, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null }> } }; +export type MultiGetTransactionBlocksQuery = { __typename?: 'Query', transactionBlocks: { __typename?: 'TransactionBlockConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, hasPreviousPage: boolean, startCursor?: string | null, endCursor?: string | null }, nodes: Array<{ __typename?: 'TransactionBlock', digest?: string | null, signatures?: Array | null, rawTransaction?: any | null, sender?: { __typename?: 'Address', address: any } | null, effects?: { __typename?: 'TransactionBlockEffects', bcs?: any, timestamp?: any | null, events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, contents: { __typename?: 'MoveValue', json: any, bcs: any, type: { __typename?: 'MoveType', repr: string } } }> }, checkpoint?: { __typename?: 'Checkpoint', sequenceNumber: any } | null, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: any, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null }> } }; export type PaginateTransactionBlockListsQueryVariables = Exact<{ digest: Scalars['String']['input']; @@ -5069,11 +5738,11 @@ export type PaginateTransactionBlockListsQueryVariables = Exact<{ }>; -export type PaginateTransactionBlockListsQuery = { __typename?: 'Query', transactionBlock?: { __typename?: 'TransactionBlock', effects?: { __typename?: 'TransactionBlockEffects', events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', json: any, bcs: any, timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, type: { __typename?: 'MoveType', repr: string } }> }, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: number, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null } | null }; +export type PaginateTransactionBlockListsQuery = { __typename?: 'Query', transactionBlock?: { __typename?: 'TransactionBlock', effects?: { __typename?: 'TransactionBlockEffects', events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, contents: { __typename?: 'MoveValue', json: any, bcs: any, type: { __typename?: 'MoveType', repr: string } } }> }, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: any, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null } | null }; -export type Paginate_Transaction_ListsFragment = { __typename?: 'TransactionBlock', effects?: { __typename?: 'TransactionBlockEffects', events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', json: any, bcs: any, timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, type: { __typename?: 'MoveType', repr: string } }> }, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: number, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null }; +export type Paginate_Transaction_ListsFragment = { __typename?: 'TransactionBlock', effects?: { __typename?: 'TransactionBlockEffects', events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, contents: { __typename?: 'MoveValue', json: any, bcs: any, type: { __typename?: 'MoveType', repr: string } } }> }, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: any, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null }; -export type Rpc_Transaction_FieldsFragment = { __typename?: 'TransactionBlock', digest?: string | null, signatures?: Array | null, rawTransaction?: any | null, sender?: { __typename?: 'Address', address: any } | null, effects?: { __typename?: 'TransactionBlockEffects', bcs?: any, timestamp?: any | null, events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', json: any, bcs: any, timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, type: { __typename?: 'MoveType', repr: string } }> }, checkpoint?: { __typename?: 'Checkpoint', sequenceNumber: number } | null, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: number, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null }; +export type Rpc_Transaction_FieldsFragment = { __typename?: 'TransactionBlock', digest?: string | null, signatures?: Array | null, rawTransaction?: any | null, sender?: { __typename?: 'Address', address: any } | null, effects?: { __typename?: 'TransactionBlockEffects', bcs?: any, timestamp?: any | null, events?: { __typename?: 'EventConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'Event', timestamp?: any | null, sendingModule?: { __typename?: 'MoveModule', name: string, package: { __typename?: 'MovePackage', address: any } } | null, sender?: { __typename?: 'Address', address: any } | null, contents: { __typename?: 'MoveValue', json: any, bcs: any, type: { __typename?: 'MoveType', repr: string } } }> }, checkpoint?: { __typename?: 'Checkpoint', sequenceNumber: any } | null, balanceChanges?: { __typename?: 'BalanceChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'BalanceChange', amount?: any | null, coinType?: { __typename?: 'MoveType', repr: string } | null, owner?: { __typename?: 'Owner', asObject?: { __typename?: 'Object', address: any } | null, asAddress?: { __typename?: 'Address', address: any } | null } | null }> }, objectChanges?: { __typename?: 'ObjectChangeConnection', pageInfo: { __typename?: 'PageInfo', hasNextPage: boolean, endCursor?: string | null }, nodes: Array<{ __typename?: 'ObjectChange', address: any, inputState?: { __typename?: 'Object', version: any, asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null } | null, outputState?: { __typename?: 'Object', asMoveObject?: { __typename?: 'MoveObject', contents?: { __typename?: 'MoveValue', type: { __typename?: 'MoveType', repr: string } } | null } | null, asMovePackage?: { __typename?: 'MovePackage', modules?: { __typename?: 'MoveModuleConnection', nodes: Array<{ __typename?: 'MoveModule', name: string }> } | null } | null } | null }> } } | null }; export class TypedDocumentString extends String @@ -5499,11 +6168,13 @@ export const Rpc_Events_FieldsFragmentDoc = new TypedDocumentString(` sender { address } - type { - repr + contents { + type { + repr + } + json + bcs } - json - bcs timestamp } `, {"fragmentName":"RPC_EVENTS_FIELDS"}) as unknown as TypedDocumentString; @@ -5586,11 +6257,13 @@ export const Paginate_Transaction_ListsFragmentDoc = new TypedDocumentString(` sender { address } - type { - repr + contents { + type { + repr + } + json + bcs } - json - bcs timestamp }`, {"fragmentName":"PAGINATE_TRANSACTION_LISTS"}) as unknown as TypedDocumentString; export const Rpc_Transaction_FieldsFragmentDoc = new TypedDocumentString(` @@ -5686,11 +6359,13 @@ export const Rpc_Transaction_FieldsFragmentDoc = new TypedDocumentString(` sender { address } - type { - repr + contents { + type { + repr + } + json + bcs } - json - bcs timestamp }`, {"fragmentName":"RPC_TRANSACTION_FIELDS"}) as unknown as TypedDocumentString; export const GetCheckpointDocument = new TypedDocumentString(` @@ -5895,11 +6570,13 @@ export const DevInspectTransactionBlockDocument = new TypedDocumentString(` sender { address } - type { - repr + contents { + type { + repr + } + json + bcs } - json - bcs timestamp } fragment RPC_TRANSACTION_FIELDS on TransactionBlock { @@ -6003,11 +6680,13 @@ export const DryRunTransactionBlockDocument = new TypedDocumentString(` sender { address } - type { - repr + contents { + type { + repr + } + json + bcs } - json - bcs timestamp } fragment RPC_TRANSACTION_FIELDS on TransactionBlock { @@ -6113,11 +6792,13 @@ export const ExecuteTransactionBlockDocument = new TypedDocumentString(` sender { address } - type { - repr + contents { + type { + repr + } + json + bcs } - json - bcs timestamp } fragment RPC_TRANSACTION_FIELDS on TransactionBlock { @@ -6279,7 +6960,7 @@ export const GetCoinsDocument = new TypedDocumentString(` } `) as unknown as TypedDocumentString; export const GetCommitteeInfoDocument = new TypedDocumentString(` - query getCommitteeInfo($epochId: Int, $after: String) { + query getCommitteeInfo($epochId: UInt53, $after: String) { epoch(id: $epochId) { epochId validatorSet { @@ -6379,7 +7060,7 @@ fragment RPC_CREDENTIAL_FIELDS on ValidatorCredentials { protocolPubKey }`) as unknown as TypedDocumentString; export const PaginateEpochValidatorsDocument = new TypedDocumentString(` - query paginateEpochValidators($id: Int!, $after: String) { + query paginateEpochValidators($id: UInt53!, $after: String) { epoch(id: $id) { validatorSet { activeValidators(after: $after) { @@ -6465,30 +7146,32 @@ export const GetDynamicFieldObjectDocument = new TypedDocumentString(` __typename ... on Parent { parent { - address - digest - version - storageRebate - owner { - __typename - ... on Parent { - parent { - address + asObject { + address + digest + version + storageRebate + owner { + __typename + ... on Parent { + parent { + address + } } } - } - previousTransactionBlock { - digest - } - asMoveObject { - contents { - data - type { - repr - layout + previousTransactionBlock { + digest + } + asMoveObject { + contents { + data + type { + repr + layout + } } + hasPublicTransfer } - hasPublicTransfer } } } @@ -6960,7 +7643,7 @@ export const GetNormalizedMoveStructDocument = new TypedDocumentString(` } }`) as unknown as TypedDocumentString; export const GetProtocolConfigDocument = new TypedDocumentString(` - query getProtocolConfig($protocolVersion: Int) { + query getProtocolConfig($protocolVersion: UInt53) { protocolConfig(protocolVersion: $protocolVersion) { protocolVersion configs { @@ -7186,7 +7869,7 @@ fragment RPC_OBJECT_OWNER_FIELDS on ObjectOwner { } }`) as unknown as TypedDocumentString; export const TryGetPastObjectDocument = new TypedDocumentString(` - query tryGetPastObject($id: SuiAddress!, $version: Int, $showBcs: Boolean = false, $showOwner: Boolean = false, $showPreviousTransaction: Boolean = false, $showContent: Boolean = false, $showDisplay: Boolean = false, $showType: Boolean = false, $showStorageRebate: Boolean = false) { + query tryGetPastObject($id: SuiAddress!, $version: UInt53, $showBcs: Boolean = false, $showOwner: Boolean = false, $showPreviousTransaction: Boolean = false, $showContent: Boolean = false, $showDisplay: Boolean = false, $showType: Boolean = false, $showStorageRebate: Boolean = false) { current: object(address: $id) { address version @@ -7369,11 +8052,13 @@ export const QueryEventsDocument = new TypedDocumentString(` sender { address } - type { - repr + contents { + type { + repr + } + json + bcs } - json - bcs timestamp }`) as unknown as TypedDocumentString; export const GetStakesDocument = new TypedDocumentString(` @@ -7474,11 +8159,13 @@ export const QueryTransactionBlocksDocument = new TypedDocumentString(` sender { address } - type { - repr + contents { + type { + repr + } + json + bcs } - json - bcs timestamp } fragment RPC_TRANSACTION_FIELDS on TransactionBlock { @@ -7579,11 +8266,13 @@ export const GetTransactionBlockDocument = new TypedDocumentString(` sender { address } - type { - repr + contents { + type { + repr + } + json + bcs } - json - bcs timestamp } fragment RPC_TRANSACTION_FIELDS on TransactionBlock { @@ -7696,11 +8385,13 @@ export const MultiGetTransactionBlocksDocument = new TypedDocumentString(` sender { address } - type { - repr + contents { + type { + repr + } + json + bcs } - json - bcs timestamp } fragment RPC_TRANSACTION_FIELDS on TransactionBlock { @@ -7801,11 +8492,13 @@ export const PaginateTransactionBlockListsDocument = new TypedDocumentString(` sender { address } - type { - repr + contents { + type { + repr + } + json + bcs } - json - bcs timestamp } fragment PAGINATE_TRANSACTION_LISTS on TransactionBlock { diff --git a/sdk/graphql-transport/src/mappers/bcs.ts b/sdk/graphql-transport/src/mappers/bcs.ts index 8c507ec5f07ba..8438155c20556 100644 --- a/sdk/graphql-transport/src/mappers/bcs.ts +++ b/sdk/graphql-transport/src/mappers/bcs.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { toB64 } from '@mysten/bcs'; +import { toBase64 } from '@mysten/bcs'; import type { BcsType } from '@mysten/bcs'; import { bcs } from '@mysten/sui/bcs'; @@ -57,5 +57,5 @@ export function layoutToBcs(layout: MoveTypeLayout): BcsType { export function mapJsonToBcs(json: unknown, layout: MoveTypeLayout) { const schema = layoutToBcs(layout); - return toB64(schema.serialize(json).toBytes()); + return toBase64(schema.serialize(json).toBytes()); } diff --git a/sdk/graphql-transport/src/mappers/transaction-block.ts b/sdk/graphql-transport/src/mappers/transaction-block.ts index 5dad4ae1036e1..cb0d4256163a3 100644 --- a/sdk/graphql-transport/src/mappers/transaction-block.ts +++ b/sdk/graphql-transport/src/mappers/transaction-block.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64, toB58 } from '@mysten/bcs'; +import { fromBase64, toBase58 } from '@mysten/bcs'; import { bcs } from '@mysten/sui/bcs'; import type { SuiArgument, @@ -47,7 +47,7 @@ export function mapGraphQLTransactionBlockToRpcTransactionBlock( ...(options?.showRawEffects ? { rawEffects: transactionBlock.effects?.bcs - ? Array.from(fromB64(transactionBlock.effects?.bcs)) + ? Array.from(fromBase64(transactionBlock.effects?.bcs)) : undefined, } : {}), @@ -55,25 +55,26 @@ export function mapGraphQLTransactionBlockToRpcTransactionBlock( ...(errors ? { errors: errors } : {}), events: transactionBlock.effects?.events?.nodes.map((event) => ({ - bcs: event.bcs, + bcs: event.contents.bcs, id: { eventSeq: '', // TODO txDigest: '', // TODO }, packageId: event.sendingModule?.package.address!, - parsedJson: event.json ? JSON.parse(event.json) : undefined, + parsedJson: event.contents.json ? JSON.parse(event.contents.json) : undefined, sender: event.sender?.address, timestampMs: new Date(event.timestamp).getTime().toString(), transactionModule: `${event.sendingModule?.package.address}::${event.sendingModule?.name}`, - type: toShortTypeString(event.type?.repr)!, + type: toShortTypeString(event.contents.type?.repr)!, })) ?? [], - rawTransaction: options?.showRawInput ? transactionBlock.rawTransaction : undefined, + rawTransaction: options?.showRawInput ? mapRawTransaction(transactionBlock) : undefined, ...(options?.showInput ? { transaction: transactionBlock.rawTransaction && mapTransactionBlockToInput( - bcs.SenderSignedData.parse(fromB64(transactionBlock.rawTransaction))[0], + bcs.TransactionData.fromBase64(transactionBlock.rawTransaction), + transactionBlock.signatures, ), } : {}), @@ -83,6 +84,30 @@ export function mapGraphQLTransactionBlockToRpcTransactionBlock( }; } +function mapRawTransaction(transactionBlock: Rpc_Transaction_FieldsFragment) { + const txData = bcs.TransactionData.fromBase64(transactionBlock.rawTransaction); + + return bcs.SenderSignedData.serialize([ + { + intentMessage: { + intent: { + scope: { + TransactionData: true, + }, + version: { + V0: true, + }, + appId: { + Sui: true, + }, + }, + value: txData, + }, + txSignatures: transactionBlock.signatures?.map((sig) => fromBase64(sig)) ?? [], + }, + ]).toBase64(); +} + function mapObjectChanges( transactionBlock: Rpc_Transaction_FieldsFragment, effects: SuiTransactionBlockResponse['effects'], @@ -169,10 +194,12 @@ function mapObjectChanges( } export function mapTransactionBlockToInput( - data: typeof bcs.SenderSignedTransaction.$inferType, + data: typeof bcs.TransactionData.$inferType, + signatures: any[] | null | undefined, ): SuiTransactionBlock | null { - const txData = data.intentMessage.value.V1; - + const txData = data.V1; + console.log('Signatures:', signatures); + const sigs: string[] = (signatures ?? []).filter((sig): sig is string => typeof sig === 'string'); const programableTransaction = 'ProgrammableTransaction' in txData.kind ? txData.kind.ProgrammableTransaction : null; @@ -181,7 +208,7 @@ export function mapTransactionBlockToInput( } return { - txSignatures: data.txSignatures, + txSignatures: sigs, data: { gasData: { budget: txData.gasData.budget, @@ -214,7 +241,7 @@ function mapTransactionInput(input: typeof bcs.CallArg.$inferType): SuiCallArg { if (input.Pure) { return { type: 'pure', - value: fromB64(input.Pure.bytes), + value: fromBase64(input.Pure.bytes), }; } @@ -340,13 +367,13 @@ function mapTransactionArgument(arg: typeof bcs.Argument.$inferType): SuiArgumen throw new Error(`Unknown argument type ${arg}`); } -const OBJECT_DIGEST_DELETED = toB58(Uint8Array.from({ length: 32 }, () => 99)); -const OBJECT_DIGEST_WRAPPED = toB58(Uint8Array.from({ length: 32 }, () => 88)); -const OBJECT_DIGEST_ZERO = toB58(Uint8Array.from({ length: 32 }, () => 0)); +const OBJECT_DIGEST_DELETED = toBase58(Uint8Array.from({ length: 32 }, () => 99)); +const OBJECT_DIGEST_WRAPPED = toBase58(Uint8Array.from({ length: 32 }, () => 88)); +const OBJECT_DIGEST_ZERO = toBase58(Uint8Array.from({ length: 32 }, () => 0)); const ADDRESS_ZERO = normalizeSuiAddress('0x0'); export function mapEffects(data: string): SuiTransactionBlockResponse['effects'] { - const effects = bcs.TransactionEffects.parse(fromB64(data)); + const effects = bcs.TransactionEffects.parse(fromBase64(data)); let effectsV1 = effects.V1; diff --git a/sdk/graphql-transport/src/methods.ts b/sdk/graphql-transport/src/methods.ts index 011e965897b84..982940aeaefe5 100644 --- a/sdk/graphql-transport/src/methods.ts +++ b/sdk/graphql-transport/src/methods.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64, toB58 } from '@mysten/bcs'; +import { fromBase64, toBase58 } from '@mysten/bcs'; import type { MoveValue, ProtocolConfigValue, @@ -622,8 +622,8 @@ export const RPC_METHODS: { : undefined, inputObject: 'InputObject' in filter ? filter.InputObject : undefined, changedObject: 'ChangedObject' in filter ? filter.ChangedObject : undefined, - signAddress: 'FromAddress' in filter ? filter.FromAddress : undefined, - recvAddress: 'ToAddress' in filter ? filter.ToAddress : undefined, + sentAddress: 'FromAddress' in filter ? filter.FromAddress : undefined, + affectedAddress: 'ToAddress' in filter ? filter.ToAddress : undefined, kind: 'TransactionKind' in filter ? filter.TransactionKind === 'ProgrammableTransaction' @@ -888,17 +888,17 @@ export const RPC_METHODS: { hasNextPage: pagination.last ? pageInfo.hasPreviousPage : pageInfo.hasNextPage, nextCursor: (pagination.last ? pageInfo.startCursor : pageInfo.endCursor) as never, data: events.map((event) => ({ - bcs: event.bcs, + bcs: event.contents.bcs, id: { eventSeq: '', // TODO txDigest: '', // TODO }, packageId: event.sendingModule?.package.address!, - parsedJson: event.json ? JSON.parse(event.json) : undefined, + parsedJson: event.contents.json ? JSON.parse(event.contents.json) : undefined, sender: event.sender?.address, timestampMs: new Date(event.timestamp).getTime().toString(), transactionModule: `${event.sendingModule?.package.address}::${event.sendingModule?.name}`, - type: toShortTypeString(event.type?.repr)!, + type: toShortTypeString(event.contents.type?.repr)!, })), }; }, @@ -948,12 +948,12 @@ export const RPC_METHODS: { : { Result: ref.input.cmd, }, - Array.from(fromB64(ref.bcs)), + Array.from(fromBase64(ref.bcs)), toShortTypeString(ref.type.repr), ], ), returnValues: result.returnValues?.map((value) => [ - Array.from(fromB64(value.bcs)), + Array.from(fromBase64(value.bcs)), toShortTypeString(value.type.repr), ]), })), @@ -974,7 +974,7 @@ export const RPC_METHODS: { return { data: fields.map((field) => ({ - bcsName: field.name?.bcs && toB58(fromB64(field.name.bcs)), + bcsName: field.name?.bcs && toBase58(fromBase64(field.name.bcs)), digest: (field.value?.__typename === 'MoveObject' ? field.value.digest : undefined)!, name: { type: toShortTypeString(field.name?.type.repr)!, @@ -1020,7 +1020,7 @@ export const RPC_METHODS: { (data) => { return data.owner?.dynamicObjectField?.value?.__typename === 'MoveObject' ? data.owner.dynamicObjectField.value.owner?.__typename === 'Parent' - ? data.owner.dynamicObjectField.value.owner.parent + ? data.owner.dynamicObjectField.value.owner.parent?.asObject : undefined : undefined; }, @@ -1077,7 +1077,7 @@ export const RPC_METHODS: { ); if (!effects?.transactionBlock) { - const tx = Transaction.from(fromB64(txBytes)); + const tx = Transaction.from(fromBase64(txBytes)); return { errors: errors ?? undefined, digest: await tx.getDigest() }; } @@ -1090,7 +1090,7 @@ export const RPC_METHODS: { ); }, async dryRunTransactionBlock(transport, [txBytes]) { - const tx = Transaction.from(fromB64(txBytes)); + const tx = Transaction.from(fromBase64(txBytes)); const { transaction, error } = await transport.graphqlQuery( { query: DryRunTransactionBlockDocument, diff --git a/sdk/graphql-transport/src/queries/getCommitteeInfo.graphql b/sdk/graphql-transport/src/queries/getCommitteeInfo.graphql index 79b136c9d2f55..2865beffdddc6 100644 --- a/sdk/graphql-transport/src/queries/getCommitteeInfo.graphql +++ b/sdk/graphql-transport/src/queries/getCommitteeInfo.graphql @@ -1,7 +1,7 @@ # Copyright (c) Mysten Labs, Inc. # SPDX-License-Identifier: Apache-2.0 -query getCommitteeInfo($epochId: Int, $after: String) { +query getCommitteeInfo($epochId: UInt53, $after: String) { epoch(id: $epochId) { epochId validatorSet { diff --git a/sdk/graphql-transport/src/queries/getCurrentEpoch.graphql b/sdk/graphql-transport/src/queries/getCurrentEpoch.graphql index d7cbba04457e7..db691e10f56bb 100644 --- a/sdk/graphql-transport/src/queries/getCurrentEpoch.graphql +++ b/sdk/graphql-transport/src/queries/getCurrentEpoch.graphql @@ -27,7 +27,7 @@ query getCurrentEpoch { } } -query paginateEpochValidators($id: Int!, $after: String) { +query paginateEpochValidators($id: UInt53!, $after: String) { epoch(id: $id) { validatorSet { activeValidators(after: $after) { diff --git a/sdk/graphql-transport/src/queries/getDynamicFieldObject.graphql b/sdk/graphql-transport/src/queries/getDynamicFieldObject.graphql index a613502fd3d5e..aca4e5eca8f7e 100644 --- a/sdk/graphql-transport/src/queries/getDynamicFieldObject.graphql +++ b/sdk/graphql-transport/src/queries/getDynamicFieldObject.graphql @@ -17,30 +17,32 @@ query getDynamicFieldObject($parentId: SuiAddress!, $name: DynamicFieldName!) { __typename ... on Parent { parent { - address - digest - version - storageRebate - owner { - __typename - ... on Parent { - parent { - address + asObject { + address + digest + version + storageRebate + owner { + __typename + ... on Parent { + parent { + address + } } } - } - previousTransactionBlock { - digest - } - asMoveObject { - contents { - data - type { - repr - layout + previousTransactionBlock { + digest + } + asMoveObject { + contents { + data + type { + repr + layout + } } + hasPublicTransfer } - hasPublicTransfer } } } diff --git a/sdk/graphql-transport/src/queries/getProtocolConfig.graphql b/sdk/graphql-transport/src/queries/getProtocolConfig.graphql index 4b669334b7fea..072c8a1ad305d 100644 --- a/sdk/graphql-transport/src/queries/getProtocolConfig.graphql +++ b/sdk/graphql-transport/src/queries/getProtocolConfig.graphql @@ -1,7 +1,7 @@ # Copyright (c) Mysten Labs, Inc. # SPDX-License-Identifier: Apache-2.0 -query getProtocolConfig($protocolVersion: Int) { +query getProtocolConfig($protocolVersion: UInt53) { protocolConfig(protocolVersion: $protocolVersion) { protocolVersion configs { diff --git a/sdk/graphql-transport/src/queries/objects.graphql b/sdk/graphql-transport/src/queries/objects.graphql index d18a0c998e181..ee58168b618c4 100644 --- a/sdk/graphql-transport/src/queries/objects.graphql +++ b/sdk/graphql-transport/src/queries/objects.graphql @@ -44,7 +44,7 @@ query getObject( query tryGetPastObject( $id: SuiAddress! - $version: Int + $version: UInt53 $showBcs: Boolean = false $showOwner: Boolean = false $showPreviousTransaction: Boolean = false diff --git a/sdk/graphql-transport/src/queries/queryEvents.graphql b/sdk/graphql-transport/src/queries/queryEvents.graphql index 52dac6de9f4b5..123294047d291 100644 --- a/sdk/graphql-transport/src/queries/queryEvents.graphql +++ b/sdk/graphql-transport/src/queries/queryEvents.graphql @@ -36,10 +36,13 @@ fragment RPC_EVENTS_FIELDS on Event { sender { address } - type { - repr + contents { + type { + repr + } + + json + bcs } - json - bcs timestamp } diff --git a/sdk/kiosk/CHANGELOG.md b/sdk/kiosk/CHANGELOG.md index 992397fc194b7..975703c2a7aa6 100644 --- a/sdk/kiosk/CHANGELOG.md +++ b/sdk/kiosk/CHANGELOG.md @@ -1,5 +1,35 @@ # @mysten/kiosk +## 0.9.22 + +### Patch Changes + +- Updated dependencies [477d2a4] + - @mysten/sui@1.13.0 + +## 0.9.21 + +### Patch Changes + +- Updated dependencies [5436a90] +- Updated dependencies [5436a90] + - @mysten/sui@1.12.0 + +## 0.9.20 + +### Patch Changes + +- Updated dependencies [489f421] +- Updated dependencies [489f421] + - @mysten/sui@1.11.0 + +## 0.9.19 + +### Patch Changes + +- Updated dependencies [830b8d8] + - @mysten/sui@1.10.0 + ## 0.9.18 ### Patch Changes diff --git a/sdk/kiosk/package.json b/sdk/kiosk/package.json index 57e298ee922d8..98e442b40e885 100644 --- a/sdk/kiosk/package.json +++ b/sdk/kiosk/package.json @@ -2,7 +2,7 @@ "name": "@mysten/kiosk", "author": "Mysten Labs ", "description": "Sui Kiosk library", - "version": "0.9.18", + "version": "0.9.22", "license": "Apache-2.0", "type": "commonjs", "main": "./dist/cjs/index.js", diff --git a/sdk/kiosk/src/query/transfer-policy.ts b/sdk/kiosk/src/query/transfer-policy.ts index a2931f6e5caf8..7b3af43a447d4 100644 --- a/sdk/kiosk/src/query/transfer-policy.ts +++ b/sdk/kiosk/src/query/transfer-policy.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { SuiClient } from '@mysten/sui/client'; -import { fromB64, isValidSuiAddress } from '@mysten/sui/utils'; +import { fromBase64, isValidSuiAddress } from '@mysten/sui/utils'; import '../bcs.js'; @@ -50,7 +50,7 @@ export async function queryTransferPolicy( throw new Error(`Invalid policy: ${policy?.objectId}, expected object, got package`); } - const parsed = TransferPolicyType.parse(fromB64(policy.bcs.bcsBytes)); + const parsed = TransferPolicyType.parse(fromBase64(policy.bcs.bcsBytes)); return { id: policy?.objectId, diff --git a/sdk/kiosk/src/utils.ts b/sdk/kiosk/src/utils.ts index 3d8293c4e09a9..7c9eede989ffa 100644 --- a/sdk/kiosk/src/utils.ts +++ b/sdk/kiosk/src/utils.ts @@ -11,7 +11,7 @@ import type { SuiObjectResponse, } from '@mysten/sui/client'; import { - fromB64, + fromBase64, normalizeStructTag, normalizeSuiAddress, parseStructTag, @@ -34,7 +34,7 @@ export async function getKioskObject(client: SuiClient, id: string): Promise { }); function pokemonBytes() { - return fromHEX( + return fromHex( 'a11ceb0b060000000a01000202020403064b055139078a019b0108a5022006c5021e0ae302140cf702f7030dee0610000a000007000009000100000d00010000020201000008030400000b050100000506010000010607000004060700000c060700000e060700000f06070000060607000010060800000309050000070a050004060800060800020201030603030303030308020202020202020a0201080000010608000102010a02020708000301070800010104030303030553746174730661747461636b0664616d6167650b64656372656173655f687007646566656e7365026870056c6576656c086c6576656c5f7570036e65770f706879736963616c5f64616d6167650a706f6b656d6f6e5f7631077363616c696e670e7370656369616c5f61747461636b0e7370656369616c5f64616d6167650f7370656369616c5f646566656e73650573706565640574797065730000000000000000000000000000000000000000000000000000000000000000030800ca9a3b0000000003080000000000000000030801000000000000000002080503010204020c020e020f020602100a02000100000b320a0331d92604090a0331ff250c04050b090c040b04040e05140b01010b00010701270a023100240419051f0b01010b00010702270a00100014340b00100114340b01100214340b02340b03340700110202010100000b320a0331d92604090a0331ff250c04050b090c040b04040e05140b01010b00010701270a023100240419051f0b01010b00010702270a00100014340b00100314340b01100414340b02340b03340700110202020000000c2a0602000000000000000b0018060100000000000000180605000000000000001a060200000000000000160c070a050b01180b021a0c060b070b03180b06180632000000000000001a0602000000000000000a0518160c080a050b041806ff000000000000001a0c090b080b0918060100000000000000180b051a0203010000050d0b00340700180b010b020b030b040b050b060b071200020401000005020700020501000005040b00100514020601000005040b00100114020701000005040b00100214020801000005040b00100314020901000005040b00100414020a01000005040b00100614020b01000005040b00100014020c01000005040b00100714020d01000005140a010a0010051424040b0600000000000000000b000f051505130a001005140b01170b000f0515020e01000005090a001000143101160b000f0015020006000100020003000400000005000700', ); } function coinTemplateBytes() { - return fromHEX( + return fromHex( 'a11ceb0b060000000a01000c020c1e032a1c044608054e46079401a10108b50260069503390ace03050cd30329000e010b0206020f021002110002020001010701000002000c01000102030c01000104040200050507000009000100010a01040100020706070102030c0b01010c040d08090001030205030a030202080007080400010b02010800010805010b01010900010800070900020a020a020a020b01010805070804020b030109000b02010900010608040105010b03010800020900050c436f696e4d65746164617461064f7074696f6e0854454d504c4154450b5472656173757279436170095478436f6e746578740355726c04636f696e0f6372656174655f63757272656e63790b64756d6d795f6669656c6404696e6974046e6f6e65066f7074696f6e0f7075626c69635f7472616e736665720673656e6465720874656d706c617465087472616e736665720a74785f636f6e746578740375726c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020201060a020504544d504c0a020e0d54656d706c61746520436f696e0a021a1954656d706c61746520436f696e204465736372697074696f6e00020108010000000002130b00070007010702070338000a0138010c020a012e110438020b020b012e110438030200', ); } diff --git a/sdk/suins-toolkit/CHANGELOG.md b/sdk/suins-toolkit/CHANGELOG.md index 09c034761eaa1..62172a2cb5e31 100644 --- a/sdk/suins-toolkit/CHANGELOG.md +++ b/sdk/suins-toolkit/CHANGELOG.md @@ -1,5 +1,35 @@ # @mysten/suins-toolkit +## 0.5.22 + +### Patch Changes + +- Updated dependencies [477d2a4] + - @mysten/sui@1.13.0 + +## 0.5.21 + +### Patch Changes + +- Updated dependencies [5436a90] +- Updated dependencies [5436a90] + - @mysten/sui@1.12.0 + +## 0.5.20 + +### Patch Changes + +- Updated dependencies [489f421] +- Updated dependencies [489f421] + - @mysten/sui@1.11.0 + +## 0.5.19 + +### Patch Changes + +- Updated dependencies [830b8d8] + - @mysten/sui@1.10.0 + ## 0.5.18 ### Patch Changes diff --git a/sdk/suins-toolkit/package.json b/sdk/suins-toolkit/package.json index 260a1fd686ed5..60d0d8024ee98 100644 --- a/sdk/suins-toolkit/package.json +++ b/sdk/suins-toolkit/package.json @@ -2,7 +2,7 @@ "name": "@mysten/suins-toolkit", "author": "Mysten Labs ", "description": "SuiNS TypeScript SDK", - "version": "0.5.18", + "version": "0.5.22", "license": "Apache-2.0", "type": "commonjs", "main": "./dist/cjs/index.js", diff --git a/sdk/typescript/CHANGELOG.md b/sdk/typescript/CHANGELOG.md index 34ab44e579cfb..cc7a666518210 100644 --- a/sdk/typescript/CHANGELOG.md +++ b/sdk/typescript/CHANGELOG.md @@ -1,5 +1,47 @@ # @mysten/sui.js +## 1.13.0 + +### Minor Changes + +- 477d2a4: Add new errors to ExecutionFailureStatus enum + +## 1.12.0 + +### Minor Changes + +- 5436a90: Update GraphQL schemas +- 5436a90: add deriveDynamicFieldID util + +## 1.11.0 + +### Minor Changes + +- 489f421: Updated hex, base64, and base58 utility names for better consistency + + All existing methods will continue to work, but the following methods have been deprecated and + replaced with methods with improved names: + + - `toHEX` -> `toHEX` + - `fromHEX` -> `fromHex` + - `toB64` -> `toBase64` + - `fromB64` -> `fromBase64` + - `toB58` -> `toBase58` + - `fromB58` -> `fromBase58` + +- 489f421: support Bech32 secrets in the Keypair.fromSecretKey methods + +### Patch Changes + +- Updated dependencies [489f421] + - @mysten/bcs@1.1.0 + +## 1.10.0 + +### Minor Changes + +- 830b8d8: Introduce new naming scheme for named packages plugin + ## 1.9.0 ### Minor Changes diff --git a/sdk/typescript/README.md b/sdk/typescript/README.md index b7f1b4a3d36cf..3dcc3afa5d256 100644 --- a/sdk/typescript/README.md +++ b/sdk/typescript/README.md @@ -178,7 +178,7 @@ For a primer for building transactions, refer to ```typescript import { getFullnodeUrl, SuiClient } from '@mysten/sui/client'; import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; -import { TransactionBlock } from '@mysten/sui/transactions'; +import { Transaction } from '@mysten/sui/transactions'; // Generate a new Ed25519 Keypair const keypair = new Ed25519Keypair(); @@ -186,14 +186,14 @@ const client = new SuiClient({ url: getFullnodeUrl('testnet'), }); -const tx = new TransactionBlock(); +const tx = new Transaction(); tx.transferObjects( ['0xe19739da1a701eadc21683c5b127e62b553e833e8a15a4f292f4f48b4afea3f2'], '0x1d20dcdb2bca4f508ea9613994683eb4e76e9c4ed371169677c1be02aaf0b12a', ); -const result = await client.signAndExecuteTransactionBlock({ +const result = await client.signAndExecuteTransaction({ signer: keypair, - transactionBlock: tx, + transaction: tx, }); console.log({ result }); ``` @@ -205,7 +205,7 @@ To transfer `1000` MIST to another address: ```typescript import { getFullnodeUrl, SuiClient } from '@mysten/sui/client'; import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; -import { TransactionBlock } from '@mysten/sui/transactions'; +import { Transaction } from '@mysten/sui/transactions'; // Generate a new Ed25519 Keypair const keypair = new Ed25519Keypair(); @@ -213,12 +213,12 @@ const client = new SuiClient({ url: getFullnodeUrl('testnet'), }); -const tx = new TransactionBlock(); +const tx = new Transaction(); const [coin] = tx.splitCoins(tx.gas, [1000]); tx.transferObjects([coin], keypair.getPublicKey().toSuiAddress()); -const result = await client.signAndExecuteTransactionBlock({ +const result = await client.signAndExecuteTransaction({ signer: keypair, - transactionBlock: tx, + transaction: tx, }); console.log({ result }); ``` @@ -228,7 +228,7 @@ console.log({ result }); ```typescript import { getFullnodeUrl, SuiClient } from '@mysten/sui/client'; import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; -import { TransactionBlock } from '@mysten/sui/transactions'; +import { Transaction } from '@mysten/sui/transactions'; // Generate a new Ed25519 Keypair const keypair = new Ed25519Keypair(); @@ -236,13 +236,13 @@ const client = new SuiClient({ url: getFullnodeUrl('testnet'), }); -const tx = new TransactionBlock(); +const tx = new Transaction(); tx.mergeCoins('0xe19739da1a701eadc21683c5b127e62b553e833e8a15a4f292f4f48b4afea3f2', [ '0x127a8975134a4824d9288722c4ee4fc824cd22502ab4ad9f6617f3ba19229c1b', ]); -const result = await client.signAndExecuteTransactionBlock({ +const result = await client.signAndExecuteTransaction({ signer: keypair, - transactionBlock: tx, + transaction: tx, }); console.log({ result }); ``` @@ -252,7 +252,7 @@ console.log({ result }); ```typescript import { getFullnodeUrl, SuiClient } from '@mysten/sui/client'; import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; -import { TransactionBlock } from '@mysten/sui/transactions'; +import { Transaction } from '@mysten/sui/transactions'; // Generate a new Ed25519 Keypair const keypair = new Ed25519Keypair(); @@ -260,14 +260,14 @@ const client = new SuiClient({ url: getFullnodeUrl('testnet'), }); const packageObjectId = '0x...'; -const tx = new TransactionBlock(); +const tx = new Transaction(); tx.moveCall({ target: `${packageObjectId}::nft::mint`, arguments: [tx.pure.string('Example NFT')], }); -const result = await client.signAndExecuteTransactionBlock({ +const result = await client.signAndExecuteTransaction({ signer: keypair, - transactionBlock: tx, + transaction: tx, }); console.log({ result }); ``` @@ -279,7 +279,7 @@ To publish a package: ```typescript import { getFullnodeUrl, SuiClient } from '@mysten/sui/client'; import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; -import { TransactionBlock } from '@mysten/sui/transactions'; +import { Transaction } from '@mysten/sui/transactions'; const { execSync } = require('child_process'); // Generate a new Ed25519 Keypair @@ -292,15 +292,15 @@ const { modules, dependencies } = JSON.parse( encoding: 'utf-8', }), ); -const tx = new TransactionBlock(); +const tx = new Transaction(); const [upgradeCap] = tx.publish({ modules, dependencies, }); -tx.transferObjects([upgradeCap], await client.getAddress()); -const result = await client.signAndExecuteTransactionBlock({ +tx.transferObjects([upgradeCap], keypair.toSuiAddress()); +const result = await client.signAndExecuteTransaction({ signer: keypair, - transactionBlock: tx, + transaction: tx, }); console.log({ result }); ``` diff --git a/sdk/typescript/package.json b/sdk/typescript/package.json index b287469fa8c37..27dcf7f82960f 100644 --- a/sdk/typescript/package.json +++ b/sdk/typescript/package.json @@ -3,7 +3,7 @@ "author": "Mysten Labs ", "description": "Sui TypeScript API(Work in Progress)", "homepage": "https://sdk.mystenlabs.com", - "version": "1.9.0", + "version": "1.13.0", "license": "Apache-2.0", "sideEffects": false, "files": [ diff --git a/sdk/typescript/scripts/update-graphql-schemas.ts b/sdk/typescript/scripts/update-graphql-schemas.ts index 4a3843ede5cbc..ac25d84d451c6 100644 --- a/sdk/typescript/scripts/update-graphql-schemas.ts +++ b/sdk/typescript/scripts/update-graphql-schemas.ts @@ -46,6 +46,10 @@ for (const release of result) { for (const { minorVersion, schema } of releasesByVersion.values()) { const res = await fetch(schema); + + if (!res.ok) { + throw new Error(`Failed to fetch schema from ${schema}`); + } const schemaContent = await res.text(); const filePath = resolve( @@ -73,7 +77,9 @@ for (const { minorVersion, schema } of releasesByVersion.values()) { `.trimStart(), ); - execSync(`pnpm run generate-schema -c ${resolve(filePath, '..', 'tsconfig.tada.json')}`); + execSync(`pnpm run generate-schema -c ${resolve(filePath, '..', 'tsconfig.tada.json')}`, { + stdio: 'inherit', + }); await mkdir(resolve(filePath, '../../../schemas', minorVersion), { recursive: true }); await writeFile( diff --git a/sdk/typescript/src/bcs/bcs.ts b/sdk/typescript/src/bcs/bcs.ts index 54a8b63e3468f..615d3f0bbf9d0 100644 --- a/sdk/typescript/src/bcs/bcs.ts +++ b/sdk/typescript/src/bcs/bcs.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import type { BcsType, BcsTypeOptions } from '@mysten/bcs'; -import { bcs, fromB58, fromB64, fromHEX, toB58, toB64, toHEX } from '@mysten/bcs'; +import { bcs, fromBase58, fromBase64, fromHex, toBase58, toBase64, toHex } from '@mysten/bcs'; import { isValidSuiAddress, normalizeSuiAddress, SUI_ADDRESS_LENGTH } from '../utils/sui-types.js'; import { TypeTagSerializer } from './type-tag-serializer.js'; @@ -29,22 +29,22 @@ function optionEnum>(type: T) { export const Address = bcs.bytes(SUI_ADDRESS_LENGTH).transform({ validate: (val) => { - const address = typeof val === 'string' ? val : toHEX(val); + const address = typeof val === 'string' ? val : toHex(val); if (!address || !isValidSuiAddress(normalizeSuiAddress(address))) { throw new Error(`Invalid Sui address ${address}`); } }, input: (val: string | Uint8Array) => - typeof val === 'string' ? fromHEX(normalizeSuiAddress(val)) : val, - output: (val) => normalizeSuiAddress(toHEX(val)), + typeof val === 'string' ? fromHex(normalizeSuiAddress(val)) : val, + output: (val) => normalizeSuiAddress(toHex(val)), }); export const ObjectDigest = bcs.vector(bcs.u8()).transform({ name: 'ObjectDigest', - input: (value: string) => fromB58(value), - output: (value) => toB58(new Uint8Array(value)), + input: (value: string) => fromBase58(value), + output: (value) => toBase58(new Uint8Array(value)), validate: (value) => { - if (fromB58(value).length !== 32) { + if (fromBase58(value).length !== 32) { throw new Error('ObjectDigest must be 32 bytes'); } }, @@ -71,8 +71,8 @@ export const ObjectArg = bcs.enum('ObjectArg', { export const CallArg = bcs.enum('CallArg', { Pure: bcs.struct('Pure', { bytes: bcs.vector(bcs.u8()).transform({ - input: (val: string | Uint8Array) => (typeof val === 'string' ? fromB64(val) : val), - output: (val) => toB64(new Uint8Array(val)), + input: (val: string | Uint8Array) => (typeof val === 'string' ? fromBase64(val) : val), + output: (val) => toBase64(new Uint8Array(val)), }), }), Object: ObjectArg, @@ -147,8 +147,8 @@ export const Command = bcs.enum('Command', { Publish: bcs.struct('Publish', { modules: bcs.vector( bcs.vector(bcs.u8()).transform({ - input: (val: string | Uint8Array) => (typeof val === 'string' ? fromB64(val) : val), - output: (val) => toB64(new Uint8Array(val)), + input: (val: string | Uint8Array) => (typeof val === 'string' ? fromBase64(val) : val), + output: (val) => toBase64(new Uint8Array(val)), }), ), dependencies: bcs.vector(Address), @@ -175,8 +175,8 @@ export const Command = bcs.enum('Command', { Upgrade: bcs.struct('Upgrade', { modules: bcs.vector( bcs.vector(bcs.u8()).transform({ - input: (val: string | Uint8Array) => (typeof val === 'string' ? fromB64(val) : val), - output: (val) => toB64(new Uint8Array(val)), + input: (val: string | Uint8Array) => (typeof val === 'string' ? fromBase64(val) : val), + output: (val) => toBase64(new Uint8Array(val)), }), ), dependencies: bcs.vector(Address), @@ -286,8 +286,8 @@ export const MultiSig = bcs.struct('MultiSig', { }); export const base64String = bcs.vector(bcs.u8()).transform({ - input: (val: string | Uint8Array) => (typeof val === 'string' ? fromB64(val) : val), - output: (val) => toB64(new Uint8Array(val)), + input: (val: string | Uint8Array) => (typeof val === 'string' ? fromBase64(val) : val), + output: (val) => toBase64(new Uint8Array(val)), }); export const SenderSignedTransaction = bcs.struct('SenderSignedTransaction', { diff --git a/sdk/typescript/src/bcs/effects.ts b/sdk/typescript/src/bcs/effects.ts index 37827116ff3cd..944a011fcf93e 100644 --- a/sdk/typescript/src/bcs/effects.ts +++ b/sdk/typescript/src/bcs/effects.ts @@ -105,6 +105,18 @@ const ExecutionFailureStatus = bcs.enum('ExecutionFailureStatus', { SuiMoveVerificationTimedout: null, SharedObjectOperationNotAllowed: null, InputObjectDeleted: null, + ExecutionCancelledDueToSharedObjectCongestion: bcs.struct( + 'ExecutionCancelledDueToSharedObjectCongestion', + { + congestedObjects: bcs.vector(Address), + }, + ), + AddressDeniedForCoin: bcs.struct('AddressDeniedForCoin', { + address: Address, + coinType: bcs.string(), + }), + CoinTypeGlobalPause: bcs.struct('CoinTypeGlobalPause', { coinType: bcs.string() }), + ExecutionCancelledDueToRandomnessUnavailable: null, }); const ExecutionStatus = bcs.enum('ExecutionStatus', { diff --git a/sdk/typescript/src/client/client.ts b/sdk/typescript/src/client/client.ts index c425f9f3d2148..6ae32e9f513b5 100644 --- a/sdk/typescript/src/client/client.ts +++ b/sdk/typescript/src/client/client.ts @@ -1,6 +1,6 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB58, toB64, toHEX } from '@mysten/bcs'; +import { fromBase58, toBase64, toHex } from '@mysten/bcs'; import type { Signer } from '../cryptography/index.js'; import type { Transaction } from '../transactions/index.js'; @@ -415,7 +415,7 @@ export class SuiClient { const result: SuiTransactionBlockResponse = await this.transport.request({ method: 'sui_executeTransactionBlock', params: [ - typeof transactionBlock === 'string' ? transactionBlock : toB64(transactionBlock), + typeof transactionBlock === 'string' ? transactionBlock : toBase64(transactionBlock), Array.isArray(signature) ? signature : [signature], options, ], @@ -580,7 +580,7 @@ export class SuiClient { let devInspectTxBytes; if (isTransaction(input.transactionBlock)) { input.transactionBlock.setSenderIfNotSet(input.sender); - devInspectTxBytes = toB64( + devInspectTxBytes = toBase64( await input.transactionBlock.build({ client: this, onlyTransactionKind: true, @@ -589,7 +589,7 @@ export class SuiClient { } else if (typeof input.transactionBlock === 'string') { devInspectTxBytes = input.transactionBlock; } else if (input.transactionBlock instanceof Uint8Array) { - devInspectTxBytes = toB64(input.transactionBlock); + devInspectTxBytes = toBase64(input.transactionBlock); } else { throw new Error('Unknown transaction block format.'); } @@ -611,7 +611,7 @@ export class SuiClient { params: [ typeof input.transactionBlock === 'string' ? input.transactionBlock - : toB64(input.transactionBlock), + : toBase64(input.transactionBlock), ], }); } @@ -743,8 +743,8 @@ export class SuiClient { // TODO: Migrate this to `sui_getChainIdentifier` once it is widely available. async getChainIdentifier(): Promise { const checkpoint = await this.getCheckpoint({ id: '0' }); - const bytes = fromB58(checkpoint.digest); - return toHEX(bytes.slice(0, 4)); + const bytes = fromBase58(checkpoint.digest); + return toHex(bytes.slice(0, 4)); } async resolveNameServiceAddress(input: ResolveNameServiceAddressParams): Promise { diff --git a/sdk/typescript/src/client/types/generated.ts b/sdk/typescript/src/client/types/generated.ts index 461fe8e3ae6ad..021527e2a1963 100644 --- a/sdk/typescript/src/client/types/generated.ts +++ b/sdk/typescript/src/client/types/generated.ts @@ -290,8 +290,7 @@ export type SuiEventFilter = Or: [SuiEventFilter, SuiEventFilter]; }; /** - * Unique ID of a Sui Event, the ID is a combination of tx seq number and event seq number, the ID is - * local to this particular fullnode and will be different from other fullnode. + * Unique ID of a Sui Event, the ID is a combination of transaction digest and event seq number. */ export interface EventId { eventSeq: string; diff --git a/sdk/typescript/src/cryptography/keypair.ts b/sdk/typescript/src/cryptography/keypair.ts index 75aa3dcbb429a..bc04b940565dc 100644 --- a/sdk/typescript/src/cryptography/keypair.ts +++ b/sdk/typescript/src/cryptography/keypair.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { bcs, toB64 } from '@mysten/bcs'; +import { bcs, toBase64 } from '@mysten/bcs'; import { blake2b } from '@noble/hashes/blake2b'; import { bech32 } from 'bech32'; @@ -47,7 +47,7 @@ export abstract class Signer { return { signature, - bytes: toB64(bytes), + bytes: toBase64(bytes), }; } /** @@ -66,7 +66,7 @@ export abstract class Signer { ); return { - bytes: toB64(bytes), + bytes: toBase64(bytes), signature, }; } diff --git a/sdk/typescript/src/cryptography/mnemonics.ts b/sdk/typescript/src/cryptography/mnemonics.ts index 48544686c6c25..feb5aa2cd90a0 100644 --- a/sdk/typescript/src/cryptography/mnemonics.ts +++ b/sdk/typescript/src/cryptography/mnemonics.ts @@ -1,6 +1,6 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { toHEX } from '@mysten/bcs'; +import { toHex } from '@mysten/bcs'; import { mnemonicToSeedSync as bip39MnemonicToSeedSync } from '@scure/bip39'; /** @@ -45,5 +45,5 @@ export function mnemonicToSeed(mnemonics: string): Uint8Array { * @param mnemonics 12 words string split by spaces. */ export function mnemonicToSeedHex(mnemonics: string): string { - return toHEX(mnemonicToSeed(mnemonics)); + return toHex(mnemonicToSeed(mnemonics)); } diff --git a/sdk/typescript/src/cryptography/publickey.ts b/sdk/typescript/src/cryptography/publickey.ts index 41bb51ceae64d..c4ada671b3aa5 100644 --- a/sdk/typescript/src/cryptography/publickey.ts +++ b/sdk/typescript/src/cryptography/publickey.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { toB64 } from '@mysten/bcs'; +import { toBase64 } from '@mysten/bcs'; import { blake2b } from '@noble/hashes/blake2b'; import { bytesToHex } from '@noble/hashes/utils'; @@ -45,7 +45,7 @@ export abstract class PublicKey { * Return the base-64 representation of the public key */ toBase64() { - return toB64(this.toRawBytes()); + return toBase64(this.toRawBytes()); } toString(): never { @@ -61,7 +61,7 @@ export abstract class PublicKey { */ toSuiPublicKey(): string { const bytes = this.toSuiBytes(); - return toB64(bytes); + return toBase64(bytes); } verifyWithIntent( diff --git a/sdk/typescript/src/cryptography/signature.ts b/sdk/typescript/src/cryptography/signature.ts index ef586e73fad16..ed75a9f30e3fd 100644 --- a/sdk/typescript/src/cryptography/signature.ts +++ b/sdk/typescript/src/cryptography/signature.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64, toB64 } from '@mysten/bcs'; +import { fromBase64, toBase64 } from '@mysten/bcs'; import { bcs } from '../bcs/index.js'; import type { MultiSigStruct } from '../multisig/publickey.js'; @@ -42,14 +42,14 @@ export function toSerializedSignature({ serializedSignature.set([SIGNATURE_SCHEME_TO_FLAG[signatureScheme]]); serializedSignature.set(signature, 1); serializedSignature.set(pubKeyBytes, 1 + signature.length); - return toB64(serializedSignature); + return toBase64(serializedSignature); } /** * Decodes a serialized signature into its constituent components: the signature scheme, the actual signature, and the public key */ export function parseSerializedSignature(serializedSignature: string) { - const bytes = fromB64(serializedSignature); + const bytes = fromBase64(serializedSignature); const signatureScheme = SIGNATURE_FLAG_TO_SCHEME[bytes[0] as keyof typeof SIGNATURE_FLAG_TO_SCHEME]; diff --git a/sdk/typescript/src/graphql/generated/2024-01/tada-env.d.ts b/sdk/typescript/src/graphql/generated/2024-01/tada-env.d.ts deleted file mode 100644 index 7ca490c5c882b..0000000000000 --- a/sdk/typescript/src/graphql/generated/2024-01/tada-env.d.ts +++ /dev/null @@ -1,202 +0,0 @@ -/* eslint-disable */ -/* prettier-ignore */ - -/** An IntrospectionQuery representation of your schema. - * - * @remarks - * This is an introspection of your schema saved as a file by GraphQLSP. - * It will automatically be used by `gql.tada` to infer the types of your GraphQL documents. - * If you need to reuse this data or update your `scalars`, update `tadaOutputLocation` to - * instead save to a .ts instead of a .d.ts file. - */ -export type introspection = { - query: 'Query'; - mutation: 'Mutation'; - subscription: never; - types: { - 'ActiveJwk': { kind: 'OBJECT'; name: 'ActiveJwk'; fields: { 'iss': { name: 'iss'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'kid': { name: 'kid'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'kty': { name: 'kty'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'e': { name: 'e'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'n': { name: 'n'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'alg': { name: 'alg'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'epoch': { name: 'epoch'; type: { kind: 'OBJECT'; name: 'Epoch'; ofType: null; } }; }; }; - 'String': unknown; - 'ActiveJwkConnection': { kind: 'OBJECT'; name: 'ActiveJwkConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ActiveJwkEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ActiveJwk'; ofType: null; }; }; }; } }; }; }; - 'ActiveJwkEdge': { kind: 'OBJECT'; name: 'ActiveJwkEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ActiveJwk'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'Address': { kind: 'OBJECT'; name: 'Address'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'objects': { name: 'objects'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveObjectConnection'; ofType: null; }; } }; 'balance': { name: 'balance'; type: { kind: 'OBJECT'; name: 'Balance'; ofType: null; } }; 'balances': { name: 'balances'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BalanceConnection'; ofType: null; }; } }; 'coins': { name: 'coins'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CoinConnection'; ofType: null; }; } }; 'stakedSuis': { name: 'stakedSuis'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'StakedSuiConnection'; ofType: null; }; } }; 'defaultSuinsName': { name: 'defaultSuinsName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'suinsRegistrations': { name: 'suinsRegistrations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'SuinsRegistrationConnection'; ofType: null; }; } }; 'transactionBlocks': { name: 'transactionBlocks'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionBlockConnection'; ofType: null; }; } }; }; }; - 'Int': unknown; - 'AddressConnection': { kind: 'OBJECT'; name: 'AddressConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'AddressEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Address'; ofType: null; }; }; }; } }; }; }; - 'AddressEdge': { kind: 'OBJECT'; name: 'AddressEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Address'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'AddressOwner': { kind: 'OBJECT'; name: 'AddressOwner'; fields: { 'owner': { name: 'owner'; type: { kind: 'OBJECT'; name: 'Owner'; ofType: null; } }; }; }; - 'AddressTransactionBlockRelationship': { kind: 'ENUM'; name: 'AddressTransactionBlockRelationship'; type: 'SIGN' | 'RECV'; }; - 'AuthenticatorStateCreateTransaction': { kind: 'OBJECT'; name: 'AuthenticatorStateCreateTransaction'; fields: { '_': { name: '_'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; }; }; - 'Boolean': unknown; - 'AuthenticatorStateExpireTransaction': { kind: 'OBJECT'; name: 'AuthenticatorStateExpireTransaction'; fields: { 'minEpoch': { name: 'minEpoch'; type: { kind: 'OBJECT'; name: 'Epoch'; ofType: null; } }; 'authenticatorObjInitialSharedVersion': { name: 'authenticatorObjInitialSharedVersion'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; - 'AuthenticatorStateUpdateTransaction': { kind: 'OBJECT'; name: 'AuthenticatorStateUpdateTransaction'; fields: { 'epoch': { name: 'epoch'; type: { kind: 'OBJECT'; name: 'Epoch'; ofType: null; } }; 'round': { name: 'round'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'newActiveJwks': { name: 'newActiveJwks'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ActiveJwkConnection'; ofType: null; }; } }; 'authenticatorObjInitialSharedVersion': { name: 'authenticatorObjInitialSharedVersion'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; - 'AvailableRange': { kind: 'OBJECT'; name: 'AvailableRange'; fields: { 'first': { name: 'first'; type: { kind: 'OBJECT'; name: 'Checkpoint'; ofType: null; } }; 'last': { name: 'last'; type: { kind: 'OBJECT'; name: 'Checkpoint'; ofType: null; } }; }; }; - 'Balance': { kind: 'OBJECT'; name: 'Balance'; fields: { 'coinType': { name: 'coinType'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveType'; ofType: null; }; } }; 'coinObjectCount': { name: 'coinObjectCount'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'totalBalance': { name: 'totalBalance'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; }; }; - 'BalanceChange': { kind: 'OBJECT'; name: 'BalanceChange'; fields: { 'owner': { name: 'owner'; type: { kind: 'OBJECT'; name: 'Owner'; ofType: null; } }; 'coinType': { name: 'coinType'; type: { kind: 'OBJECT'; name: 'MoveType'; ofType: null; } }; 'amount': { name: 'amount'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; }; }; - 'BalanceChangeConnection': { kind: 'OBJECT'; name: 'BalanceChangeConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BalanceChangeEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BalanceChange'; ofType: null; }; }; }; } }; }; }; - 'BalanceChangeEdge': { kind: 'OBJECT'; name: 'BalanceChangeEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BalanceChange'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'BalanceConnection': { kind: 'OBJECT'; name: 'BalanceConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BalanceEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Balance'; ofType: null; }; }; }; } }; }; }; - 'BalanceEdge': { kind: 'OBJECT'; name: 'BalanceEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Balance'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'Base64': unknown; - 'BigInt': unknown; - 'ChangeEpochTransaction': { kind: 'OBJECT'; name: 'ChangeEpochTransaction'; fields: { 'epoch': { name: 'epoch'; type: { kind: 'OBJECT'; name: 'Epoch'; ofType: null; } }; 'protocolVersion': { name: 'protocolVersion'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'storageCharge': { name: 'storageCharge'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'computationCharge': { name: 'computationCharge'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'storageRebate': { name: 'storageRebate'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'nonRefundableStorageFee': { name: 'nonRefundableStorageFee'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'startTimestamp': { name: 'startTimestamp'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'systemPackages': { name: 'systemPackages'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MovePackageConnection'; ofType: null; }; } }; }; }; - 'Checkpoint': { kind: 'OBJECT'; name: 'Checkpoint'; fields: { 'digest': { name: 'digest'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'sequenceNumber': { name: 'sequenceNumber'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'timestamp': { name: 'timestamp'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'validatorSignatures': { name: 'validatorSignatures'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Base64'; ofType: null; }; } }; 'previousCheckpointDigest': { name: 'previousCheckpointDigest'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'networkTotalTransactions': { name: 'networkTotalTransactions'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'rollingGasSummary': { name: 'rollingGasSummary'; type: { kind: 'OBJECT'; name: 'GasCostSummary'; ofType: null; } }; 'epoch': { name: 'epoch'; type: { kind: 'OBJECT'; name: 'Epoch'; ofType: null; } }; 'transactionBlocks': { name: 'transactionBlocks'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionBlockConnection'; ofType: null; }; } }; }; }; - 'CheckpointConnection': { kind: 'OBJECT'; name: 'CheckpointConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CheckpointEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Checkpoint'; ofType: null; }; }; }; } }; }; }; - 'CheckpointEdge': { kind: 'OBJECT'; name: 'CheckpointEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Checkpoint'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'CheckpointId': { kind: 'INPUT_OBJECT'; name: 'CheckpointId'; inputFields: [{ name: 'digest'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'sequenceNumber'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }]; }; - 'Coin': { kind: 'OBJECT'; name: 'Coin'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'objects': { name: 'objects'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveObjectConnection'; ofType: null; }; } }; 'balance': { name: 'balance'; type: { kind: 'OBJECT'; name: 'Balance'; ofType: null; } }; 'balances': { name: 'balances'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BalanceConnection'; ofType: null; }; } }; 'coins': { name: 'coins'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CoinConnection'; ofType: null; }; } }; 'stakedSuis': { name: 'stakedSuis'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'StakedSuiConnection'; ofType: null; }; } }; 'defaultSuinsName': { name: 'defaultSuinsName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'suinsRegistrations': { name: 'suinsRegistrations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'SuinsRegistrationConnection'; ofType: null; }; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'status': { name: 'status'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ObjectKind'; ofType: null; }; } }; 'digest': { name: 'digest'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'owner': { name: 'owner'; type: { kind: 'UNION'; name: 'ObjectOwner'; ofType: null; } }; 'previousTransactionBlock': { name: 'previousTransactionBlock'; type: { kind: 'OBJECT'; name: 'TransactionBlock'; ofType: null; } }; 'storageRebate': { name: 'storageRebate'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'receivedTransactionBlocks': { name: 'receivedTransactionBlocks'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionBlockConnection'; ofType: null; }; } }; 'bcs': { name: 'bcs'; type: { kind: 'SCALAR'; name: 'Base64'; ofType: null; } }; 'contents': { name: 'contents'; type: { kind: 'OBJECT'; name: 'MoveValue'; ofType: null; } }; 'hasPublicTransfer': { name: 'hasPublicTransfer'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'display': { name: 'display'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DisplayEntry'; ofType: null; }; }; } }; 'dynamicField': { name: 'dynamicField'; type: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; } }; 'dynamicObjectField': { name: 'dynamicObjectField'; type: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; } }; 'dynamicFields': { name: 'dynamicFields'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DynamicFieldConnection'; ofType: null; }; } }; 'coinBalance': { name: 'coinBalance'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; }; }; - 'CoinConnection': { kind: 'OBJECT'; name: 'CoinConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CoinEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Coin'; ofType: null; }; }; }; } }; }; }; - 'CoinDenyListStateCreateTransaction': { kind: 'OBJECT'; name: 'CoinDenyListStateCreateTransaction'; fields: { '_': { name: '_'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; }; }; - 'CoinEdge': { kind: 'OBJECT'; name: 'CoinEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Coin'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'CoinMetadata': { kind: 'OBJECT'; name: 'CoinMetadata'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'objects': { name: 'objects'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveObjectConnection'; ofType: null; }; } }; 'balance': { name: 'balance'; type: { kind: 'OBJECT'; name: 'Balance'; ofType: null; } }; 'balances': { name: 'balances'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BalanceConnection'; ofType: null; }; } }; 'coins': { name: 'coins'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CoinConnection'; ofType: null; }; } }; 'stakedSuis': { name: 'stakedSuis'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'StakedSuiConnection'; ofType: null; }; } }; 'defaultSuinsName': { name: 'defaultSuinsName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'suinsRegistrations': { name: 'suinsRegistrations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'SuinsRegistrationConnection'; ofType: null; }; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'status': { name: 'status'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ObjectKind'; ofType: null; }; } }; 'digest': { name: 'digest'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'owner': { name: 'owner'; type: { kind: 'UNION'; name: 'ObjectOwner'; ofType: null; } }; 'previousTransactionBlock': { name: 'previousTransactionBlock'; type: { kind: 'OBJECT'; name: 'TransactionBlock'; ofType: null; } }; 'storageRebate': { name: 'storageRebate'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'receivedTransactionBlocks': { name: 'receivedTransactionBlocks'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionBlockConnection'; ofType: null; }; } }; 'bcs': { name: 'bcs'; type: { kind: 'SCALAR'; name: 'Base64'; ofType: null; } }; 'contents': { name: 'contents'; type: { kind: 'OBJECT'; name: 'MoveValue'; ofType: null; } }; 'hasPublicTransfer': { name: 'hasPublicTransfer'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'display': { name: 'display'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DisplayEntry'; ofType: null; }; }; } }; 'dynamicField': { name: 'dynamicField'; type: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; } }; 'dynamicObjectField': { name: 'dynamicObjectField'; type: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; } }; 'dynamicFields': { name: 'dynamicFields'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DynamicFieldConnection'; ofType: null; }; } }; 'decimals': { name: 'decimals'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'symbol': { name: 'symbol'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'description': { name: 'description'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'iconUrl': { name: 'iconUrl'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'supply': { name: 'supply'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; }; }; - 'ConsensusCommitPrologueTransaction': { kind: 'OBJECT'; name: 'ConsensusCommitPrologueTransaction'; fields: { 'epoch': { name: 'epoch'; type: { kind: 'OBJECT'; name: 'Epoch'; ofType: null; } }; 'round': { name: 'round'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'commitTimestamp': { name: 'commitTimestamp'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'consensusCommitDigest': { name: 'consensusCommitDigest'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; - 'DateTime': unknown; - 'DependencyConnection': { kind: 'OBJECT'; name: 'DependencyConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DependencyEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionBlock'; ofType: null; }; }; }; } }; }; }; - 'DependencyEdge': { kind: 'OBJECT'; name: 'DependencyEdge'; fields: { 'node': { name: 'node'; type: { kind: 'OBJECT'; name: 'TransactionBlock'; ofType: null; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'DisplayEntry': { kind: 'OBJECT'; name: 'DisplayEntry'; fields: { 'key': { name: 'key'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'value': { name: 'value'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'error': { name: 'error'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; - 'DomainFormat': { kind: 'ENUM'; name: 'DomainFormat'; type: 'AT' | 'DOT'; }; - 'DryRunEffect': { kind: 'OBJECT'; name: 'DryRunEffect'; fields: { 'mutatedReferences': { name: 'mutatedReferences'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DryRunMutation'; ofType: null; }; }; } }; 'returnValues': { name: 'returnValues'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DryRunReturn'; ofType: null; }; }; } }; }; }; - 'DryRunMutation': { kind: 'OBJECT'; name: 'DryRunMutation'; fields: { 'input': { name: 'input'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'TransactionArgument'; ofType: null; }; } }; 'type': { name: 'type'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveType'; ofType: null; }; } }; 'bcs': { name: 'bcs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Base64'; ofType: null; }; } }; }; }; - 'DryRunResult': { kind: 'OBJECT'; name: 'DryRunResult'; fields: { 'error': { name: 'error'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'results': { name: 'results'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DryRunEffect'; ofType: null; }; }; } }; 'transaction': { name: 'transaction'; type: { kind: 'OBJECT'; name: 'TransactionBlock'; ofType: null; } }; }; }; - 'DryRunReturn': { kind: 'OBJECT'; name: 'DryRunReturn'; fields: { 'type': { name: 'type'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveType'; ofType: null; }; } }; 'bcs': { name: 'bcs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Base64'; ofType: null; }; } }; }; }; - 'DynamicField': { kind: 'OBJECT'; name: 'DynamicField'; fields: { 'name': { name: 'name'; type: { kind: 'OBJECT'; name: 'MoveValue'; ofType: null; } }; 'value': { name: 'value'; type: { kind: 'UNION'; name: 'DynamicFieldValue'; ofType: null; } }; }; }; - 'DynamicFieldConnection': { kind: 'OBJECT'; name: 'DynamicFieldConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DynamicFieldEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; }; }; }; } }; }; }; - 'DynamicFieldEdge': { kind: 'OBJECT'; name: 'DynamicFieldEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'DynamicFieldName': { kind: 'INPUT_OBJECT'; name: 'DynamicFieldName'; inputFields: [{ name: 'type'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }, { name: 'bcs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Base64'; ofType: null; }; }; defaultValue: null }]; }; - 'DynamicFieldValue': { kind: 'UNION'; name: 'DynamicFieldValue'; fields: {}; possibleTypes: 'MoveObject' | 'MoveValue'; }; - 'EndOfEpochTransaction': { kind: 'OBJECT'; name: 'EndOfEpochTransaction'; fields: { 'transactions': { name: 'transactions'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'EndOfEpochTransactionKindConnection'; ofType: null; }; } }; }; }; - 'EndOfEpochTransactionKind': { kind: 'UNION'; name: 'EndOfEpochTransactionKind'; fields: {}; possibleTypes: 'ChangeEpochTransaction' | 'AuthenticatorStateCreateTransaction' | 'AuthenticatorStateExpireTransaction' | 'RandomnessStateCreateTransaction' | 'CoinDenyListStateCreateTransaction'; }; - 'EndOfEpochTransactionKindConnection': { kind: 'OBJECT'; name: 'EndOfEpochTransactionKindConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'EndOfEpochTransactionKindEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'EndOfEpochTransactionKind'; ofType: null; }; }; }; } }; }; }; - 'EndOfEpochTransactionKindEdge': { kind: 'OBJECT'; name: 'EndOfEpochTransactionKindEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'EndOfEpochTransactionKind'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'Epoch': { kind: 'OBJECT'; name: 'Epoch'; fields: { 'epochId': { name: 'epochId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'referenceGasPrice': { name: 'referenceGasPrice'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'validatorSet': { name: 'validatorSet'; type: { kind: 'OBJECT'; name: 'ValidatorSet'; ofType: null; } }; 'startTimestamp': { name: 'startTimestamp'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; }; } }; 'endTimestamp': { name: 'endTimestamp'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'totalCheckpoints': { name: 'totalCheckpoints'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'totalTransactions': { name: 'totalTransactions'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'totalGasFees': { name: 'totalGasFees'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'totalStakeRewards': { name: 'totalStakeRewards'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'totalStakeSubsidies': { name: 'totalStakeSubsidies'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'fundSize': { name: 'fundSize'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'netInflow': { name: 'netInflow'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'fundInflow': { name: 'fundInflow'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'fundOutflow': { name: 'fundOutflow'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'protocolConfigs': { name: 'protocolConfigs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ProtocolConfigs'; ofType: null; }; } }; 'storageFund': { name: 'storageFund'; type: { kind: 'OBJECT'; name: 'StorageFund'; ofType: null; } }; 'safeMode': { name: 'safeMode'; type: { kind: 'OBJECT'; name: 'SafeMode'; ofType: null; } }; 'systemStateVersion': { name: 'systemStateVersion'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'systemParameters': { name: 'systemParameters'; type: { kind: 'OBJECT'; name: 'SystemParameters'; ofType: null; } }; 'systemStakeSubsidy': { name: 'systemStakeSubsidy'; type: { kind: 'OBJECT'; name: 'StakeSubsidy'; ofType: null; } }; 'liveObjectSetDigest': { name: 'liveObjectSetDigest'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'checkpoints': { name: 'checkpoints'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CheckpointConnection'; ofType: null; }; } }; 'transactionBlocks': { name: 'transactionBlocks'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionBlockConnection'; ofType: null; }; } }; }; }; - 'Event': { kind: 'OBJECT'; name: 'Event'; fields: { 'sendingModule': { name: 'sendingModule'; type: { kind: 'OBJECT'; name: 'MoveModule'; ofType: null; } }; 'sender': { name: 'sender'; type: { kind: 'OBJECT'; name: 'Address'; ofType: null; } }; 'timestamp': { name: 'timestamp'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'type': { name: 'type'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveType'; ofType: null; }; } }; 'bcs': { name: 'bcs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Base64'; ofType: null; }; } }; 'data': { name: 'data'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'MoveData'; ofType: null; }; } }; 'json': { name: 'json'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; } }; }; }; - 'EventConnection': { kind: 'OBJECT'; name: 'EventConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'EventEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Event'; ofType: null; }; }; }; } }; }; }; - 'EventEdge': { kind: 'OBJECT'; name: 'EventEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Event'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'EventFilter': { kind: 'INPUT_OBJECT'; name: 'EventFilter'; inputFields: [{ name: 'sender'; type: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; defaultValue: null }, { name: 'transactionDigest'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'emittingModule'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'eventType'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }]; }; - 'ExecutionResult': { kind: 'OBJECT'; name: 'ExecutionResult'; fields: { 'errors': { name: 'errors'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; } }; 'effects': { name: 'effects'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionBlockEffects'; ofType: null; }; } }; }; }; - 'ExecutionStatus': { kind: 'ENUM'; name: 'ExecutionStatus'; type: 'SUCCESS' | 'FAILURE'; }; - 'Feature': { kind: 'ENUM'; name: 'Feature'; type: 'ANALYTICS' | 'COINS' | 'DYNAMIC_FIELDS' | 'NAME_SERVICE' | 'SUBSCRIPTIONS' | 'SYSTEM_STATE'; }; - 'GasCoin': { kind: 'OBJECT'; name: 'GasCoin'; fields: { '_': { name: '_'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; }; }; - 'GasCostSummary': { kind: 'OBJECT'; name: 'GasCostSummary'; fields: { 'computationCost': { name: 'computationCost'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'storageCost': { name: 'storageCost'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'storageRebate': { name: 'storageRebate'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'nonRefundableStorageFee': { name: 'nonRefundableStorageFee'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; }; }; - 'GasEffects': { kind: 'OBJECT'; name: 'GasEffects'; fields: { 'gasObject': { name: 'gasObject'; type: { kind: 'OBJECT'; name: 'Object'; ofType: null; } }; 'gasSummary': { name: 'gasSummary'; type: { kind: 'OBJECT'; name: 'GasCostSummary'; ofType: null; } }; }; }; - 'GasInput': { kind: 'OBJECT'; name: 'GasInput'; fields: { 'gasSponsor': { name: 'gasSponsor'; type: { kind: 'OBJECT'; name: 'Address'; ofType: null; } }; 'gasPayment': { name: 'gasPayment'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ObjectConnection'; ofType: null; }; } }; 'gasPrice': { name: 'gasPrice'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'gasBudget': { name: 'gasBudget'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; }; }; - 'GenesisTransaction': { kind: 'OBJECT'; name: 'GenesisTransaction'; fields: { 'objects': { name: 'objects'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ObjectConnection'; ofType: null; }; } }; }; }; - 'IMoveObject': { kind: 'INTERFACE'; name: 'IMoveObject'; fields: { 'contents': { name: 'contents'; type: { kind: 'OBJECT'; name: 'MoveValue'; ofType: null; } }; 'hasPublicTransfer': { name: 'hasPublicTransfer'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'display': { name: 'display'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DisplayEntry'; ofType: null; }; }; } }; 'dynamicField': { name: 'dynamicField'; type: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; } }; 'dynamicObjectField': { name: 'dynamicObjectField'; type: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; } }; 'dynamicFields': { name: 'dynamicFields'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DynamicFieldConnection'; ofType: null; }; } }; }; possibleTypes: 'Coin' | 'CoinMetadata' | 'MoveObject' | 'StakedSui' | 'SuinsRegistration'; }; - 'IObject': { kind: 'INTERFACE'; name: 'IObject'; fields: { 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'status': { name: 'status'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ObjectKind'; ofType: null; }; } }; 'digest': { name: 'digest'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'owner': { name: 'owner'; type: { kind: 'UNION'; name: 'ObjectOwner'; ofType: null; } }; 'previousTransactionBlock': { name: 'previousTransactionBlock'; type: { kind: 'OBJECT'; name: 'TransactionBlock'; ofType: null; } }; 'storageRebate': { name: 'storageRebate'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'receivedTransactionBlocks': { name: 'receivedTransactionBlocks'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionBlockConnection'; ofType: null; }; } }; 'bcs': { name: 'bcs'; type: { kind: 'SCALAR'; name: 'Base64'; ofType: null; } }; }; possibleTypes: 'Coin' | 'CoinMetadata' | 'MoveObject' | 'MovePackage' | 'Object' | 'StakedSui' | 'SuinsRegistration'; }; - 'IOwner': { kind: 'INTERFACE'; name: 'IOwner'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'objects': { name: 'objects'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveObjectConnection'; ofType: null; }; } }; 'balance': { name: 'balance'; type: { kind: 'OBJECT'; name: 'Balance'; ofType: null; } }; 'balances': { name: 'balances'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BalanceConnection'; ofType: null; }; } }; 'coins': { name: 'coins'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CoinConnection'; ofType: null; }; } }; 'stakedSuis': { name: 'stakedSuis'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'StakedSuiConnection'; ofType: null; }; } }; 'defaultSuinsName': { name: 'defaultSuinsName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'suinsRegistrations': { name: 'suinsRegistrations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'SuinsRegistrationConnection'; ofType: null; }; } }; }; possibleTypes: 'Address' | 'Coin' | 'CoinMetadata' | 'MoveObject' | 'MovePackage' | 'Object' | 'Owner' | 'StakedSui' | 'SuinsRegistration'; }; - 'Immutable': { kind: 'OBJECT'; name: 'Immutable'; fields: { '_': { name: '_'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; }; }; - 'Input': { kind: 'OBJECT'; name: 'Input'; fields: { 'ix': { name: 'ix'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; - 'JSON': unknown; - 'Linkage': { kind: 'OBJECT'; name: 'Linkage'; fields: { 'originalId': { name: 'originalId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'upgradedId': { name: 'upgradedId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; - 'MakeMoveVecTransaction': { kind: 'OBJECT'; name: 'MakeMoveVecTransaction'; fields: { 'type': { name: 'type'; type: { kind: 'OBJECT'; name: 'MoveType'; ofType: null; } }; 'elements': { name: 'elements'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'TransactionArgument'; ofType: null; }; }; }; } }; }; }; - 'MergeCoinsTransaction': { kind: 'OBJECT'; name: 'MergeCoinsTransaction'; fields: { 'coin': { name: 'coin'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'TransactionArgument'; ofType: null; }; } }; 'coins': { name: 'coins'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'TransactionArgument'; ofType: null; }; }; }; } }; }; }; - 'MoveAbility': { kind: 'ENUM'; name: 'MoveAbility'; type: 'COPY' | 'DROP' | 'KEY' | 'STORE'; }; - 'MoveCallTransaction': { kind: 'OBJECT'; name: 'MoveCallTransaction'; fields: { 'package': { name: 'package'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'module': { name: 'module'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'functionName': { name: 'functionName'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'function': { name: 'function'; type: { kind: 'OBJECT'; name: 'MoveFunction'; ofType: null; } }; 'typeArguments': { name: 'typeArguments'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveType'; ofType: null; }; }; }; } }; 'arguments': { name: 'arguments'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'TransactionArgument'; ofType: null; }; }; }; } }; }; }; - 'MoveData': unknown; - 'MoveField': { kind: 'OBJECT'; name: 'MoveField'; fields: { 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'type': { name: 'type'; type: { kind: 'OBJECT'; name: 'OpenMoveType'; ofType: null; } }; }; }; - 'MoveFunction': { kind: 'OBJECT'; name: 'MoveFunction'; fields: { 'module': { name: 'module'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveModule'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'visibility': { name: 'visibility'; type: { kind: 'ENUM'; name: 'MoveVisibility'; ofType: null; } }; 'isEntry': { name: 'isEntry'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'typeParameters': { name: 'typeParameters'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveFunctionTypeParameter'; ofType: null; }; }; } }; 'parameters': { name: 'parameters'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'OpenMoveType'; ofType: null; }; }; } }; 'return': { name: 'return'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'OpenMoveType'; ofType: null; }; }; } }; }; }; - 'MoveFunctionConnection': { kind: 'OBJECT'; name: 'MoveFunctionConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveFunctionEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveFunction'; ofType: null; }; }; }; } }; }; }; - 'MoveFunctionEdge': { kind: 'OBJECT'; name: 'MoveFunctionEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveFunction'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'MoveFunctionTypeParameter': { kind: 'OBJECT'; name: 'MoveFunctionTypeParameter'; fields: { 'constraints': { name: 'constraints'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'MoveAbility'; ofType: null; }; }; }; } }; }; }; - 'MoveModule': { kind: 'OBJECT'; name: 'MoveModule'; fields: { 'package': { name: 'package'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MovePackage'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'fileFormatVersion': { name: 'fileFormatVersion'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'friends': { name: 'friends'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveModuleConnection'; ofType: null; }; } }; 'struct': { name: 'struct'; type: { kind: 'OBJECT'; name: 'MoveStruct'; ofType: null; } }; 'structs': { name: 'structs'; type: { kind: 'OBJECT'; name: 'MoveStructConnection'; ofType: null; } }; 'function': { name: 'function'; type: { kind: 'OBJECT'; name: 'MoveFunction'; ofType: null; } }; 'functions': { name: 'functions'; type: { kind: 'OBJECT'; name: 'MoveFunctionConnection'; ofType: null; } }; 'bytes': { name: 'bytes'; type: { kind: 'SCALAR'; name: 'Base64'; ofType: null; } }; 'disassembly': { name: 'disassembly'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; - 'MoveModuleConnection': { kind: 'OBJECT'; name: 'MoveModuleConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveModuleEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveModule'; ofType: null; }; }; }; } }; }; }; - 'MoveModuleEdge': { kind: 'OBJECT'; name: 'MoveModuleEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveModule'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'MoveObject': { kind: 'OBJECT'; name: 'MoveObject'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'objects': { name: 'objects'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveObjectConnection'; ofType: null; }; } }; 'balance': { name: 'balance'; type: { kind: 'OBJECT'; name: 'Balance'; ofType: null; } }; 'balances': { name: 'balances'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BalanceConnection'; ofType: null; }; } }; 'coins': { name: 'coins'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CoinConnection'; ofType: null; }; } }; 'stakedSuis': { name: 'stakedSuis'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'StakedSuiConnection'; ofType: null; }; } }; 'defaultSuinsName': { name: 'defaultSuinsName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'suinsRegistrations': { name: 'suinsRegistrations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'SuinsRegistrationConnection'; ofType: null; }; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'status': { name: 'status'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ObjectKind'; ofType: null; }; } }; 'digest': { name: 'digest'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'owner': { name: 'owner'; type: { kind: 'UNION'; name: 'ObjectOwner'; ofType: null; } }; 'previousTransactionBlock': { name: 'previousTransactionBlock'; type: { kind: 'OBJECT'; name: 'TransactionBlock'; ofType: null; } }; 'storageRebate': { name: 'storageRebate'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'receivedTransactionBlocks': { name: 'receivedTransactionBlocks'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionBlockConnection'; ofType: null; }; } }; 'bcs': { name: 'bcs'; type: { kind: 'SCALAR'; name: 'Base64'; ofType: null; } }; 'contents': { name: 'contents'; type: { kind: 'OBJECT'; name: 'MoveValue'; ofType: null; } }; 'hasPublicTransfer': { name: 'hasPublicTransfer'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'display': { name: 'display'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DisplayEntry'; ofType: null; }; }; } }; 'dynamicField': { name: 'dynamicField'; type: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; } }; 'dynamicObjectField': { name: 'dynamicObjectField'; type: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; } }; 'dynamicFields': { name: 'dynamicFields'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DynamicFieldConnection'; ofType: null; }; } }; 'asCoin': { name: 'asCoin'; type: { kind: 'OBJECT'; name: 'Coin'; ofType: null; } }; 'asStakedSui': { name: 'asStakedSui'; type: { kind: 'OBJECT'; name: 'StakedSui'; ofType: null; } }; 'asCoinMetadata': { name: 'asCoinMetadata'; type: { kind: 'OBJECT'; name: 'CoinMetadata'; ofType: null; } }; 'asSuinsRegistration': { name: 'asSuinsRegistration'; type: { kind: 'OBJECT'; name: 'SuinsRegistration'; ofType: null; } }; }; }; - 'MoveObjectConnection': { kind: 'OBJECT'; name: 'MoveObjectConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveObjectEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveObject'; ofType: null; }; }; }; } }; }; }; - 'MoveObjectEdge': { kind: 'OBJECT'; name: 'MoveObjectEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveObject'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'MovePackage': { kind: 'OBJECT'; name: 'MovePackage'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'objects': { name: 'objects'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveObjectConnection'; ofType: null; }; } }; 'balance': { name: 'balance'; type: { kind: 'OBJECT'; name: 'Balance'; ofType: null; } }; 'balances': { name: 'balances'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BalanceConnection'; ofType: null; }; } }; 'coins': { name: 'coins'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CoinConnection'; ofType: null; }; } }; 'stakedSuis': { name: 'stakedSuis'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'StakedSuiConnection'; ofType: null; }; } }; 'defaultSuinsName': { name: 'defaultSuinsName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'suinsRegistrations': { name: 'suinsRegistrations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'SuinsRegistrationConnection'; ofType: null; }; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'status': { name: 'status'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ObjectKind'; ofType: null; }; } }; 'digest': { name: 'digest'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'owner': { name: 'owner'; type: { kind: 'UNION'; name: 'ObjectOwner'; ofType: null; } }; 'previousTransactionBlock': { name: 'previousTransactionBlock'; type: { kind: 'OBJECT'; name: 'TransactionBlock'; ofType: null; } }; 'storageRebate': { name: 'storageRebate'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'receivedTransactionBlocks': { name: 'receivedTransactionBlocks'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionBlockConnection'; ofType: null; }; } }; 'bcs': { name: 'bcs'; type: { kind: 'SCALAR'; name: 'Base64'; ofType: null; } }; 'module': { name: 'module'; type: { kind: 'OBJECT'; name: 'MoveModule'; ofType: null; } }; 'modules': { name: 'modules'; type: { kind: 'OBJECT'; name: 'MoveModuleConnection'; ofType: null; } }; 'linkage': { name: 'linkage'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Linkage'; ofType: null; }; }; } }; 'typeOrigins': { name: 'typeOrigins'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TypeOrigin'; ofType: null; }; }; } }; 'moduleBcs': { name: 'moduleBcs'; type: { kind: 'SCALAR'; name: 'Base64'; ofType: null; } }; }; }; - 'MovePackageConnection': { kind: 'OBJECT'; name: 'MovePackageConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MovePackageEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MovePackage'; ofType: null; }; }; }; } }; }; }; - 'MovePackageEdge': { kind: 'OBJECT'; name: 'MovePackageEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MovePackage'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'MoveStruct': { kind: 'OBJECT'; name: 'MoveStruct'; fields: { 'module': { name: 'module'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveModule'; ofType: null; }; } }; 'name': { name: 'name'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'abilities': { name: 'abilities'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'MoveAbility'; ofType: null; }; }; } }; 'typeParameters': { name: 'typeParameters'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveStructTypeParameter'; ofType: null; }; }; } }; 'fields': { name: 'fields'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveField'; ofType: null; }; }; } }; }; }; - 'MoveStructConnection': { kind: 'OBJECT'; name: 'MoveStructConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveStructEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveStruct'; ofType: null; }; }; }; } }; }; }; - 'MoveStructEdge': { kind: 'OBJECT'; name: 'MoveStructEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveStruct'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'MoveStructTypeParameter': { kind: 'OBJECT'; name: 'MoveStructTypeParameter'; fields: { 'constraints': { name: 'constraints'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'MoveAbility'; ofType: null; }; }; }; } }; 'isPhantom': { name: 'isPhantom'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; }; }; - 'MoveType': { kind: 'OBJECT'; name: 'MoveType'; fields: { 'repr': { name: 'repr'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'signature': { name: 'signature'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'MoveTypeSignature'; ofType: null; }; } }; 'layout': { name: 'layout'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'MoveTypeLayout'; ofType: null; }; } }; 'abilities': { name: 'abilities'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'MoveAbility'; ofType: null; }; }; }; } }; }; }; - 'MoveTypeLayout': unknown; - 'MoveTypeSignature': unknown; - 'MoveValue': { kind: 'OBJECT'; name: 'MoveValue'; fields: { 'type': { name: 'type'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveType'; ofType: null; }; } }; 'bcs': { name: 'bcs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Base64'; ofType: null; }; } }; 'data': { name: 'data'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'MoveData'; ofType: null; }; } }; 'json': { name: 'json'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'JSON'; ofType: null; }; } }; }; }; - 'MoveVisibility': { kind: 'ENUM'; name: 'MoveVisibility'; type: 'PUBLIC' | 'PRIVATE' | 'FRIEND'; }; - 'Mutation': { kind: 'OBJECT'; name: 'Mutation'; fields: { 'executeTransactionBlock': { name: 'executeTransactionBlock'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ExecutionResult'; ofType: null; }; } }; }; }; - 'Object': { kind: 'OBJECT'; name: 'Object'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'objects': { name: 'objects'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveObjectConnection'; ofType: null; }; } }; 'balance': { name: 'balance'; type: { kind: 'OBJECT'; name: 'Balance'; ofType: null; } }; 'balances': { name: 'balances'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BalanceConnection'; ofType: null; }; } }; 'coins': { name: 'coins'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CoinConnection'; ofType: null; }; } }; 'stakedSuis': { name: 'stakedSuis'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'StakedSuiConnection'; ofType: null; }; } }; 'defaultSuinsName': { name: 'defaultSuinsName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'suinsRegistrations': { name: 'suinsRegistrations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'SuinsRegistrationConnection'; ofType: null; }; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'status': { name: 'status'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ObjectKind'; ofType: null; }; } }; 'digest': { name: 'digest'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'owner': { name: 'owner'; type: { kind: 'UNION'; name: 'ObjectOwner'; ofType: null; } }; 'previousTransactionBlock': { name: 'previousTransactionBlock'; type: { kind: 'OBJECT'; name: 'TransactionBlock'; ofType: null; } }; 'storageRebate': { name: 'storageRebate'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'receivedTransactionBlocks': { name: 'receivedTransactionBlocks'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionBlockConnection'; ofType: null; }; } }; 'bcs': { name: 'bcs'; type: { kind: 'SCALAR'; name: 'Base64'; ofType: null; } }; 'display': { name: 'display'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DisplayEntry'; ofType: null; }; }; } }; 'dynamicField': { name: 'dynamicField'; type: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; } }; 'dynamicObjectField': { name: 'dynamicObjectField'; type: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; } }; 'dynamicFields': { name: 'dynamicFields'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DynamicFieldConnection'; ofType: null; }; } }; 'asMoveObject': { name: 'asMoveObject'; type: { kind: 'OBJECT'; name: 'MoveObject'; ofType: null; } }; 'asMovePackage': { name: 'asMovePackage'; type: { kind: 'OBJECT'; name: 'MovePackage'; ofType: null; } }; }; }; - 'ObjectChange': { kind: 'OBJECT'; name: 'ObjectChange'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'inputState': { name: 'inputState'; type: { kind: 'OBJECT'; name: 'Object'; ofType: null; } }; 'outputState': { name: 'outputState'; type: { kind: 'OBJECT'; name: 'Object'; ofType: null; } }; 'idCreated': { name: 'idCreated'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'idDeleted': { name: 'idDeleted'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; }; }; - 'ObjectChangeConnection': { kind: 'OBJECT'; name: 'ObjectChangeConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ObjectChangeEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ObjectChange'; ofType: null; }; }; }; } }; }; }; - 'ObjectChangeEdge': { kind: 'OBJECT'; name: 'ObjectChangeEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ObjectChange'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'ObjectConnection': { kind: 'OBJECT'; name: 'ObjectConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ObjectEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Object'; ofType: null; }; }; }; } }; }; }; - 'ObjectEdge': { kind: 'OBJECT'; name: 'ObjectEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Object'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'ObjectFilter': { kind: 'INPUT_OBJECT'; name: 'ObjectFilter'; inputFields: [{ name: 'type'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'owner'; type: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; defaultValue: null }, { name: 'objectIds'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; }; }; defaultValue: null }, { name: 'objectKeys'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ObjectKey'; ofType: null; }; }; }; defaultValue: null }]; }; - 'ObjectKey': { kind: 'INPUT_OBJECT'; name: 'ObjectKey'; inputFields: [{ name: 'objectId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; }; defaultValue: null }, { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; }; defaultValue: null }]; }; - 'ObjectKind': { kind: 'ENUM'; name: 'ObjectKind'; type: 'NOT_INDEXED' | 'INDEXED' | 'WRAPPED_OR_DELETED'; }; - 'ObjectOwner': { kind: 'UNION'; name: 'ObjectOwner'; fields: {}; possibleTypes: 'Immutable' | 'Shared' | 'Parent' | 'AddressOwner'; }; - 'ObjectRef': { kind: 'INPUT_OBJECT'; name: 'ObjectRef'; inputFields: [{ name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; }; defaultValue: null }, { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; }; defaultValue: null }, { name: 'digest'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; defaultValue: null }]; }; - 'OpenMoveType': { kind: 'OBJECT'; name: 'OpenMoveType'; fields: { 'signature': { name: 'signature'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'OpenMoveTypeSignature'; ofType: null; }; } }; 'repr': { name: 'repr'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'OpenMoveTypeSignature': unknown; - 'OwnedOrImmutable': { kind: 'OBJECT'; name: 'OwnedOrImmutable'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'digest': { name: 'digest'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'object': { name: 'object'; type: { kind: 'OBJECT'; name: 'Object'; ofType: null; } }; }; }; - 'Owner': { kind: 'OBJECT'; name: 'Owner'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'objects': { name: 'objects'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveObjectConnection'; ofType: null; }; } }; 'balance': { name: 'balance'; type: { kind: 'OBJECT'; name: 'Balance'; ofType: null; } }; 'balances': { name: 'balances'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BalanceConnection'; ofType: null; }; } }; 'coins': { name: 'coins'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CoinConnection'; ofType: null; }; } }; 'stakedSuis': { name: 'stakedSuis'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'StakedSuiConnection'; ofType: null; }; } }; 'defaultSuinsName': { name: 'defaultSuinsName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'suinsRegistrations': { name: 'suinsRegistrations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'SuinsRegistrationConnection'; ofType: null; }; } }; 'asAddress': { name: 'asAddress'; type: { kind: 'OBJECT'; name: 'Address'; ofType: null; } }; 'asObject': { name: 'asObject'; type: { kind: 'OBJECT'; name: 'Object'; ofType: null; } }; 'dynamicField': { name: 'dynamicField'; type: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; } }; 'dynamicObjectField': { name: 'dynamicObjectField'; type: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; } }; 'dynamicFields': { name: 'dynamicFields'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DynamicFieldConnection'; ofType: null; }; } }; }; }; - 'PageInfo': { kind: 'OBJECT'; name: 'PageInfo'; fields: { 'hasPreviousPage': { name: 'hasPreviousPage'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'hasNextPage': { name: 'hasNextPage'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'startCursor': { name: 'startCursor'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'endCursor': { name: 'endCursor'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; - 'Parent': { kind: 'OBJECT'; name: 'Parent'; fields: { 'parent': { name: 'parent'; type: { kind: 'OBJECT'; name: 'Object'; ofType: null; } }; }; }; - 'ProgrammableTransaction': { kind: 'UNION'; name: 'ProgrammableTransaction'; fields: {}; possibleTypes: 'MoveCallTransaction' | 'TransferObjectsTransaction' | 'SplitCoinsTransaction' | 'MergeCoinsTransaction' | 'PublishTransaction' | 'UpgradeTransaction' | 'MakeMoveVecTransaction'; }; - 'ProgrammableTransactionBlock': { kind: 'OBJECT'; name: 'ProgrammableTransactionBlock'; fields: { 'inputs': { name: 'inputs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionInputConnection'; ofType: null; }; } }; 'transactions': { name: 'transactions'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ProgrammableTransactionConnection'; ofType: null; }; } }; }; }; - 'ProgrammableTransactionConnection': { kind: 'OBJECT'; name: 'ProgrammableTransactionConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ProgrammableTransactionEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'ProgrammableTransaction'; ofType: null; }; }; }; } }; }; }; - 'ProgrammableTransactionEdge': { kind: 'OBJECT'; name: 'ProgrammableTransactionEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'ProgrammableTransaction'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'ProtocolConfigAttr': { kind: 'OBJECT'; name: 'ProtocolConfigAttr'; fields: { 'key': { name: 'key'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'value': { name: 'value'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; - 'ProtocolConfigFeatureFlag': { kind: 'OBJECT'; name: 'ProtocolConfigFeatureFlag'; fields: { 'key': { name: 'key'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'value': { name: 'value'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; }; }; - 'ProtocolConfigs': { kind: 'OBJECT'; name: 'ProtocolConfigs'; fields: { 'protocolVersion': { name: 'protocolVersion'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'featureFlags': { name: 'featureFlags'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ProtocolConfigFeatureFlag'; ofType: null; }; }; }; } }; 'configs': { name: 'configs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ProtocolConfigAttr'; ofType: null; }; }; }; } }; 'config': { name: 'config'; type: { kind: 'OBJECT'; name: 'ProtocolConfigAttr'; ofType: null; } }; 'featureFlag': { name: 'featureFlag'; type: { kind: 'OBJECT'; name: 'ProtocolConfigFeatureFlag'; ofType: null; } }; }; }; - 'PublishTransaction': { kind: 'OBJECT'; name: 'PublishTransaction'; fields: { 'modules': { name: 'modules'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Base64'; ofType: null; }; }; }; } }; 'dependencies': { name: 'dependencies'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; }; }; } }; }; }; - 'Pure': { kind: 'OBJECT'; name: 'Pure'; fields: { 'bytes': { name: 'bytes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Base64'; ofType: null; }; } }; }; }; - 'Query': { kind: 'OBJECT'; name: 'Query'; fields: { 'chainIdentifier': { name: 'chainIdentifier'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'availableRange': { name: 'availableRange'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'AvailableRange'; ofType: null; }; } }; 'serviceConfig': { name: 'serviceConfig'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ServiceConfig'; ofType: null; }; } }; 'dryRunTransactionBlock': { name: 'dryRunTransactionBlock'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DryRunResult'; ofType: null; }; } }; 'owner': { name: 'owner'; type: { kind: 'OBJECT'; name: 'Owner'; ofType: null; } }; 'object': { name: 'object'; type: { kind: 'OBJECT'; name: 'Object'; ofType: null; } }; 'address': { name: 'address'; type: { kind: 'OBJECT'; name: 'Address'; ofType: null; } }; 'type': { name: 'type'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveType'; ofType: null; }; } }; 'epoch': { name: 'epoch'; type: { kind: 'OBJECT'; name: 'Epoch'; ofType: null; } }; 'checkpoint': { name: 'checkpoint'; type: { kind: 'OBJECT'; name: 'Checkpoint'; ofType: null; } }; 'transactionBlock': { name: 'transactionBlock'; type: { kind: 'OBJECT'; name: 'TransactionBlock'; ofType: null; } }; 'coins': { name: 'coins'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CoinConnection'; ofType: null; }; } }; 'checkpoints': { name: 'checkpoints'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CheckpointConnection'; ofType: null; }; } }; 'transactionBlocks': { name: 'transactionBlocks'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionBlockConnection'; ofType: null; }; } }; 'events': { name: 'events'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'EventConnection'; ofType: null; }; } }; 'objects': { name: 'objects'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ObjectConnection'; ofType: null; }; } }; 'protocolConfig': { name: 'protocolConfig'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ProtocolConfigs'; ofType: null; }; } }; 'resolveSuinsAddress': { name: 'resolveSuinsAddress'; type: { kind: 'OBJECT'; name: 'Address'; ofType: null; } }; 'coinMetadata': { name: 'coinMetadata'; type: { kind: 'OBJECT'; name: 'CoinMetadata'; ofType: null; } }; 'verifyZkloginSignature': { name: 'verifyZkloginSignature'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ZkLoginVerifyResult'; ofType: null; }; } }; }; }; - 'RandomnessStateCreateTransaction': { kind: 'OBJECT'; name: 'RandomnessStateCreateTransaction'; fields: { '_': { name: '_'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; }; }; - 'RandomnessStateUpdateTransaction': { kind: 'OBJECT'; name: 'RandomnessStateUpdateTransaction'; fields: { 'epoch': { name: 'epoch'; type: { kind: 'OBJECT'; name: 'Epoch'; ofType: null; } }; 'randomnessRound': { name: 'randomnessRound'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'randomBytes': { name: 'randomBytes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Base64'; ofType: null; }; } }; 'randomnessObjInitialSharedVersion': { name: 'randomnessObjInitialSharedVersion'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; - 'Receiving': { kind: 'OBJECT'; name: 'Receiving'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'digest': { name: 'digest'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'object': { name: 'object'; type: { kind: 'OBJECT'; name: 'Object'; ofType: null; } }; }; }; - 'Result': { kind: 'OBJECT'; name: 'Result'; fields: { 'cmd': { name: 'cmd'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'ix': { name: 'ix'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; }; }; - 'SafeMode': { kind: 'OBJECT'; name: 'SafeMode'; fields: { 'enabled': { name: 'enabled'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'gasSummary': { name: 'gasSummary'; type: { kind: 'OBJECT'; name: 'GasCostSummary'; ofType: null; } }; }; }; - 'ServiceConfig': { kind: 'OBJECT'; name: 'ServiceConfig'; fields: { 'isEnabled': { name: 'isEnabled'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'enabledFeatures': { name: 'enabledFeatures'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'Feature'; ofType: null; }; }; }; } }; 'maxQueryDepth': { name: 'maxQueryDepth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxQueryNodes': { name: 'maxQueryNodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxOutputNodes': { name: 'maxOutputNodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxDbQueryCost': { name: 'maxDbQueryCost'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'defaultPageSize': { name: 'defaultPageSize'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxPageSize': { name: 'maxPageSize'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'requestTimeoutMs': { name: 'requestTimeoutMs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxQueryPayloadSize': { name: 'maxQueryPayloadSize'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxTypeArgumentDepth': { name: 'maxTypeArgumentDepth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxTypeArgumentWidth': { name: 'maxTypeArgumentWidth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxTypeNodes': { name: 'maxTypeNodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxMoveValueDepth': { name: 'maxMoveValueDepth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; - 'Shared': { kind: 'OBJECT'; name: 'Shared'; fields: { 'initialSharedVersion': { name: 'initialSharedVersion'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; - 'SharedInput': { kind: 'OBJECT'; name: 'SharedInput'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'initialSharedVersion': { name: 'initialSharedVersion'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'mutable': { name: 'mutable'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; }; }; - 'SharedObjectCancelled': { kind: 'OBJECT'; name: 'SharedObjectCancelled'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; - 'SharedObjectDelete': { kind: 'OBJECT'; name: 'SharedObjectDelete'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'mutable': { name: 'mutable'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; }; }; - 'SharedObjectRead': { kind: 'OBJECT'; name: 'SharedObjectRead'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'digest': { name: 'digest'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'object': { name: 'object'; type: { kind: 'OBJECT'; name: 'Object'; ofType: null; } }; }; }; - 'SplitCoinsTransaction': { kind: 'OBJECT'; name: 'SplitCoinsTransaction'; fields: { 'coin': { name: 'coin'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'TransactionArgument'; ofType: null; }; } }; 'amounts': { name: 'amounts'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'TransactionArgument'; ofType: null; }; }; }; } }; }; }; - 'StakeStatus': { kind: 'ENUM'; name: 'StakeStatus'; type: 'ACTIVE' | 'PENDING' | 'UNSTAKED'; }; - 'StakeSubsidy': { kind: 'OBJECT'; name: 'StakeSubsidy'; fields: { 'balance': { name: 'balance'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'distributionCounter': { name: 'distributionCounter'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'currentDistributionAmount': { name: 'currentDistributionAmount'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'periodLength': { name: 'periodLength'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'decreaseRate': { name: 'decreaseRate'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; }; }; - 'StakedSui': { kind: 'OBJECT'; name: 'StakedSui'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'objects': { name: 'objects'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveObjectConnection'; ofType: null; }; } }; 'balance': { name: 'balance'; type: { kind: 'OBJECT'; name: 'Balance'; ofType: null; } }; 'balances': { name: 'balances'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BalanceConnection'; ofType: null; }; } }; 'coins': { name: 'coins'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CoinConnection'; ofType: null; }; } }; 'stakedSuis': { name: 'stakedSuis'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'StakedSuiConnection'; ofType: null; }; } }; 'defaultSuinsName': { name: 'defaultSuinsName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'suinsRegistrations': { name: 'suinsRegistrations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'SuinsRegistrationConnection'; ofType: null; }; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'status': { name: 'status'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ObjectKind'; ofType: null; }; } }; 'digest': { name: 'digest'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'owner': { name: 'owner'; type: { kind: 'UNION'; name: 'ObjectOwner'; ofType: null; } }; 'previousTransactionBlock': { name: 'previousTransactionBlock'; type: { kind: 'OBJECT'; name: 'TransactionBlock'; ofType: null; } }; 'storageRebate': { name: 'storageRebate'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'receivedTransactionBlocks': { name: 'receivedTransactionBlocks'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionBlockConnection'; ofType: null; }; } }; 'bcs': { name: 'bcs'; type: { kind: 'SCALAR'; name: 'Base64'; ofType: null; } }; 'contents': { name: 'contents'; type: { kind: 'OBJECT'; name: 'MoveValue'; ofType: null; } }; 'hasPublicTransfer': { name: 'hasPublicTransfer'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'display': { name: 'display'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DisplayEntry'; ofType: null; }; }; } }; 'dynamicField': { name: 'dynamicField'; type: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; } }; 'dynamicObjectField': { name: 'dynamicObjectField'; type: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; } }; 'dynamicFields': { name: 'dynamicFields'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DynamicFieldConnection'; ofType: null; }; } }; 'stakeStatus': { name: 'stakeStatus'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'StakeStatus'; ofType: null; }; } }; 'activatedEpoch': { name: 'activatedEpoch'; type: { kind: 'OBJECT'; name: 'Epoch'; ofType: null; } }; 'requestedEpoch': { name: 'requestedEpoch'; type: { kind: 'OBJECT'; name: 'Epoch'; ofType: null; } }; 'poolId': { name: 'poolId'; type: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; } }; 'principal': { name: 'principal'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'estimatedReward': { name: 'estimatedReward'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; }; }; - 'StakedSuiConnection': { kind: 'OBJECT'; name: 'StakedSuiConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'StakedSuiEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'StakedSui'; ofType: null; }; }; }; } }; }; }; - 'StakedSuiEdge': { kind: 'OBJECT'; name: 'StakedSuiEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'StakedSui'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'StorageFund': { kind: 'OBJECT'; name: 'StorageFund'; fields: { 'totalObjectStorageRebates': { name: 'totalObjectStorageRebates'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'nonRefundableBalance': { name: 'nonRefundableBalance'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; }; }; - 'SuiAddress': unknown; - 'SuinsRegistration': { kind: 'OBJECT'; name: 'SuinsRegistration'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'objects': { name: 'objects'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'MoveObjectConnection'; ofType: null; }; } }; 'balance': { name: 'balance'; type: { kind: 'OBJECT'; name: 'Balance'; ofType: null; } }; 'balances': { name: 'balances'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BalanceConnection'; ofType: null; }; } }; 'coins': { name: 'coins'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'CoinConnection'; ofType: null; }; } }; 'stakedSuis': { name: 'stakedSuis'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'StakedSuiConnection'; ofType: null; }; } }; 'defaultSuinsName': { name: 'defaultSuinsName'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'suinsRegistrations': { name: 'suinsRegistrations'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'SuinsRegistrationConnection'; ofType: null; }; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'status': { name: 'status'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'ObjectKind'; ofType: null; }; } }; 'digest': { name: 'digest'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'owner': { name: 'owner'; type: { kind: 'UNION'; name: 'ObjectOwner'; ofType: null; } }; 'previousTransactionBlock': { name: 'previousTransactionBlock'; type: { kind: 'OBJECT'; name: 'TransactionBlock'; ofType: null; } }; 'storageRebate': { name: 'storageRebate'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'receivedTransactionBlocks': { name: 'receivedTransactionBlocks'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionBlockConnection'; ofType: null; }; } }; 'bcs': { name: 'bcs'; type: { kind: 'SCALAR'; name: 'Base64'; ofType: null; } }; 'contents': { name: 'contents'; type: { kind: 'OBJECT'; name: 'MoveValue'; ofType: null; } }; 'hasPublicTransfer': { name: 'hasPublicTransfer'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'display': { name: 'display'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DisplayEntry'; ofType: null; }; }; } }; 'dynamicField': { name: 'dynamicField'; type: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; } }; 'dynamicObjectField': { name: 'dynamicObjectField'; type: { kind: 'OBJECT'; name: 'DynamicField'; ofType: null; } }; 'dynamicFields': { name: 'dynamicFields'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DynamicFieldConnection'; ofType: null; }; } }; 'domain': { name: 'domain'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'SuinsRegistrationConnection': { kind: 'OBJECT'; name: 'SuinsRegistrationConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'SuinsRegistrationEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'SuinsRegistration'; ofType: null; }; }; }; } }; }; }; - 'SuinsRegistrationEdge': { kind: 'OBJECT'; name: 'SuinsRegistrationEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'SuinsRegistration'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'SystemParameters': { kind: 'OBJECT'; name: 'SystemParameters'; fields: { 'durationMs': { name: 'durationMs'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'stakeSubsidyStartEpoch': { name: 'stakeSubsidyStartEpoch'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'minValidatorCount': { name: 'minValidatorCount'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'maxValidatorCount': { name: 'maxValidatorCount'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'minValidatorJoiningStake': { name: 'minValidatorJoiningStake'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'validatorLowStakeThreshold': { name: 'validatorLowStakeThreshold'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'validatorVeryLowStakeThreshold': { name: 'validatorVeryLowStakeThreshold'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'validatorLowStakeGracePeriod': { name: 'validatorLowStakeGracePeriod'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; }; }; - 'TransactionArgument': { kind: 'UNION'; name: 'TransactionArgument'; fields: {}; possibleTypes: 'GasCoin' | 'Input' | 'Result'; }; - 'TransactionBlock': { kind: 'OBJECT'; name: 'TransactionBlock'; fields: { 'digest': { name: 'digest'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'sender': { name: 'sender'; type: { kind: 'OBJECT'; name: 'Address'; ofType: null; } }; 'gasInput': { name: 'gasInput'; type: { kind: 'OBJECT'; name: 'GasInput'; ofType: null; } }; 'kind': { name: 'kind'; type: { kind: 'UNION'; name: 'TransactionBlockKind'; ofType: null; } }; 'signatures': { name: 'signatures'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Base64'; ofType: null; }; }; } }; 'effects': { name: 'effects'; type: { kind: 'OBJECT'; name: 'TransactionBlockEffects'; ofType: null; } }; 'expiration': { name: 'expiration'; type: { kind: 'OBJECT'; name: 'Epoch'; ofType: null; } }; 'bcs': { name: 'bcs'; type: { kind: 'SCALAR'; name: 'Base64'; ofType: null; } }; }; }; - 'TransactionBlockConnection': { kind: 'OBJECT'; name: 'TransactionBlockConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionBlockEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionBlock'; ofType: null; }; }; }; } }; }; }; - 'TransactionBlockEdge': { kind: 'OBJECT'; name: 'TransactionBlockEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionBlock'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'TransactionBlockEffects': { kind: 'OBJECT'; name: 'TransactionBlockEffects'; fields: { 'transactionBlock': { name: 'transactionBlock'; type: { kind: 'OBJECT'; name: 'TransactionBlock'; ofType: null; } }; 'status': { name: 'status'; type: { kind: 'ENUM'; name: 'ExecutionStatus'; ofType: null; } }; 'lamportVersion': { name: 'lamportVersion'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'errors': { name: 'errors'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'dependencies': { name: 'dependencies'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'DependencyConnection'; ofType: null; }; } }; 'gasEffects': { name: 'gasEffects'; type: { kind: 'OBJECT'; name: 'GasEffects'; ofType: null; } }; 'unchangedSharedObjects': { name: 'unchangedSharedObjects'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UnchangedSharedObjectConnection'; ofType: null; }; } }; 'objectChanges': { name: 'objectChanges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ObjectChangeConnection'; ofType: null; }; } }; 'balanceChanges': { name: 'balanceChanges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'BalanceChangeConnection'; ofType: null; }; } }; 'events': { name: 'events'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'EventConnection'; ofType: null; }; } }; 'timestamp': { name: 'timestamp'; type: { kind: 'SCALAR'; name: 'DateTime'; ofType: null; } }; 'epoch': { name: 'epoch'; type: { kind: 'OBJECT'; name: 'Epoch'; ofType: null; } }; 'checkpoint': { name: 'checkpoint'; type: { kind: 'OBJECT'; name: 'Checkpoint'; ofType: null; } }; 'bcs': { name: 'bcs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Base64'; ofType: null; }; } }; }; }; - 'TransactionBlockFilter': { kind: 'INPUT_OBJECT'; name: 'TransactionBlockFilter'; inputFields: [{ name: 'function'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; }; defaultValue: null }, { name: 'kind'; type: { kind: 'ENUM'; name: 'TransactionBlockKindInput'; ofType: null; }; defaultValue: null }, { name: 'afterCheckpoint'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'atCheckpoint'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'beforeCheckpoint'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'signAddress'; type: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; defaultValue: null }, { name: 'recvAddress'; type: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; defaultValue: null }, { name: 'inputObject'; type: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; defaultValue: null }, { name: 'changedObject'; type: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; defaultValue: null }, { name: 'transactionIds'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; }; defaultValue: null }]; }; - 'TransactionBlockKind': { kind: 'UNION'; name: 'TransactionBlockKind'; fields: {}; possibleTypes: 'ConsensusCommitPrologueTransaction' | 'GenesisTransaction' | 'ChangeEpochTransaction' | 'ProgrammableTransactionBlock' | 'AuthenticatorStateUpdateTransaction' | 'RandomnessStateUpdateTransaction' | 'EndOfEpochTransaction'; }; - 'TransactionBlockKindInput': { kind: 'ENUM'; name: 'TransactionBlockKindInput'; type: 'SYSTEM_TX' | 'PROGRAMMABLE_TX'; }; - 'TransactionInput': { kind: 'UNION'; name: 'TransactionInput'; fields: {}; possibleTypes: 'OwnedOrImmutable' | 'SharedInput' | 'Receiving' | 'Pure'; }; - 'TransactionInputConnection': { kind: 'OBJECT'; name: 'TransactionInputConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'TransactionInputEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'TransactionInput'; ofType: null; }; }; }; } }; }; }; - 'TransactionInputEdge': { kind: 'OBJECT'; name: 'TransactionInputEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'TransactionInput'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'TransactionMetadata': { kind: 'INPUT_OBJECT'; name: 'TransactionMetadata'; inputFields: [{ name: 'sender'; type: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; defaultValue: null }, { name: 'gasPrice'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'gasObjects'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ObjectRef'; ofType: null; }; }; }; defaultValue: null }, { name: 'gasBudget'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'gasSponsor'; type: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; defaultValue: null }]; }; - 'TransferObjectsTransaction': { kind: 'OBJECT'; name: 'TransferObjectsTransaction'; fields: { 'inputs': { name: 'inputs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'TransactionArgument'; ofType: null; }; }; }; } }; 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'TransactionArgument'; ofType: null; }; } }; }; }; - 'TypeOrigin': { kind: 'OBJECT'; name: 'TypeOrigin'; fields: { 'module': { name: 'module'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'struct': { name: 'struct'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'definingId': { name: 'definingId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; }; }; - 'UnchangedSharedObject': { kind: 'UNION'; name: 'UnchangedSharedObject'; fields: {}; possibleTypes: 'SharedObjectRead' | 'SharedObjectDelete' | 'SharedObjectCancelled'; }; - 'UnchangedSharedObjectConnection': { kind: 'OBJECT'; name: 'UnchangedSharedObjectConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UnchangedSharedObjectEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'UnchangedSharedObject'; ofType: null; }; }; }; } }; }; }; - 'UnchangedSharedObjectEdge': { kind: 'OBJECT'; name: 'UnchangedSharedObjectEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'UnchangedSharedObject'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'UpgradeTransaction': { kind: 'OBJECT'; name: 'UpgradeTransaction'; fields: { 'modules': { name: 'modules'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Base64'; ofType: null; }; }; }; } }; 'dependencies': { name: 'dependencies'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; }; }; } }; 'currentPackage': { name: 'currentPackage'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'upgradeTicket': { name: 'upgradeTicket'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'TransactionArgument'; ofType: null; }; } }; }; }; - 'Validator': { kind: 'OBJECT'; name: 'Validator'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Address'; ofType: null; }; } }; 'credentials': { name: 'credentials'; type: { kind: 'OBJECT'; name: 'ValidatorCredentials'; ofType: null; } }; 'nextEpochCredentials': { name: 'nextEpochCredentials'; type: { kind: 'OBJECT'; name: 'ValidatorCredentials'; ofType: null; } }; 'name': { name: 'name'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'description': { name: 'description'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'imageUrl': { name: 'imageUrl'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'projectUrl': { name: 'projectUrl'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'operationCap': { name: 'operationCap'; type: { kind: 'OBJECT'; name: 'MoveObject'; ofType: null; } }; 'stakingPool': { name: 'stakingPool'; type: { kind: 'OBJECT'; name: 'MoveObject'; ofType: null; } }; 'stakingPoolId': { name: 'stakingPoolId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'exchangeRates': { name: 'exchangeRates'; type: { kind: 'OBJECT'; name: 'MoveObject'; ofType: null; } }; 'exchangeRatesTable': { name: 'exchangeRatesTable'; type: { kind: 'OBJECT'; name: 'Owner'; ofType: null; } }; 'exchangeRatesSize': { name: 'exchangeRatesSize'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'stakingPoolActivationEpoch': { name: 'stakingPoolActivationEpoch'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'stakingPoolSuiBalance': { name: 'stakingPoolSuiBalance'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'rewardsPool': { name: 'rewardsPool'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'poolTokenBalance': { name: 'poolTokenBalance'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'pendingStake': { name: 'pendingStake'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'pendingTotalSuiWithdraw': { name: 'pendingTotalSuiWithdraw'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'pendingPoolTokenWithdraw': { name: 'pendingPoolTokenWithdraw'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'votingPower': { name: 'votingPower'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'gasPrice': { name: 'gasPrice'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'commissionRate': { name: 'commissionRate'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'nextEpochStake': { name: 'nextEpochStake'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'nextEpochGasPrice': { name: 'nextEpochGasPrice'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'nextEpochCommissionRate': { name: 'nextEpochCommissionRate'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'atRisk': { name: 'atRisk'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'reportRecords': { name: 'reportRecords'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'AddressConnection'; ofType: null; }; } }; 'apy': { name: 'apy'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; }; }; - 'ValidatorConnection': { kind: 'OBJECT'; name: 'ValidatorConnection'; fields: { 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ValidatorEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Validator'; ofType: null; }; }; }; } }; }; }; - 'ValidatorCredentials': { kind: 'OBJECT'; name: 'ValidatorCredentials'; fields: { 'protocolPubKey': { name: 'protocolPubKey'; type: { kind: 'SCALAR'; name: 'Base64'; ofType: null; } }; 'networkPubKey': { name: 'networkPubKey'; type: { kind: 'SCALAR'; name: 'Base64'; ofType: null; } }; 'workerPubKey': { name: 'workerPubKey'; type: { kind: 'SCALAR'; name: 'Base64'; ofType: null; } }; 'proofOfPossession': { name: 'proofOfPossession'; type: { kind: 'SCALAR'; name: 'Base64'; ofType: null; } }; 'netAddress': { name: 'netAddress'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'p2PAddress': { name: 'p2PAddress'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'primaryAddress': { name: 'primaryAddress'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; 'workerAddress': { name: 'workerAddress'; type: { kind: 'SCALAR'; name: 'String'; ofType: null; } }; }; }; - 'ValidatorEdge': { kind: 'OBJECT'; name: 'ValidatorEdge'; fields: { 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'Validator'; ofType: null; }; } }; 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'ValidatorSet': { kind: 'OBJECT'; name: 'ValidatorSet'; fields: { 'totalStake': { name: 'totalStake'; type: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; } }; 'pendingRemovals': { name: 'pendingRemovals'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; }; } }; 'pendingActiveValidatorsId': { name: 'pendingActiveValidatorsId'; type: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; } }; 'pendingActiveValidatorsSize': { name: 'pendingActiveValidatorsSize'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'stakingPoolMappingsId': { name: 'stakingPoolMappingsId'; type: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; } }; 'stakingPoolMappingsSize': { name: 'stakingPoolMappingsSize'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'inactivePoolsId': { name: 'inactivePoolsId'; type: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; } }; 'inactivePoolsSize': { name: 'inactivePoolsSize'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'validatorCandidatesId': { name: 'validatorCandidatesId'; type: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; } }; 'validatorCandidatesSize': { name: 'validatorCandidatesSize'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; 'activeValidators': { name: 'activeValidators'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'ValidatorConnection'; ofType: null; }; } }; }; }; - 'ZkLoginIntentScope': { kind: 'ENUM'; name: 'ZkLoginIntentScope'; type: 'TRANSACTION_DATA' | 'PERSONAL_MESSAGE'; }; - 'ZkLoginVerifyResult': { kind: 'OBJECT'; name: 'ZkLoginVerifyResult'; fields: { 'success': { name: 'success'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'errors': { name: 'errors'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; }; } }; }; }; - }; -}; - -import * as gqlTada from 'gql.tada'; - -declare module 'gql.tada' { - interface setupSchema { - introspection: introspection - } -} \ No newline at end of file diff --git a/sdk/typescript/src/graphql/generated/2024.1/schema.graphql b/sdk/typescript/src/graphql/generated/2024.1/schema.graphql index 2f34a67c5b7bb..e0b89727fa1d9 100644 --- a/sdk/typescript/src/graphql/generated/2024.1/schema.graphql +++ b/sdk/typescript/src/graphql/generated/2024.1/schema.graphql @@ -876,9 +876,10 @@ type DynamicField { """ name: MoveValue """ - The actual data stored in the dynamic field. - The returned dynamic field is an object if its return type is MoveObject, - in which case it is also accessible off-chain via its address. + The returned dynamic field is an object if its return type is `MoveObject`, + in which case it is also accessible off-chain via its address. Its contents + will be from the latest version that is at most equal to its parent object's + version """ value: DynamicFieldValue } @@ -3008,6 +3009,10 @@ type ServiceConfig { """ isEnabled(feature: Feature!): Boolean! """ + List the available versions for this GraphQL service. + """ + availableVersions: [String!]! + """ List of all features that are enabled on this GraphQL service. """ enabledFeatures: [Feature!]! @@ -3046,7 +3051,14 @@ type ServiceConfig { """ maxPageSize: Int! """ - Maximum time in milliseconds that will be spent to serve one request. + Maximum time in milliseconds spent waiting for a response from fullnode after issuing a + a transaction to execute. Note that the transaction may still succeed even in the case of a + timeout. Transactions are idempotent, so a transaction that times out should be resubmitted + until the network returns a definite response (success or failure, not timeout). + """ + mutationTimeoutMs: Int! + """ + Maximum time in milliseconds that will be spent to serve one query request. """ requestTimeoutMs: Int! """ @@ -3700,6 +3712,8 @@ type TransactionBlockEffects { lamportVersion: Int! """ The reason for a transaction failure, if it did fail. + If the error is a Move abort, the error message will be resolved to a human-readable form if + possible, otherwise it will fall back to displaying the abort code and location. """ errors: String """ diff --git a/sdk/typescript/src/graphql/generated/2024.1/tada-env.d.ts b/sdk/typescript/src/graphql/generated/2024.1/tada-env.d.ts index 735f1ca3ab650..ea3c5e5899b59 100644 --- a/sdk/typescript/src/graphql/generated/2024.1/tada-env.d.ts +++ b/sdk/typescript/src/graphql/generated/2024.1/tada-env.d.ts @@ -146,7 +146,7 @@ export type introspection = { 'Receiving': { kind: 'OBJECT'; name: 'Receiving'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'digest': { name: 'digest'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'object': { name: 'object'; type: { kind: 'OBJECT'; name: 'Object'; ofType: null; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; 'Result': { kind: 'OBJECT'; name: 'Result'; fields: { 'cmd': { name: 'cmd'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'ix': { name: 'ix'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; }; }; 'SafeMode': { kind: 'OBJECT'; name: 'SafeMode'; fields: { 'enabled': { name: 'enabled'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'gasSummary': { name: 'gasSummary'; type: { kind: 'OBJECT'; name: 'GasCostSummary'; ofType: null; } }; }; }; - 'ServiceConfig': { kind: 'OBJECT'; name: 'ServiceConfig'; fields: { 'defaultPageSize': { name: 'defaultPageSize'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'enabledFeatures': { name: 'enabledFeatures'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'Feature'; ofType: null; }; }; }; } }; 'isEnabled': { name: 'isEnabled'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'maxDbQueryCost': { name: 'maxDbQueryCost'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'maxMoveValueDepth': { name: 'maxMoveValueDepth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxOutputNodes': { name: 'maxOutputNodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxPageSize': { name: 'maxPageSize'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxQueryDepth': { name: 'maxQueryDepth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxQueryNodes': { name: 'maxQueryNodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxQueryPayloadSize': { name: 'maxQueryPayloadSize'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxTypeArgumentDepth': { name: 'maxTypeArgumentDepth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxTypeArgumentWidth': { name: 'maxTypeArgumentWidth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxTypeNodes': { name: 'maxTypeNodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'requestTimeoutMs': { name: 'requestTimeoutMs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'ServiceConfig': { kind: 'OBJECT'; name: 'ServiceConfig'; fields: { 'availableVersions': { name: 'availableVersions'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; }; } }; 'defaultPageSize': { name: 'defaultPageSize'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'enabledFeatures': { name: 'enabledFeatures'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'Feature'; ofType: null; }; }; }; } }; 'isEnabled': { name: 'isEnabled'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'maxDbQueryCost': { name: 'maxDbQueryCost'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'maxMoveValueDepth': { name: 'maxMoveValueDepth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxOutputNodes': { name: 'maxOutputNodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxPageSize': { name: 'maxPageSize'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxQueryDepth': { name: 'maxQueryDepth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxQueryNodes': { name: 'maxQueryNodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxQueryPayloadSize': { name: 'maxQueryPayloadSize'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxTypeArgumentDepth': { name: 'maxTypeArgumentDepth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxTypeArgumentWidth': { name: 'maxTypeArgumentWidth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxTypeNodes': { name: 'maxTypeNodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'mutationTimeoutMs': { name: 'mutationTimeoutMs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'requestTimeoutMs': { name: 'requestTimeoutMs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; 'Shared': { kind: 'OBJECT'; name: 'Shared'; fields: { 'initialSharedVersion': { name: 'initialSharedVersion'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; 'SharedInput': { kind: 'OBJECT'; name: 'SharedInput'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'initialSharedVersion': { name: 'initialSharedVersion'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'mutable': { name: 'mutable'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; }; }; 'SharedObjectDelete': { kind: 'OBJECT'; name: 'SharedObjectDelete'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'mutable': { name: 'mutable'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; diff --git a/sdk/typescript/src/graphql/generated/2024.4/schema.graphql b/sdk/typescript/src/graphql/generated/2024.4/schema.graphql index 0719c8bb2c22b..065d2c4a404f2 100644 --- a/sdk/typescript/src/graphql/generated/2024.4/schema.graphql +++ b/sdk/typescript/src/graphql/generated/2024.4/schema.graphql @@ -881,9 +881,10 @@ type DynamicField { """ name: MoveValue """ - The actual data stored in the dynamic field. - The returned dynamic field is an object if its return type is MoveObject, - in which case it is also accessible off-chain via its address. + The returned dynamic field is an object if its return type is `MoveObject`, + in which case it is also accessible off-chain via its address. Its contents + will be from the latest version that is at most equal to its parent object's + version """ value: DynamicFieldValue } @@ -3013,6 +3014,10 @@ type ServiceConfig { """ isEnabled(feature: Feature!): Boolean! """ + List the available versions for this GraphQL service. + """ + availableVersions: [String!]! + """ List of all features that are enabled on this GraphQL service. """ enabledFeatures: [Feature!]! @@ -3051,7 +3056,14 @@ type ServiceConfig { """ maxPageSize: Int! """ - Maximum time in milliseconds that will be spent to serve one request. + Maximum time in milliseconds spent waiting for a response from fullnode after issuing a + a transaction to execute. Note that the transaction may still succeed even in the case of a + timeout. Transactions are idempotent, so a transaction that times out should be resubmitted + until the network returns a definite response (success or failure, not timeout). + """ + mutationTimeoutMs: Int! + """ + Maximum time in milliseconds that will be spent to serve one query request. """ requestTimeoutMs: Int! """ @@ -3105,6 +3117,20 @@ type SharedInput { mutable: Boolean! } +""" +The transaction accpeted a shared object as input, but its execution was cancelled. +""" +type SharedObjectCancelled { + """ + ID of the shared object. + """ + address: SuiAddress! + """ + The assigned shared object version. It is a special version indicating transaction cancellation reason. + """ + version: Int! +} + """ The transaction accepted a shared object as input, but it was deleted before the transaction executed. @@ -3705,6 +3731,8 @@ type TransactionBlockEffects { lamportVersion: Int! """ The reason for a transaction failure, if it did fail. + If the error is a Move abort, the error message will be resolved to a human-readable form if + possible, otherwise it will fall back to displaying the abort code and location. """ errors: String """ @@ -3871,7 +3899,7 @@ This information is considered part of the effects, because although the transac the shared object as input, consensus must schedule it and pick the version that is actually used. """ -union UnchangedSharedObject = SharedObjectRead | SharedObjectDelete +union UnchangedSharedObject = SharedObjectRead | SharedObjectDelete | SharedObjectCancelled type UnchangedSharedObjectConnection { """ diff --git a/sdk/typescript/src/graphql/generated/2024.4/tada-env.d.ts b/sdk/typescript/src/graphql/generated/2024.4/tada-env.d.ts index 2a54a9de22716..bf470dc13c5e1 100644 --- a/sdk/typescript/src/graphql/generated/2024.4/tada-env.d.ts +++ b/sdk/typescript/src/graphql/generated/2024.4/tada-env.d.ts @@ -147,9 +147,10 @@ export type introspection = { 'Receiving': { kind: 'OBJECT'; name: 'Receiving'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'digest': { name: 'digest'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'object': { name: 'object'; type: { kind: 'OBJECT'; name: 'Object'; ofType: null; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; 'Result': { kind: 'OBJECT'; name: 'Result'; fields: { 'cmd': { name: 'cmd'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'ix': { name: 'ix'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; } }; }; }; 'SafeMode': { kind: 'OBJECT'; name: 'SafeMode'; fields: { 'enabled': { name: 'enabled'; type: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; } }; 'gasSummary': { name: 'gasSummary'; type: { kind: 'OBJECT'; name: 'GasCostSummary'; ofType: null; } }; }; }; - 'ServiceConfig': { kind: 'OBJECT'; name: 'ServiceConfig'; fields: { 'defaultPageSize': { name: 'defaultPageSize'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'enabledFeatures': { name: 'enabledFeatures'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'Feature'; ofType: null; }; }; }; } }; 'isEnabled': { name: 'isEnabled'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'maxDbQueryCost': { name: 'maxDbQueryCost'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'maxMoveValueDepth': { name: 'maxMoveValueDepth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxOutputNodes': { name: 'maxOutputNodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxPageSize': { name: 'maxPageSize'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxQueryDepth': { name: 'maxQueryDepth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxQueryNodes': { name: 'maxQueryNodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxQueryPayloadSize': { name: 'maxQueryPayloadSize'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxTypeArgumentDepth': { name: 'maxTypeArgumentDepth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxTypeArgumentWidth': { name: 'maxTypeArgumentWidth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxTypeNodes': { name: 'maxTypeNodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'requestTimeoutMs': { name: 'requestTimeoutMs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; + 'ServiceConfig': { kind: 'OBJECT'; name: 'ServiceConfig'; fields: { 'availableVersions': { name: 'availableVersions'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; }; }; } }; 'defaultPageSize': { name: 'defaultPageSize'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'enabledFeatures': { name: 'enabledFeatures'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'ENUM'; name: 'Feature'; ofType: null; }; }; }; } }; 'isEnabled': { name: 'isEnabled'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'maxDbQueryCost': { name: 'maxDbQueryCost'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'BigInt'; ofType: null; }; } }; 'maxMoveValueDepth': { name: 'maxMoveValueDepth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxOutputNodes': { name: 'maxOutputNodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxPageSize': { name: 'maxPageSize'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxQueryDepth': { name: 'maxQueryDepth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxQueryNodes': { name: 'maxQueryNodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxQueryPayloadSize': { name: 'maxQueryPayloadSize'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxTypeArgumentDepth': { name: 'maxTypeArgumentDepth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxTypeArgumentWidth': { name: 'maxTypeArgumentWidth'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'maxTypeNodes': { name: 'maxTypeNodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'mutationTimeoutMs': { name: 'mutationTimeoutMs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'requestTimeoutMs': { name: 'requestTimeoutMs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; 'Shared': { kind: 'OBJECT'; name: 'Shared'; fields: { 'initialSharedVersion': { name: 'initialSharedVersion'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; 'SharedInput': { kind: 'OBJECT'; name: 'SharedInput'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'initialSharedVersion': { name: 'initialSharedVersion'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; 'mutable': { name: 'mutable'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; }; }; + 'SharedObjectCancelled': { kind: 'OBJECT'; name: 'SharedObjectCancelled'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; 'SharedObjectDelete': { kind: 'OBJECT'; name: 'SharedObjectDelete'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'mutable': { name: 'mutable'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Boolean'; ofType: null; }; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; 'SharedObjectRead': { kind: 'OBJECT'; name: 'SharedObjectRead'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'digest': { name: 'digest'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'object': { name: 'object'; type: { kind: 'OBJECT'; name: 'Object'; ofType: null; } }; 'version': { name: 'version'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; } }; }; }; 'SplitCoinsTransaction': { kind: 'OBJECT'; name: 'SplitCoinsTransaction'; fields: { 'amounts': { name: 'amounts'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'TransactionArgument'; ofType: null; }; }; }; } }; 'coin': { name: 'coin'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'TransactionArgument'; ofType: null; }; } }; }; }; @@ -179,7 +180,7 @@ export type introspection = { 'TransactionMetadata': { kind: 'INPUT_OBJECT'; name: 'TransactionMetadata'; isOneOf: false; inputFields: [{ name: 'sender'; type: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; defaultValue: null }, { name: 'gasPrice'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'gasObjects'; type: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'INPUT_OBJECT'; name: 'ObjectRef'; ofType: null; }; }; }; defaultValue: null }, { name: 'gasBudget'; type: { kind: 'SCALAR'; name: 'Int'; ofType: null; }; defaultValue: null }, { name: 'gasSponsor'; type: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; defaultValue: null }]; }; 'TransferObjectsTransaction': { kind: 'OBJECT'; name: 'TransferObjectsTransaction'; fields: { 'address': { name: 'address'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'TransactionArgument'; ofType: null; }; } }; 'inputs': { name: 'inputs'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'TransactionArgument'; ofType: null; }; }; }; } }; }; }; 'TypeOrigin': { kind: 'OBJECT'; name: 'TypeOrigin'; fields: { 'definingId': { name: 'definingId'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'module': { name: 'module'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'struct': { name: 'struct'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; }; }; - 'UnchangedSharedObject': { kind: 'UNION'; name: 'UnchangedSharedObject'; fields: {}; possibleTypes: 'SharedObjectDelete' | 'SharedObjectRead'; }; + 'UnchangedSharedObject': { kind: 'UNION'; name: 'UnchangedSharedObject'; fields: {}; possibleTypes: 'SharedObjectCancelled' | 'SharedObjectDelete' | 'SharedObjectRead'; }; 'UnchangedSharedObjectConnection': { kind: 'OBJECT'; name: 'UnchangedSharedObjectConnection'; fields: { 'edges': { name: 'edges'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'UnchangedSharedObjectEdge'; ofType: null; }; }; }; } }; 'nodes': { name: 'nodes'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'UnchangedSharedObject'; ofType: null; }; }; }; } }; 'pageInfo': { name: 'pageInfo'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'OBJECT'; name: 'PageInfo'; ofType: null; }; } }; }; }; 'UnchangedSharedObjectEdge': { kind: 'OBJECT'; name: 'UnchangedSharedObjectEdge'; fields: { 'cursor': { name: 'cursor'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'String'; ofType: null; }; } }; 'node': { name: 'node'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'UnchangedSharedObject'; ofType: null; }; } }; }; }; 'UpgradeTransaction': { kind: 'OBJECT'; name: 'UpgradeTransaction'; fields: { 'currentPackage': { name: 'currentPackage'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; } }; 'dependencies': { name: 'dependencies'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'SuiAddress'; ofType: null; }; }; }; } }; 'modules': { name: 'modules'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'LIST'; name: never; ofType: { kind: 'NON_NULL'; name: never; ofType: { kind: 'SCALAR'; name: 'Base64'; ofType: null; }; }; }; } }; 'upgradeTicket': { name: 'upgradeTicket'; type: { kind: 'NON_NULL'; name: never; ofType: { kind: 'UNION'; name: 'TransactionArgument'; ofType: null; }; } }; }; }; diff --git a/sdk/typescript/src/keypairs/ed25519/ed25519-hd-key.ts b/sdk/typescript/src/keypairs/ed25519/ed25519-hd-key.ts index eb3f3bb6bf32b..daaadca91fb05 100644 --- a/sdk/typescript/src/keypairs/ed25519/ed25519-hd-key.ts +++ b/sdk/typescript/src/keypairs/ed25519/ed25519-hd-key.ts @@ -4,7 +4,7 @@ // This is adapted from https://github.com/alepop/ed25519-hd-key replacing create-hmac // with @noble/hashes to be browser compatible. -import { fromHEX } from '@mysten/bcs'; +import { fromHex } from '@mysten/bcs'; import { hmac } from '@noble/hashes/hmac'; import { sha512 } from '@noble/hashes/sha512'; import nacl from 'tweetnacl'; @@ -26,7 +26,7 @@ export const replaceDerive = (val: string): string => val.replace("'", ''); export const getMasterKeyFromSeed = (seed: Hex): Keys => { const h = hmac.create(sha512, ED25519_CURVE); - const I = h.update(fromHEX(seed)).digest(); + const I = h.update(fromHex(seed)).digest(); const IL = I.slice(0, 32); const IR = I.slice(32); return { diff --git a/sdk/typescript/src/keypairs/ed25519/keypair.ts b/sdk/typescript/src/keypairs/ed25519/keypair.ts index 9ebd3500bd208..e7102f1f49fac 100644 --- a/sdk/typescript/src/keypairs/ed25519/keypair.ts +++ b/sdk/typescript/src/keypairs/ed25519/keypair.ts @@ -3,7 +3,12 @@ import nacl from 'tweetnacl'; -import { encodeSuiPrivateKey, Keypair, PRIVATE_KEY_SIZE } from '../../cryptography/keypair.js'; +import { + decodeSuiPrivateKey, + encodeSuiPrivateKey, + Keypair, + PRIVATE_KEY_SIZE, +} from '../../cryptography/keypair.js'; import { isValidHardenedPath, mnemonicToSeedHex } from '../../cryptography/mnemonics.js'; import type { SignatureScheme } from '../../cryptography/signature-scheme.js'; import { derivePath } from './ed25519-hd-key.js'; @@ -63,13 +68,23 @@ export class Ed25519Keypair extends Keypair { * * @throws error if the provided secret key is invalid and validation is not skipped. * - * @param secretKey secret key byte array + * @param secretKey secret key as a byte array or Bech32 secret key string * @param options: skip secret key validation */ static fromSecretKey( - secretKey: Uint8Array, + secretKey: Uint8Array | string, options?: { skipValidation?: boolean }, ): Ed25519Keypair { + if (typeof secretKey === 'string') { + const decoded = decodeSuiPrivateKey(secretKey); + + if (decoded.schema !== 'ED25519') { + throw new Error(`Expected a ED25519 keypair, got ${decoded.schema}`); + } + + return this.fromSecretKey(decoded.secretKey, options); + } + const secretKeyLength = secretKey.length; if (secretKeyLength !== PRIVATE_KEY_SIZE) { throw new Error( diff --git a/sdk/typescript/src/keypairs/ed25519/publickey.ts b/sdk/typescript/src/keypairs/ed25519/publickey.ts index 519d041c32416..8afbfe454326e 100644 --- a/sdk/typescript/src/keypairs/ed25519/publickey.ts +++ b/sdk/typescript/src/keypairs/ed25519/publickey.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64 } from '@mysten/bcs'; +import { fromBase64 } from '@mysten/bcs'; import nacl from 'tweetnacl'; import type { PublicKeyInitData } from '../../cryptography/publickey.js'; @@ -26,7 +26,7 @@ export class Ed25519PublicKey extends PublicKey { super(); if (typeof value === 'string') { - this.data = fromB64(value); + this.data = fromBase64(value); } else if (value instanceof Uint8Array) { this.data = value; } else { diff --git a/sdk/typescript/src/keypairs/secp256k1/keypair.ts b/sdk/typescript/src/keypairs/secp256k1/keypair.ts index e469b518aba74..ef418553fdc19 100644 --- a/sdk/typescript/src/keypairs/secp256k1/keypair.ts +++ b/sdk/typescript/src/keypairs/secp256k1/keypair.ts @@ -7,7 +7,7 @@ import { sha256 } from '@noble/hashes/sha256'; import { bytesToHex } from '@noble/hashes/utils'; import { HDKey } from '@scure/bip32'; -import { encodeSuiPrivateKey, Keypair } from '../../cryptography/keypair.js'; +import { decodeSuiPrivateKey, encodeSuiPrivateKey, Keypair } from '../../cryptography/keypair.js'; import { isValidBIP32Path, mnemonicToSeed } from '../../cryptography/mnemonics.js'; import type { PublicKey } from '../../cryptography/publickey.js'; import type { SignatureScheme } from '../../cryptography/signature-scheme.js'; @@ -70,14 +70,24 @@ export class Secp256k1Keypair extends Keypair { * * @throws error if the provided secret key is invalid and validation is not skipped. * - * @param secretKey secret key byte array + * @param secretKey secret key byte array or Bech32 secret key string * @param options: skip secret key validation */ static fromSecretKey( - secretKey: Uint8Array, + secretKey: Uint8Array | string, options?: { skipValidation?: boolean }, ): Secp256k1Keypair { + if (typeof secretKey === 'string') { + const decoded = decodeSuiPrivateKey(secretKey); + + if (decoded.schema !== 'Secp256k1') { + throw new Error(`Expected a Secp256k1 keypair, got ${decoded.schema}`); + } + + return this.fromSecretKey(decoded.secretKey, options); + } + const publicKey: Uint8Array = secp256k1.getPublicKey(secretKey, true); if (!options || !options.skipValidation) { const encoder = new TextEncoder(); diff --git a/sdk/typescript/src/keypairs/secp256k1/publickey.ts b/sdk/typescript/src/keypairs/secp256k1/publickey.ts index 657b55a020e60..bd44844aa4db3 100644 --- a/sdk/typescript/src/keypairs/secp256k1/publickey.ts +++ b/sdk/typescript/src/keypairs/secp256k1/publickey.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64 } from '@mysten/bcs'; +import { fromBase64 } from '@mysten/bcs'; import { secp256k1 } from '@noble/curves/secp256k1'; import { sha256 } from '@noble/hashes/sha256'; @@ -27,7 +27,7 @@ export class Secp256k1PublicKey extends PublicKey { super(); if (typeof value === 'string') { - this.data = fromB64(value); + this.data = fromBase64(value); } else if (value instanceof Uint8Array) { this.data = value; } else { diff --git a/sdk/typescript/src/keypairs/secp256r1/keypair.ts b/sdk/typescript/src/keypairs/secp256r1/keypair.ts index 705fa8a6e9570..269ced2429c2d 100644 --- a/sdk/typescript/src/keypairs/secp256r1/keypair.ts +++ b/sdk/typescript/src/keypairs/secp256r1/keypair.ts @@ -7,7 +7,7 @@ import { sha256 } from '@noble/hashes/sha256'; import { bytesToHex } from '@noble/hashes/utils'; import { HDKey } from '@scure/bip32'; -import { encodeSuiPrivateKey, Keypair } from '../../cryptography/keypair.js'; +import { decodeSuiPrivateKey, encodeSuiPrivateKey, Keypair } from '../../cryptography/keypair.js'; import { isValidBIP32Path, mnemonicToSeed } from '../../cryptography/mnemonics.js'; import type { PublicKey } from '../../cryptography/publickey.js'; import type { SignatureScheme } from '../../cryptography/signature-scheme.js'; @@ -70,14 +70,24 @@ export class Secp256r1Keypair extends Keypair { * * @throws error if the provided secret key is invalid and validation is not skipped. * - * @param secretKey secret key byte array + * @param secretKey secret key byte array or Bech32 secret key string * @param options: skip secret key validation */ static fromSecretKey( - secretKey: Uint8Array, + secretKey: Uint8Array | string, options?: { skipValidation?: boolean }, ): Secp256r1Keypair { + if (typeof secretKey === 'string') { + const decoded = decodeSuiPrivateKey(secretKey); + + if (decoded.schema !== 'Secp256r1') { + throw new Error(`Expected a Secp256r1 keypair, got ${decoded.schema}`); + } + + return this.fromSecretKey(decoded.secretKey, options); + } + const publicKey: Uint8Array = secp256r1.getPublicKey(secretKey, true); if (!options || !options.skipValidation) { const encoder = new TextEncoder(); diff --git a/sdk/typescript/src/keypairs/secp256r1/publickey.ts b/sdk/typescript/src/keypairs/secp256r1/publickey.ts index a1cddbb8491e3..8cf11a9de89a5 100644 --- a/sdk/typescript/src/keypairs/secp256r1/publickey.ts +++ b/sdk/typescript/src/keypairs/secp256r1/publickey.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64 } from '@mysten/bcs'; +import { fromBase64 } from '@mysten/bcs'; import { secp256r1 } from '@noble/curves/p256'; import { sha256 } from '@noble/hashes/sha256'; @@ -27,7 +27,7 @@ export class Secp256r1PublicKey extends PublicKey { super(); if (typeof value === 'string') { - this.data = fromB64(value); + this.data = fromBase64(value); } else if (value instanceof Uint8Array) { this.data = value; } else { diff --git a/sdk/typescript/src/multisig/publickey.ts b/sdk/typescript/src/multisig/publickey.ts index ada52d8b15c41..b97ec9cce54b0 100644 --- a/sdk/typescript/src/multisig/publickey.ts +++ b/sdk/typescript/src/multisig/publickey.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64, toB64 } from '@mysten/bcs'; +import { fromBase64, toBase64 } from '@mysten/bcs'; import { blake2b } from '@noble/hashes/blake2b'; import { bytesToHex } from '@noble/hashes/utils'; @@ -81,7 +81,7 @@ export class MultiSigPublicKey extends PublicKey { super(); if (typeof value === 'string') { - this.rawBytes = fromB64(value); + this.rawBytes = fromBase64(value); this.multisigPublicKey = bcs.MultiSigPublicKey.parse(this.rawBytes); } else if (value instanceof Uint8Array) { @@ -306,7 +306,7 @@ export class MultiSigPublicKey extends PublicKey { let tmp = new Uint8Array(bytes.length + 1); tmp.set([SIGNATURE_SCHEME_TO_FLAG['MultiSig']]); tmp.set(bytes, 1); - return toB64(tmp); + return toBase64(tmp); } } diff --git a/sdk/typescript/src/multisig/signer.ts b/sdk/typescript/src/multisig/signer.ts index 7385ee1f851a7..0cd5719b194c9 100644 --- a/sdk/typescript/src/multisig/signer.ts +++ b/sdk/typescript/src/multisig/signer.ts @@ -1,6 +1,6 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { toB64 } from '@mysten/bcs'; +import { toBase64 } from '@mysten/bcs'; import type { SignatureScheme } from '../cryptography/index.js'; import { Signer } from '../cryptography/index.js'; @@ -73,7 +73,7 @@ export class MultiSigSigner extends Signer { return { signature, - bytes: toB64(bytes), + bytes: toBase64(bytes), }; } @@ -86,7 +86,7 @@ export class MultiSigSigner extends Signer { return { signature, - bytes: toB64(bytes), + bytes: toBase64(bytes), }; } } diff --git a/sdk/typescript/src/transactions/Commands.ts b/sdk/typescript/src/transactions/Commands.ts index 7021c07f075d5..7d8e658148280 100644 --- a/sdk/typescript/src/transactions/Commands.ts +++ b/sdk/typescript/src/transactions/Commands.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { toB64 } from '@mysten/bcs'; +import { toBase64 } from '@mysten/bcs'; import type { InferInput } from 'valibot'; import { parse } from 'valibot'; @@ -108,7 +108,7 @@ export const Commands = { $kind: 'Publish', Publish: { modules: modules.map((module) => - typeof module === 'string' ? module : toB64(new Uint8Array(module)), + typeof module === 'string' ? module : toBase64(new Uint8Array(module)), ), dependencies: dependencies.map((dep) => normalizeSuiObjectId(dep)), }, @@ -129,7 +129,7 @@ export const Commands = { $kind: 'Upgrade', Upgrade: { modules: modules.map((module) => - typeof module === 'string' ? module : toB64(new Uint8Array(module)), + typeof module === 'string' ? module : toBase64(new Uint8Array(module)), ), dependencies: dependencies.map((dep) => normalizeSuiObjectId(dep)), package: packageId, diff --git a/sdk/typescript/src/transactions/Inputs.ts b/sdk/typescript/src/transactions/Inputs.ts index 2abfb1482aa6b..d3e8281cf2d93 100644 --- a/sdk/typescript/src/transactions/Inputs.ts +++ b/sdk/typescript/src/transactions/Inputs.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { toB64 } from '@mysten/bcs'; +import { toBase64 } from '@mysten/bcs'; import type { SerializedBcs } from '@mysten/bcs'; import { normalizeSuiAddress } from '../utils/sui-types.js'; @@ -11,7 +11,7 @@ function Pure(data: Uint8Array | SerializedBcs): Extract Array.from(fromB64(mod))), + modules: command.Publish.modules.map((mod) => Array.from(fromBase64(mod))), dependencies: command.Publish.dependencies, }; } @@ -337,7 +337,7 @@ export function serializeV1TransactionData( if (command.Upgrade) { return { kind: 'Upgrade', - modules: command.Upgrade.modules.map((mod) => Array.from(fromB64(mod))), + modules: command.Upgrade.modules.map((mod) => Array.from(fromBase64(mod))), dependencies: command.Upgrade.dependencies, packageId: command.Upgrade.package, ticket: convertTransactionArgument(command.Upgrade.ticket, inputs), @@ -434,7 +434,7 @@ export function transactionDataFromV1(data: SerializedTransactionDataV1): Transa return { Pure: { - bytes: toB64(new Uint8Array(value.Pure)), + bytes: toBase64(new Uint8Array(value.Pure)), }, }; } @@ -491,7 +491,7 @@ export function transactionDataFromV1(data: SerializedTransactionDataV1): Transa case 'Publish': { return { Publish: { - modules: transaction.modules.map((mod) => toB64(Uint8Array.from(mod))), + modules: transaction.modules.map((mod) => toBase64(Uint8Array.from(mod))), dependencies: transaction.dependencies, }, }; @@ -515,7 +515,7 @@ export function transactionDataFromV1(data: SerializedTransactionDataV1): Transa case 'Upgrade': { return { Upgrade: { - modules: transaction.modules.map((mod) => toB64(Uint8Array.from(mod))), + modules: transaction.modules.map((mod) => toBase64(Uint8Array.from(mod))), dependencies: transaction.dependencies, package: transaction.packageId, ticket: parseV1TransactionArgument(transaction.ticket), diff --git a/sdk/typescript/src/transactions/executor/parallel.ts b/sdk/typescript/src/transactions/executor/parallel.ts index 72cf1100ba691..c6c73eae35439 100644 --- a/sdk/typescript/src/transactions/executor/parallel.ts +++ b/sdk/typescript/src/transactions/executor/parallel.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { toB64 } from '@mysten/bcs'; +import { toBase64 } from '@mysten/bcs'; import { bcs } from '../../bcs/index.js'; import type { SuiObjectRef } from '../../bcs/types.js'; @@ -266,7 +266,7 @@ export class ParallelTransactionExecutor { return { digest: results.digest, - effects: toB64(effectsBytes), + effects: toBase64(effectsBytes), data: results, }; } catch (error) { diff --git a/sdk/typescript/src/transactions/executor/serial.ts b/sdk/typescript/src/transactions/executor/serial.ts index ce81022cfa6ae..0e25ed95c0445 100644 --- a/sdk/typescript/src/transactions/executor/serial.ts +++ b/sdk/typescript/src/transactions/executor/serial.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { toB64 } from '@mysten/bcs'; +import { toBase64 } from '@mysten/bcs'; import { bcs } from '../../bcs/index.js'; import type { SuiClient, SuiTransactionBlockResponseOptions } from '../../client/index.js'; @@ -109,7 +109,7 @@ export class SerialTransactionExecutor { return { digest: results.digest, - effects: toB64(effectsBytes), + effects: toBase64(effectsBytes), data: results, }; }); diff --git a/sdk/typescript/src/transactions/json-rpc-resolver.ts b/sdk/typescript/src/transactions/json-rpc-resolver.ts index ee364f012d3a8..3ffbb16ed0836 100644 --- a/sdk/typescript/src/transactions/json-rpc-resolver.ts +++ b/sdk/typescript/src/transactions/json-rpc-resolver.ts @@ -473,7 +473,7 @@ function isReceivingType(type: OpenMoveTypeSignature): boolean { export function getClient(options: BuildTransactionOptions): SuiClient { if (!options.client) { throw new Error( - `No provider passed to Transaction#build, but transaction data was not sufficient to build offline.`, + `No sui client passed to Transaction#build, but transaction data was not sufficient to build offline.`, ); } diff --git a/sdk/typescript/src/utils/dynamic-fields.ts b/sdk/typescript/src/utils/dynamic-fields.ts new file mode 100644 index 0000000000000..838e935b8501a --- /dev/null +++ b/sdk/typescript/src/utils/dynamic-fields.ts @@ -0,0 +1,30 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { toHex } from '@mysten/bcs'; +import { blake2b } from '@noble/hashes/blake2b'; + +import type { TypeTag } from '../bcs/bcs.js'; +import { bcs } from '../bcs/index.js'; + +export function deriveDynamicFieldID( + parentId: string, + typeTag: typeof TypeTag.$inferInput, + key: Uint8Array, +) { + const address = bcs.Address.serialize(parentId).toBytes(); + const tag = bcs.TypeTag.serialize(typeTag).toBytes(); + const keyLength = bcs.u64().serialize(key.length).toBytes(); + + const hash = blake2b.create({ + dkLen: 32, + }); + + hash.update(new Uint8Array([0xf0])); + hash.update(address); + hash.update(keyLength); + hash.update(key); + hash.update(tag); + + return `0x${toHex(hash.digest().slice(0, 32))}`; +} diff --git a/sdk/typescript/src/utils/index.ts b/sdk/typescript/src/utils/index.ts index aa6bf138653fa..bc52bceea2336 100644 --- a/sdk/typescript/src/utils/index.ts +++ b/sdk/typescript/src/utils/index.ts @@ -13,7 +13,18 @@ export { SUI_ADDRESS_LENGTH, } from './sui-types.js'; -export { fromB64, toB64, fromHEX, toHEX } from '@mysten/bcs'; +export { + fromB64, + toB64, + fromHEX, + toHex, + toHEX, + fromHex, + fromBase64, + toBase64, + fromBase58, + toBase58, +} from '@mysten/bcs'; export { isValidSuiNSName, normalizeSuiNSName } from './suins.js'; export { @@ -29,3 +40,5 @@ export { } from './constants.js'; export { isValidNamedPackage, isValidNamedType } from './move-registry.js'; + +export { deriveDynamicFieldID } from './dynamic-fields.js'; diff --git a/sdk/typescript/src/utils/sui-types.ts b/sdk/typescript/src/utils/sui-types.ts index 311269961a8bf..f8cb10b997136 100644 --- a/sdk/typescript/src/utils/sui-types.ts +++ b/sdk/typescript/src/utils/sui-types.ts @@ -1,14 +1,14 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB58, splitGenericParameters } from '@mysten/bcs'; +import { fromBase58, splitGenericParameters } from '@mysten/bcs'; const TX_DIGEST_LENGTH = 32; /** Returns whether the tx digest is valid based on the serialization format */ export function isValidTransactionDigest(value: string): value is string { try { - const buffer = fromB58(value); + const buffer = fromBase58(value); return buffer.length === TX_DIGEST_LENGTH; } catch (e) { return false; diff --git a/sdk/typescript/src/verify/verify.ts b/sdk/typescript/src/verify/verify.ts index 7ba6fcf63a6ab..f4c3798a094fd 100644 --- a/sdk/typescript/src/verify/verify.ts +++ b/sdk/typescript/src/verify/verify.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64 } from '@mysten/bcs'; +import { fromBase64 } from '@mysten/bcs'; import type { PublicKey, SignatureFlag, SignatureScheme } from '../cryptography/index.js'; import { parseSerializedSignature, SIGNATURE_FLAG_TO_SCHEME } from '../cryptography/index.js'; @@ -107,7 +107,7 @@ export function publicKeyFromSuiBytes( publicKey: string | Uint8Array, options: { client?: SuiGraphQLClient } = {}, ) { - const bytes = typeof publicKey === 'string' ? fromB64(publicKey) : publicKey; + const bytes = typeof publicKey === 'string' ? fromBase64(publicKey) : publicKey; const signatureScheme = SIGNATURE_FLAG_TO_SCHEME[bytes[0] as SignatureFlag]; diff --git a/sdk/typescript/src/version.ts b/sdk/typescript/src/version.ts index c8cd520ecb877..204307cfc05e4 100644 --- a/sdk/typescript/src/version.ts +++ b/sdk/typescript/src/version.ts @@ -3,5 +3,5 @@ // This file is generated by genversion.mjs. Do not edit it directly. -export const PACKAGE_VERSION = '1.9.0'; -export const TARGETED_RPC_VERSION = '1.34.0'; +export const PACKAGE_VERSION = '1.13.0'; +export const TARGETED_RPC_VERSION = '1.36.0'; diff --git a/sdk/typescript/src/zklogin/publickey.ts b/sdk/typescript/src/zklogin/publickey.ts index d42afefb0559d..7eb23ffa23b0c 100644 --- a/sdk/typescript/src/zklogin/publickey.ts +++ b/sdk/typescript/src/zklogin/publickey.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64, toB64 } from '@mysten/bcs'; +import { fromBase64, toBase64 } from '@mysten/bcs'; import { PublicKey } from '../cryptography/publickey.js'; import type { PublicKeyInitData } from '../cryptography/publickey.js'; @@ -29,7 +29,7 @@ export class ZkLoginPublicIdentifier extends PublicKey { this.#client = client; if (typeof value === 'string') { - this.#data = fromB64(value); + this.#data = fromBase64(value); } else if (value instanceof Uint8Array) { this.#data = value; } else { @@ -74,7 +74,7 @@ export class ZkLoginPublicIdentifier extends PublicKey { return graphqlVerifyZkLoginSignature({ address: address, - bytes: toB64(message), + bytes: toBase64(message), signature: parsedSignature.serializedSignature, intentScope: 'PERSONAL_MESSAGE', client: this.#client, @@ -89,7 +89,7 @@ export class ZkLoginPublicIdentifier extends PublicKey { const address = new ZkLoginPublicIdentifier(parsedSignature.publicKey).toSuiAddress(); return graphqlVerifyZkLoginSignature({ address: address, - bytes: toB64(transaction), + bytes: toBase64(transaction), signature: parsedSignature.serializedSignature, intentScope: 'TRANSACTION_DATA', client: this.#client, @@ -164,7 +164,7 @@ async function graphqlVerifyZkLoginSignature({ } export function parseSerializedZkLoginSignature(signature: Uint8Array | string) { - const bytes = typeof signature === 'string' ? fromB64(signature) : signature; + const bytes = typeof signature === 'string' ? fromBase64(signature) : signature; if (bytes[0] !== SIGNATURE_SCHEME_TO_FLAG.ZkLogin) { throw new Error('Invalid signature scheme'); @@ -176,7 +176,7 @@ export function parseSerializedZkLoginSignature(signature: Uint8Array | string) const iss = extractClaimValue(issBase64Details, 'iss'); const publicIdentifer = toZkLoginPublicIdentifier(BigInt(addressSeed), iss); return { - serializedSignature: toB64(bytes), + serializedSignature: toBase64(bytes), signatureScheme: 'ZkLogin' as const, zkLogin: { inputs, diff --git a/sdk/typescript/src/zklogin/signature.ts b/sdk/typescript/src/zklogin/signature.ts index 732279b7062c5..04d8b0f1b46c0 100644 --- a/sdk/typescript/src/zklogin/signature.ts +++ b/sdk/typescript/src/zklogin/signature.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64, toB64 } from '@mysten/bcs'; +import { fromBase64, toBase64 } from '@mysten/bcs'; import { SIGNATURE_SCHEME_TO_FLAG } from '../cryptography/signature-scheme.js'; import type { ZkLoginSignature } from './bcs.js'; @@ -17,7 +17,8 @@ function getZkLoginSignatureBytes({ inputs, maxEpoch, userSignature }: ZkLoginSi { inputs, maxEpoch, - userSignature: typeof userSignature === 'string' ? fromB64(userSignature) : userSignature, + userSignature: + typeof userSignature === 'string' ? fromBase64(userSignature) : userSignature, }, { maxSize: 2048 }, ) @@ -29,9 +30,9 @@ export function getZkLoginSignature({ inputs, maxEpoch, userSignature }: ZkLogin const signatureBytes = new Uint8Array(bytes.length + 1); signatureBytes.set([SIGNATURE_SCHEME_TO_FLAG.ZkLogin]); signatureBytes.set(bytes, 1); - return toB64(signatureBytes); + return toBase64(signatureBytes); } export function parseZkLoginSignature(signature: string | Uint8Array) { - return zkLoginSignature.parse(typeof signature === 'string' ? fromB64(signature) : signature); + return zkLoginSignature.parse(typeof signature === 'string' ? fromBase64(signature) : signature); } diff --git a/sdk/typescript/test/e2e/coin-with-balance.test.ts b/sdk/typescript/test/e2e/coin-with-balance.test.ts index 0e15b69d954b8..5114b9c83286c 100644 --- a/sdk/typescript/test/e2e/coin-with-balance.test.ts +++ b/sdk/typescript/test/e2e/coin-with-balance.test.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { resolve } from 'path'; -import { fromHEX, toB64 } from '@mysten/bcs'; +import { fromHex, toBase64 } from '@mysten/bcs'; import { beforeAll, describe, expect, it } from 'vitest'; import { bcs } from '../../src/bcs'; @@ -57,7 +57,7 @@ describe('coinWithBalance', () => { inputs: [ { Pure: { - bytes: toB64(fromHEX(receiver.toSuiAddress())), + bytes: toBase64(fromHex(receiver.toSuiAddress())), }, }, ], @@ -107,12 +107,12 @@ describe('coinWithBalance', () => { inputs: [ { Pure: { - bytes: toB64(fromHEX(receiver.toSuiAddress())), + bytes: toBase64(fromHex(receiver.toSuiAddress())), }, }, { Pure: { - bytes: toB64(bcs.u64().serialize(12345).toBytes()), + bytes: toBase64(bcs.u64().serialize(12345).toBytes()), }, }, ], @@ -205,7 +205,7 @@ describe('coinWithBalance', () => { inputs: [ { Pure: { - bytes: toB64(fromHEX(receiver.toSuiAddress())), + bytes: toBase64(fromHex(receiver.toSuiAddress())), }, }, ], @@ -255,7 +255,7 @@ describe('coinWithBalance', () => { inputs: [ { Pure: { - bytes: toB64(fromHEX(receiver.toSuiAddress())), + bytes: toBase64(fromHex(receiver.toSuiAddress())), }, }, { @@ -265,7 +265,7 @@ describe('coinWithBalance', () => { }, { Pure: { - bytes: toB64(bcs.u64().serialize(1).toBytes()), + bytes: toBase64(bcs.u64().serialize(1).toBytes()), }, }, ], @@ -355,7 +355,7 @@ describe('coinWithBalance', () => { inputs: [ { Pure: { - bytes: toB64(fromHEX(receiver.toSuiAddress())), + bytes: toBase64(fromHex(receiver.toSuiAddress())), }, }, ], @@ -444,7 +444,7 @@ describe('coinWithBalance', () => { inputs: [ { Pure: { - bytes: toB64(fromHEX(receiver.toSuiAddress())), + bytes: toBase64(fromHex(receiver.toSuiAddress())), }, }, { @@ -454,22 +454,22 @@ describe('coinWithBalance', () => { }, { Pure: { - bytes: toB64(bcs.u64().serialize(1).toBytes()), + bytes: toBase64(bcs.u64().serialize(1).toBytes()), }, }, { Pure: { - bytes: toB64(bcs.u64().serialize(2).toBytes()), + bytes: toBase64(bcs.u64().serialize(2).toBytes()), }, }, { Pure: { - bytes: toB64(bcs.u64().serialize(3).toBytes()), + bytes: toBase64(bcs.u64().serialize(3).toBytes()), }, }, { Pure: { - bytes: toB64(bcs.u64().serialize(4).toBytes()), + bytes: toBase64(bcs.u64().serialize(4).toBytes()), }, }, ], diff --git a/sdk/typescript/test/e2e/keypairs.test.ts b/sdk/typescript/test/e2e/keypairs.test.ts index 77869d70d6a29..ed079065e0cf7 100644 --- a/sdk/typescript/test/e2e/keypairs.test.ts +++ b/sdk/typescript/test/e2e/keypairs.test.ts @@ -7,7 +7,7 @@ import { describe, expect, it } from 'vitest'; import { messageWithIntent, parseSerializedSignature } from '../../src/cryptography'; import { Ed25519Keypair } from '../../src/keypairs/ed25519'; import { Secp256k1Keypair } from '../../src/keypairs/secp256k1'; -import { fromB64, toB64 } from '../../src/utils'; +import { fromBase64, toBase64 } from '../../src/utils'; const TX_BYTES = 'AAACAQDMdYtdFSLGe6VbgpuIsMksv9Ypzpvkq2jiYq0hAjUpOQIAAAAAAAAAIHGwPza+lUm6RuJV1vn9pA4y0PwVT7k/KMMbUViQS5ydACAMVn/9+BYsttUa90vgGZRDuS6CPUumztJN5cbEY3l9RgEBAQEAAAEBAHUFfdk1Tg9l6STLBoSBJbbUuehTDUlLH7p81kpqCKsaBCiJ034Ac84f1oqgmpz79O8L/UeLNDUpOUMa+LadeX93AgAAAAAAAAAgs1e67e789jSlrzOJUXq0bb7Bn/hji+3F5UoMAbze595xCSZCVjU1ItUC9G7KQjygNiBbzZe8t7YLPjRAQyGTzAIAAAAAAAAAIAujHFcrkJJhZfCmxmCHsBWxj5xkviUqB479oupdgMZu07b+hkrjyvCcX50dO30v3PszXFj7+lCNTUTuE4UI3eoCAAAAAAAAACBIv39dyVELUFTkNv72mat5R1uHFkQdViikc1lTMiSVlOD+eESUq3neyciBatafk9dHuhhrS37RaSflqKwFlwzPAgAAAAAAAAAg8gqL3hCkAho8bb0PoqshJdqQFoRP8ZmQMZDFvsGBqa11BX3ZNU4PZekkywaEgSW21LnoUw1JSx+6fNZKagirGgEAAAAAAAAAKgQAAAAAAAAA'; @@ -69,11 +69,11 @@ const TEST_CASES_SECP256K1 = [ describe('Keypairs', () => { it('Ed25519 keypair signData', async () => { - const tx_bytes = fromB64(TX_BYTES); + const tx_bytes = fromBase64(TX_BYTES); const intentMessage = messageWithIntent('TransactionData', tx_bytes); const digest = blake2b(intentMessage, { dkLen: 32 }); - expect(toB64(digest)).toEqual(DIGEST); + expect(toBase64(digest)).toEqual(DIGEST); for (const t of TEST_CASES) { const keypair = Ed25519Keypair.deriveKeypair(t[0], DERIVATION_PATH); @@ -83,7 +83,7 @@ describe('Keypairs', () => { const { signature: serializedSignature } = await keypair.signTransaction(tx_bytes); const { signature } = parseSerializedSignature(serializedSignature); - expect(toB64(signature!)).toEqual(t[3]); + expect(toBase64(signature!)).toEqual(t[3]); const isValid = await keypair.getPublicKey().verifyTransaction(tx_bytes, serializedSignature); expect(isValid).toBeTruthy(); @@ -111,10 +111,10 @@ describe('Keypairs', () => { }); it('Secp256k1 keypair signData', async () => { - const tx_bytes = fromB64(TX_BYTES); + const tx_bytes = fromBase64(TX_BYTES); const intentMessage = messageWithIntent('TransactionData', tx_bytes); const digest = blake2b(intentMessage, { dkLen: 32 }); - expect(toB64(digest)).toEqual(DIGEST); + expect(toBase64(digest)).toEqual(DIGEST); for (const t of TEST_CASES_SECP256K1) { const keypair = Secp256k1Keypair.deriveKeypair(t[0], DERIVATION_PATH_SECP256K1); @@ -124,7 +124,7 @@ describe('Keypairs', () => { const { signature: serializedSignature } = await keypair.signTransaction(tx_bytes); const { signature } = parseSerializedSignature(serializedSignature); - expect(toB64(signature!)).toEqual(t[3]); + expect(toBase64(signature!)).toEqual(t[3]); const isValid = await keypair.getPublicKey().verifyTransaction(tx_bytes, serializedSignature); expect(isValid).toBeTruthy(); diff --git a/sdk/typescript/test/e2e/multisig.test.ts b/sdk/typescript/test/e2e/multisig.test.ts index 53a50966638f0..2863f976c4a8a 100644 --- a/sdk/typescript/test/e2e/multisig.test.ts +++ b/sdk/typescript/test/e2e/multisig.test.ts @@ -3,7 +3,7 @@ import { tmpdir } from 'os'; import path from 'path'; -import { fromB64 } from '@mysten/bcs'; +import { fromBase64 } from '@mysten/bcs'; import { describe, expect, it } from 'vitest'; import { decodeSuiPrivateKey } from '../../src/cryptography'; @@ -95,7 +95,7 @@ describe('MultiSig with zklogin signature', () => { const zkLoginSig = getZkLoginSignature({ inputs: zkLoginInputs, maxEpoch: '2', - userSignature: fromB64(ephemeralSig), + userSignature: fromBase64(ephemeralSig), }); // combine to multisig and execute the transaction. diff --git a/sdk/typescript/test/e2e/parallel-executor.test.ts b/sdk/typescript/test/e2e/parallel-executor.test.ts index 7d0d036fd4043..a6682bdf3a25c 100644 --- a/sdk/typescript/test/e2e/parallel-executor.test.ts +++ b/sdk/typescript/test/e2e/parallel-executor.test.ts @@ -13,38 +13,38 @@ import { setup, TestToolbox } from './utils/setup'; let toolbox: TestToolbox; let executor: ParallelTransactionExecutor; -beforeAll(async () => { - toolbox = await setup(); - - // Creates bear package - await toolbox.mintNft(); +describe('ParallelTransactionExecutor', { retry: 3 }, () => { + beforeAll(async () => { + toolbox = await setup(); - executor = new ParallelTransactionExecutor({ - client: toolbox.client, - signer: toolbox.keypair, - maxPoolSize: 3, - coinBatchSize: 2, - }); + // Creates bear package + await toolbox.mintNft(); - vi.spyOn(toolbox.client, 'multiGetObjects'); - vi.spyOn(toolbox.client, 'getCoins'); - vi.spyOn(toolbox.client, 'executeTransactionBlock'); -}); + executor = new ParallelTransactionExecutor({ + client: toolbox.client, + signer: toolbox.keypair, + maxPoolSize: 3, + coinBatchSize: 2, + }); -afterEach(async () => { - await executor.waitForLastTransaction(); -}); + vi.spyOn(toolbox.client, 'multiGetObjects'); + vi.spyOn(toolbox.client, 'getCoins'); + vi.spyOn(toolbox.client, 'executeTransactionBlock'); + }); -afterAll(() => { - vi.restoreAllMocks(); -}); + afterEach(async () => { + await executor.waitForLastTransaction(); + }); -describe('ParallelTransactionExecutor', { retry: 3 }, () => { beforeEach(async () => { await executor.resetCache(); vi.clearAllMocks(); }); + afterAll(() => { + vi.restoreAllMocks(); + }); + it('Executes multiple transactions in parallel', async () => { let concurrentRequests = 0; let maxConcurrentRequests = 0; diff --git a/sdk/typescript/test/e2e/receive-object.test.ts b/sdk/typescript/test/e2e/receive-object.test.ts index f7622e4fc13a3..799c2055c7c5a 100644 --- a/sdk/typescript/test/e2e/receive-object.test.ts +++ b/sdk/typescript/test/e2e/receive-object.test.ts @@ -18,7 +18,7 @@ function getOwnerAddress(o: OwnedObjectRef): string | undefined { } } -describe('Transfer to Object', () => { +describe('Transfer to Object', { retry: 3 }, () => { let toolbox: TestToolbox; let packageId: string; let parentObjectId: OwnedObjectRef; diff --git a/sdk/typescript/test/e2e/zklogin-signature.test.ts b/sdk/typescript/test/e2e/zklogin-signature.test.ts index 478b9e81f3631..0403160eaed39 100644 --- a/sdk/typescript/test/e2e/zklogin-signature.test.ts +++ b/sdk/typescript/test/e2e/zklogin-signature.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64 } from '@mysten/bcs'; +import { fromBase64 } from '@mysten/bcs'; import { describe, expect, it, test } from 'vitest'; import { SuiGraphQLClient } from '../../src/graphql'; @@ -50,10 +50,10 @@ const anEphemeralSignature = describe('zkLogin signature', () => { test('is parsed successfully', () => { - expect(parseZkLoginSignature(fromB64(aSignature).slice(1))).toMatchObject({ + expect(parseZkLoginSignature(fromBase64(aSignature).slice(1))).toMatchObject({ inputs: aSignatureInputs, maxEpoch: '174', - userSignature: fromB64(anEphemeralSignature), + userSignature: fromBase64(anEphemeralSignature), }); }); test('is serialized successfully', () => { @@ -61,7 +61,7 @@ describe('zkLogin signature', () => { getZkLoginSignature({ inputs: aSignatureInputs, maxEpoch: '174', - userSignature: fromB64(anEphemeralSignature), + userSignature: fromBase64(anEphemeralSignature), }), ).toBe(aSignature); }); @@ -81,7 +81,7 @@ describe('zkLogin signature', () => { }); // verifies ok bc max_epoch 3 is within upper bound. - let res = await pk.verifyPersonalMessage(fromB64(bytes), parsed.signature); + let res = await pk.verifyPersonalMessage(fromBase64(bytes), parsed.signature); expect(res).toBe(true); // test case generated from `sui keytool zk-login-insecure-sign-personal-message --data "hello" --max-epoch 100` @@ -89,7 +89,7 @@ describe('zkLogin signature', () => { let testSignature2 = 'BQNNMTU3MTEzMjIxMjQyNzE4OTQyODkwNzkwMzcyMTAyNzkxNDU1MzAxMTc4NzgxMDIyOTYzNzQ2Njk5MTE0NzU5MDY3ODYyNDYyNzQ2MTBNMTY2MDg4MjI5MjU0NDI1OTQyMjkxMjY4MDIzMzUyNDE3NDU3NTcwMDc0NjUxMjQ0MTI1OTczMTE2MDM5NzYwMTk2ODk0MzE5ODY5MDYBMQMCTTEzNDQ1MjU4Mzc0Mjk4MTE1MjAzMjEwODM4NzU1Nzk0MDExMTg1NDU0OTgzODgxMTg5OTYwNTQzODc5NjMzMDE5OTQxODEyMDk2MjYzTDE3Njk4NDE1NzUzNDg4NDgzOTEzMjMxMTA3NDMyNDkzMTkyOTAxMTEwNjY0NzE2OTkxMzQwNzY0NjExMzg2OTk5NDg1NDAyODA3MzgCTTE0ODU5NDk0ODMxNjI4MzQyMDEzMTM0NDA4NzAxMTIwNDUxMDI4MDkyMTg4MDAxMTMwOTkxNjkxMjAyNzMyMzA2NzcxODI4NTYxNzU0TTIwMzM1NDE4NjE3NzgyMzU5MTQ2NTg0NzcwNzM0MDcyMzI3NzYwMjAyNDYwMDE2NDY0NjAwNjQzMDA2Nzg5NzAyODg0MzQ1NTkzNjg5AgExATADTTE4Nzk4Mjk5MDAzOTAyMDI3MDcxNTg1ODY5MjY3MzYyOTc5ODUwOTExNzA3Nzk2MzU0NDQyMTY2NzEzOTcyNjQ2NzE2OTQ1OTgyMjM4TTEyMDExNjg0MjA0MDI0NTMxNzY2ODUxMTU0OTAyMzI5Njk4MDIwODQ3NTQ1NDU5NDk2MjA2MDI2NDg5MTE5MzUzODI4NTI2NTE5MzAwATEod2lhWE56SWpvaWFIUjBjSE02THk5dllYVjBhQzV6ZFdrdWFXOGlMQwI+ZXlKcmFXUWlPaUp6ZFdrdGEyVjVMV2xrSWl3aWRIbHdJam9pU2xkVUlpd2lZV3huSWpvaVVsTXlOVFlpZlFNMjA0MzUzNjY2MDAwMzYzNzU3NDU5MjU5NjM0NTY4NjEzMDc5MjUyMDk0NzAyMTkzMzQwMTg1NjQxNTgxNDg1NDQwMzYxOTYyODQ2NDJkAAAAAAAAAGEA+XrHUDMkMaPswTIFsqIgx3yX6j7IvU1T/1yzw4kjKwjgLL0ZtPQZjc2djX7Q9pFoBdObkSZ6JZ4epiOf05q4BrnG7hYw7z5xEUSmSNsGu7IoT3J0z77lP/zuUDzBpJIA'; let parsed2 = parseSerializedZkLoginSignature(testSignature2); - let res1 = await pk.verifyPersonalMessage(fromB64(bytes), parsed2.signature); + let res1 = await pk.verifyPersonalMessage(fromBase64(bytes), parsed2.signature); expect(res1).toBe(false); }, { @@ -114,7 +114,7 @@ describe('zkLogin signature', () => { }); // verifies ok bc max_epoch 5 is within upper bound. - let res = await pk.verifyTransaction(fromB64(bytes), parsed.signature); + let res = await pk.verifyTransaction(fromBase64(bytes), parsed.signature); expect(res).toBe(true); // fails to verify bc max_epoch 100 is too large. @@ -123,7 +123,7 @@ describe('zkLogin signature', () => { let testSignature2 = 'BQNLNjE1NDU3MTU5NjYwMDM3ODM1MTY2OTE1OTkwMzg1MjQ3NTkzMDg3NTk3NzM4Nzg2MzkxNjE3MTU4MDg1ODIzMTcyNjg4MzkyMjg3TTE0ODc4NTgwNDQ2ODcxNzE3Mzc2ODEwNjM5ODIxNDQxNDk5OTI0MDQxNjMwMzQ5MTExNTAwNzQzOTk2NTY0MzczNTU5NDU2NDg4NDY5ATEDAkwxOTI5MDM5OTU1Mjk3Njg0NzU0NDU5NDc2NzQwMzEyNjMzMDI0NTMwOTk2MjAwMzI3ODUxNzc2NDk5MTUyNjQ3MjUxMjQ1MzA3OTEwTDU4OTY0NzA0NDQyMTkyODA1MDYwNjM5MTI2NTMzODk1ODIyNzIzMDA4NDI0MjkwMDMxNTIxMjk2Njk3OTM1MTc2NTQ1NTczMDMyODcCTDcyOTk2ODY3MjgzOTc3MzQ3MDg3NjYzNDUzMjcwODc2ODc3MTgzOTU5MTQwNzkwMTc0MjIwOTUwNTkzNTg3MTcxMjg3MjA2Njk2OTFNMTAxNzIyNzE1ODY2OTc0MzY4MTU1MTQzOTcyMjkyMTgzMzUxMDg1NDY2NTEzNzI1MTI5MTE2NjI3NDcxNDg5MjU2NDY5OTk0NTQxMjYCATEBMANMNjMwNDA2MTEyMzQ1OTY1NjE5MzQ1ODAzOTA0MzExNTg0OTU2ODgwMDM1Njc5NTU5NTUxNTAwNDAwNTE1ODA3NDkxMTI5MzA3MDI0OEwyMjI2NTQ3MzA3NzY0MzE5NjA1NDgwMzk3NDE4MTM5MTEwODk5NDk2NTQxODM4MzM5OTU0MTkwMDQzOTc0ODQzNTAxNDUxNzc2Mzc5ATEod2lhWE56SWpvaWFIUjBjSE02THk5dllYVjBhQzV6ZFdrdWFXOGlMQwI+ZXlKcmFXUWlPaUp6ZFdrdGEyVjVMV2xrSWl3aWRIbHdJam9pU2xkVUlpd2lZV3huSWpvaVVsTXlOVFlpZlFNMjA0MzUzNjY2MDAwMzYzNzU3NDU5MjU5NjM0NTY4NjEzMDc5MjUyMDk0NzAyMTkzMzQwMTg1NjQxNTgxNDg1NDQwMzYxOTYyODQ2NDJkAAAAAAAAAGEAHvkRO3Mx6v8o57/BfVE/lOyy/XNNIo3I8elULrhPn0f2hENWzhvW+xEfPX0V4de38++4mYi7ctY8XNJmkBPODrnG7hYw7z5xEUSmSNsGu7IoT3J0z77lP/zuUDzBpJIA'; let parsed2 = parseSerializedZkLoginSignature(testSignature2); - let res1 = await pk.verifyPersonalMessage(fromB64(bytes2), parsed2.signature); + let res1 = await pk.verifyPersonalMessage(fromBase64(bytes2), parsed2.signature); expect(res1).toBe(false); }, { diff --git a/sdk/typescript/test/unit/arguments.test.ts b/sdk/typescript/test/unit/arguments.test.ts index 736b0be0eab50..2748d8f5b626d 100644 --- a/sdk/typescript/test/unit/arguments.test.ts +++ b/sdk/typescript/test/unit/arguments.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { toB58 } from '@mysten/bcs'; +import { toBase58 } from '@mysten/bcs'; import { describe, expect, it } from 'vitest'; import { Arguments, Transaction } from '../../src/transactions'; @@ -13,7 +13,7 @@ describe('Arguments helpers', () => { Arguments.receivingRef({ objectId: '1', version: '123', - digest: toB58(new Uint8Array(32).fill(0x1)), + digest: toBase58(new Uint8Array(32).fill(0x1)), }), Arguments.sharedObjectRef({ objectId: '2', @@ -23,7 +23,7 @@ describe('Arguments helpers', () => { Arguments.objectRef({ objectId: '3', version: '123', - digest: toB58(new Uint8Array(32).fill(0x1)), + digest: toBase58(new Uint8Array(32).fill(0x1)), }), Arguments.pure.address('0x2'), Arguments.object.system(), diff --git a/sdk/typescript/test/unit/cryptography/ed25519-keypair.test.ts b/sdk/typescript/test/unit/cryptography/ed25519-keypair.test.ts index de32c40114fd5..3f432401e900c 100644 --- a/sdk/typescript/test/unit/cryptography/ed25519-keypair.test.ts +++ b/sdk/typescript/test/unit/cryptography/ed25519-keypair.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64, toB58 } from '@mysten/bcs'; +import { fromBase64, toBase58 } from '@mysten/bcs'; import nacl from 'tweetnacl'; import { describe, expect, it } from 'vitest'; @@ -40,7 +40,7 @@ describe('ed25519-keypair', () => { }); it('create keypair from secret key', () => { - const secretKey = fromB64(VALID_SECRET_KEY); + const secretKey = fromBase64(VALID_SECRET_KEY); const keypair = Ed25519Keypair.fromSecretKey(secretKey); expect(keypair.getPublicKey().toBase64()).toEqual( 'Gy9JCW4+Xb0Pz6nAwM2S2as7IVRLNNXdSmXZi4eLmSI=', @@ -125,7 +125,7 @@ describe('ed25519-keypair', () => { { objectId: (Math.random() * 100000).toFixed(0).padEnd(64, '0'), version: String((Math.random() * 10000).toFixed(0)), - digest: toB58( + digest: toBase58( new Uint8Array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, diff --git a/sdk/typescript/test/unit/cryptography/keypair.test.ts b/sdk/typescript/test/unit/cryptography/keypair.test.ts index b3403ef2bef38..a7abf32ea6f39 100644 --- a/sdk/typescript/test/unit/cryptography/keypair.test.ts +++ b/sdk/typescript/test/unit/cryptography/keypair.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { toB64 } from '@mysten/bcs'; +import { toBase64 } from '@mysten/bcs'; import { beforeAll, describe, expect, it } from 'vitest'; import { bcs } from '../../../src/bcs/index.js'; @@ -50,19 +50,19 @@ describe('Keypair', () => { const sig2 = await k2.signWithIntent(data, 'TransactionData'); const sig3 = await k3.signWithIntent(bytes, 'PersonalMessage'); - expect(sig1.bytes).toEqual(toB64(bytes)); + expect(sig1.bytes).toEqual(toBase64(bytes)); expect(sig1.bytes).toEqual('CQAAAAVIZWxsbw=='); expect(sig1.signature).toEqual( 'ADXvYCSZk+ZtVL6VfB4+5zson++q0uWYINW4u1QKbbPisLUnNgYPFieiwXxp2SroKzqrULJOXdkPiDESw+IWJgVa4iC0svZel3wS7eYVef9RcLbCLABhaMN7XnxhrwGAgw==', ); - expect(sig2.bytes).toEqual(toB64(data)); + expect(sig2.bytes).toEqual(toBase64(data)); expect(sig2.bytes).toEqual('AAAABUhlbGxv'); expect(sig2.signature).toEqual( 'AdF5r9uq1AygqXu+WrGoe+fSVU2ld1F3lxTAcj9Uh38lR9j6trumZ7VPvIuzsnIlDqeiPKzo98KSVXy+AVraiKsCHRUjB8a3Kw7QQYsOcM2A5/UpW42G9XItP1IT+9I5TzY=', ); - expect(sig3.bytes).toEqual(toB64(bytes)); + expect(sig3.bytes).toEqual(toBase64(bytes)); expect(sig3.bytes).toEqual('CQAAAAVIZWxsbw=='); expect(sig3.signature).toEqual( 'Apd48/4qVHSja5u2i7ZxobPL6iTLulNIuCxbd5GhfWVvcd69k9BtIqpFGMYXYyn7zapyvnJbtUZsF2ILc7Rp/X0CJzIrOokaCigNa8H7LLsj0o9UkG/WQH9fdB9t71diYJo=', @@ -76,19 +76,19 @@ describe('Keypair', () => { const sig2 = await k2.signTransaction(data); const sig3 = await k3.signTransaction(data); - expect(sig1.bytes).toEqual(toB64(data)); + expect(sig1.bytes).toEqual(toBase64(data)); expect(sig1.bytes).toEqual('AAAABUhlbGxv'); expect(sig1.signature).toEqual( 'AKu3E+/SrcDpRHQYYljHAxkcUBzXHwkXtdi7X57rYd3f/VeckrWHfU6GgFiwFLEvLexGWNYPGSJKL12VJTCzFQpa4iC0svZel3wS7eYVef9RcLbCLABhaMN7XnxhrwGAgw==', ); - expect(sig2.bytes).toEqual(toB64(data)); + expect(sig2.bytes).toEqual(toBase64(data)); expect(sig2.bytes).toEqual('AAAABUhlbGxv'); expect(sig2.signature).toEqual( 'AdF5r9uq1AygqXu+WrGoe+fSVU2ld1F3lxTAcj9Uh38lR9j6trumZ7VPvIuzsnIlDqeiPKzo98KSVXy+AVraiKsCHRUjB8a3Kw7QQYsOcM2A5/UpW42G9XItP1IT+9I5TzY=', ); - expect(sig3.bytes).toEqual(toB64(data)); + expect(sig3.bytes).toEqual(toBase64(data)); expect(sig3.bytes).toEqual('AAAABUhlbGxv'); expect(sig3.signature).toEqual( 'AvKS25z99kTnsHe70qf2Dd9+Lz0DHTzM7cKFrMF47Z2RNy6qSFzOV87thExeKqug6VvEFiaqYhplx3fsT/rgk9kCJzIrOokaCigNa8H7LLsj0o9UkG/WQH9fdB9t71diYJo=', @@ -102,19 +102,19 @@ describe('Keypair', () => { const sig2 = await k2.signPersonalMessage(data); const sig3 = await k3.signPersonalMessage(data); - expect(sig1.bytes).toEqual(toB64(data)); + expect(sig1.bytes).toEqual(toBase64(data)); expect(sig1.bytes).toEqual('AAAABUhlbGxv'); expect(sig1.signature).toEqual( 'ADXvYCSZk+ZtVL6VfB4+5zson++q0uWYINW4u1QKbbPisLUnNgYPFieiwXxp2SroKzqrULJOXdkPiDESw+IWJgVa4iC0svZel3wS7eYVef9RcLbCLABhaMN7XnxhrwGAgw==', ); - expect(sig2.bytes).toEqual(toB64(data)); + expect(sig2.bytes).toEqual(toBase64(data)); expect(sig2.bytes).toEqual('AAAABUhlbGxv'); expect(sig2.signature).toEqual( 'AViWuVdzTX9lJ2DBIPd4YR2bqTHC07AC9NZ1vbA1k/YeeSCuH6Kd1g3izZB332JgLP7GxjppPmWk4GwNlvbH0vICHRUjB8a3Kw7QQYsOcM2A5/UpW42G9XItP1IT+9I5TzY=', ); - expect(sig3.bytes).toEqual(toB64(data)); + expect(sig3.bytes).toEqual(toBase64(data)); expect(sig3.bytes).toEqual('AAAABUhlbGxv'); expect(sig3.signature).toEqual( 'Apd48/4qVHSja5u2i7ZxobPL6iTLulNIuCxbd5GhfWVvcd69k9BtIqpFGMYXYyn7zapyvnJbtUZsF2ILc7Rp/X0CJzIrOokaCigNa8H7LLsj0o9UkG/WQH9fdB9t71diYJo=', diff --git a/sdk/typescript/test/unit/cryptography/multisig.publickey.test.ts b/sdk/typescript/test/unit/cryptography/multisig.publickey.test.ts index c9fde2c9b49d6..b122370b1a722 100644 --- a/sdk/typescript/test/unit/cryptography/multisig.publickey.test.ts +++ b/sdk/typescript/test/unit/cryptography/multisig.publickey.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64 } from '@mysten/bcs'; +import { fromBase64 } from '@mysten/bcs'; import { blake2b } from '@noble/hashes/blake2b'; import { bytesToHex } from '@noble/hashes/utils'; import { beforeAll, describe, expect, it } from 'vitest'; @@ -204,7 +204,7 @@ describe('Publickey', () => { const sig2 = await k2.signPersonalMessage(data); const multisig = multiSigPublicKey.combinePartialSignatures([sig1.signature, sig2.signature]); - const rawBytes = fromB64(multisig).slice(134); + const rawBytes = fromBase64(multisig).slice(134); expect(multiSigPublicKey.toRawBytes()).toEqual(rawBytes); expect(multiSigPublicKey.toRawBytes()).toEqual( @@ -356,7 +356,7 @@ describe('Publickey', () => { 'AwIANe9gJJmT5m1UvpV8Hj7nOyif76rS5Zgg1bi7VApts+KwtSc2Bg8WJ6LBfGnZKugrOqtQsk5d2Q+IMRLD4hYmBQFYlrlXc01/ZSdgwSD3eGEdm6kxwtOwAvTWdb2wNZP2Hnkgrh+indYN4s2Qd99iYCz+xsY6aT5lpOBsDZb2x9LyAwADAFriILSy9l6XfBLt5hV5/1FwtsIsAGFow3tefGGvAYCDAQECHRUjB8a3Kw7QQYsOcM2A5/UpW42G9XItP1IT+9I5TzYCAgInMis6iRoKKA1rwfssuyPSj1SQb9ZAf190H23vV2JgmgMDAA==', ); - const decoded = bcs.MultiSig.parse(fromB64(multisig).slice(1)); + const decoded = bcs.MultiSig.parse(fromBase64(multisig).slice(1)); expect(decoded).toEqual({ bitmap: 3, @@ -425,7 +425,7 @@ describe('Publickey', () => { const multisig = multiSigPublicKey.combinePartialSignatures([sig1.signature, sig2.signature]); - const bytes = fromB64(multisig); + const bytes = fromBase64(multisig); const multiSigStruct: MultiSigStruct = bcs.MultiSig.parse(bytes.slice(1)); const parsedPartialSignatures = parsePartialSignatures(multiSigStruct); diff --git a/sdk/typescript/test/unit/cryptography/multisig.test.ts b/sdk/typescript/test/unit/cryptography/multisig.test.ts index deb8066c1861a..9fc2ae848000c 100644 --- a/sdk/typescript/test/unit/cryptography/multisig.test.ts +++ b/sdk/typescript/test/unit/cryptography/multisig.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64, toB58, toB64 } from '@mysten/bcs'; +import { fromBase64, toBase58, toBase64 } from '@mysten/bcs'; import { beforeAll, describe, expect, it, test } from 'vitest'; import { bcs } from '../../../src/bcs'; @@ -50,7 +50,7 @@ describe('Multisig scenarios', () => { { objectId: (Math.random() * 100000).toFixed(0).padEnd(64, '0'), version: String((Math.random() * 10000).toFixed(0)), - digest: toB58( + digest: toBase58( new Uint8Array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, @@ -133,7 +133,7 @@ describe('Multisig scenarios', () => { tmp.set([SIGNATURE_SCHEME_TO_FLAG['MultiSig']]); tmp.set(bytes, 1); - const multisig = toB64(tmp); + const multisig = toBase64(tmp); expect(() => multiSigPublicKey.combinePartialSignatures([sig1.signature, sig3.signature]), @@ -198,7 +198,7 @@ describe('Multisig scenarios', () => { tmp.set([SIGNATURE_SCHEME_TO_FLAG['MultiSig']]); tmp.set(bytes, 1); - const multisig = toB64(tmp); + const multisig = toBase64(tmp); expect(() => multiSigPublicKey.combinePartialSignatures([sig2.signature, sig2.signature]), @@ -436,7 +436,7 @@ describe('Multisig address creation:', () => { '0xb9c0780a3943cde13a2409bf1a6f06ae60b0dff2b2f373260cf627aa4f43a588', ); const data = new Uint8Array( - fromB64( + fromBase64( 'AAABACACAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgEBAQABAAC5wHgKOUPN4TokCb8abwauYLDf8rLzcyYM9ieqT0OliAGbB4FfBEl+LgXSLKw6oGFBCyCGjMYZFUxCocYb6ZAnFwEAAAAAAAAAIJZw7UpW1XHubORIOaY8d2+WyBNwoJ+FEAxlsa7h7JHrucB4CjlDzeE6JAm/Gm8GrmCw3/Ky83MmDPYnqk9DpYgBAAAAAAAAABAnAAAAAAAAAA==', ), ); @@ -449,7 +449,7 @@ describe('Multisig address creation:', () => { 'AwIAcAEsWrZtlsE3AdGUKJAPag8Tu6HPfMW7gEemeneO9fmNGiJP/rDZu/tL75lr8A22eFDx9K2G1DL4v8XlmuTtCgOaBwUDTTE3MzE4MDg5MTI1OTUyNDIxNzM2MzQyMjYzNzE3OTMyNzE5NDM3NzE3ODQ0MjgyNDEwMTg3OTU3OTg0NzUxOTM5OTQyODk4MjUxMjUwTTExMzczOTY2NjQ1NDY5MTIyNTgyMDc0MDgyMjk1OTg1Mzg4MjU4ODQwNjgxNjE4MjY4NTkzOTc2Njk3MzI1ODkyMjgwOTE1NjgxMjA3ATEDAkw1OTM5ODcxMTQ3MzQ4ODM0OTk3MzYxNzIwMTIyMjM4OTgwMTc3MTUyMzAzMjc0MzExMDQ3MjQ5OTA1OTQyMzg0OTE1NzY4NjkwODk1TDQ1MzM1NjgyNzExMzQ3ODUyNzg3MzEyMzQ1NzAzNjE0ODI2NTE5OTY3NDA3OTE4ODgyODU4NjQ5NjY4ODQwMzI3MTcwNDk4MTE3MDgCTTEwNTY0Mzg3Mjg1MDcxNTU1NDY5NzUzOTkwNjYxNDEwODQwMTE4NjM1OTI1NDY2NTk3MDM3MDE4MDU4NzcwMDQxMzQ3NTE4NDYxMzY4TTEyNTk3MzIzNTQ3Mjc3NTc5MTQ0Njk4NDk2MzcyMjQyNjE1MzY4MDg1ODAxMzEzMzQzMTU1NzM1NTExMzMwMDAzODg0NzY3OTU3ODU0AgExATADTTE1NzkxNTg5NDcyNTU2ODI2MjYzMjMxNjQ0NzI4ODczMzM3NjI5MDE1MjY5OTg0Njk5NDA0MDczNjIzNjAzMzUyNTM3Njc4ODEzMTcxTDQ1NDc4NjY0OTkyNDg4ODE0NDk2NzYxNjExNTgwMjQ3NDgwNjA0ODUzNzMyNTAwMjk0MjM5MDQxMTMwMTc0MjI1MzkwMzcxNjI1MjcBMTF3aWFYTnpJam9pYUhSMGNITTZMeTlwWkM1MGQybDBZMmd1ZEhZdmIyRjFkR2d5SWl3AjJleUpoYkdjaU9pSlNVekkxTmlJc0luUjVjQ0k2SWtwWFZDSXNJbXRwWkNJNklqRWlmUU0yMDc5NDc4ODU1OTYyMDY2OTU5NjIwNjQ1NzAyMjk2NjE3Njk4NjY4ODcyNzg3NjEyODIyMzYyODExMzkxNjM4MDkyNzUwMjczNzkxMQoAAAAAAAAAYQAR6ZEOSb8am6giraofTNFOXN6N5etgegC1TsTKup7HNMtyZWPBn9WaDwPe+naJustyRgVE7K8umsX6h3Fa7UQMucbuFjDvPnERRKZI2wa7sihPcnTPvuU//O5QPMGkkgADAAIADX2rNYyNrapO+gBJp1sHQ2VVsQo2ghm7aA9wVxNJ13UBAzwbaHR0cHM6Ly9pZC50d2l0Y2gudHYvb2F1dGgyLflu6Eag/zG3tLd5CtZRYx9p1t34RovVSn/+uHFiYfcBAQA=', ); - const decoded = parsePartialSignatures(bcs.MultiSig.parse(fromB64(multisig).slice(1))); + const decoded = parsePartialSignatures(bcs.MultiSig.parse(fromBase64(multisig).slice(1))); expect(decoded).toEqual([ { signature: parseSerializedSignature(sig1.signature).signature, @@ -631,7 +631,7 @@ describe('MultisigKeypair', () => { { objectId: (Math.random() * 100000).toFixed(0).padEnd(64, '0'), version: String((Math.random() * 10000).toFixed(0)), - digest: toB58( + digest: toBase58( new Uint8Array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, diff --git a/sdk/typescript/test/unit/cryptography/publickey.test.ts b/sdk/typescript/test/unit/cryptography/publickey.test.ts index 1961a19af4043..b3cfe1ead7060 100644 --- a/sdk/typescript/test/unit/cryptography/publickey.test.ts +++ b/sdk/typescript/test/unit/cryptography/publickey.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { toB64 } from '@mysten/bcs'; +import { toBase64 } from '@mysten/bcs'; import { blake2b } from '@noble/hashes/blake2b'; import { bytesToHex } from '@noble/hashes/utils'; import { beforeAll, describe, expect, it } from 'vitest'; @@ -56,12 +56,12 @@ describe('Publickey', () => { }); it('`toBase64()` should return a valid base-64 representation', async () => { - expect(pk2.toBase64()).toEqual(toB64(pk2.toRawBytes())); + expect(pk2.toBase64()).toEqual(toBase64(pk2.toRawBytes())); expect(pk2.toBase64()).toEqual('Ah0VIwfGtysO0EGLDnDNgOf1KVuNhvVyLT9SE/vSOU82'); }); it('`toSuiPublicKey()` should return a valid sui representation', async () => { - expect(pk2.toSuiPublicKey()).toEqual(toB64(pk2.toSuiBytes())); + expect(pk2.toSuiPublicKey()).toEqual(toBase64(pk2.toSuiBytes())); expect(pk2.toSuiPublicKey()).toEqual('AQIdFSMHxrcrDtBBiw5wzYDn9SlbjYb1ci0/UhP70jlPNg=='); }); diff --git a/sdk/typescript/test/unit/cryptography/secp256k1-keypair.test.ts b/sdk/typescript/test/unit/cryptography/secp256k1-keypair.test.ts index 814eaacb624e0..f4290e3345552 100644 --- a/sdk/typescript/test/unit/cryptography/secp256k1-keypair.test.ts +++ b/sdk/typescript/test/unit/cryptography/secp256k1-keypair.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64, toB58, toB64 } from '@mysten/bcs'; +import { fromBase64, toBase58, toBase64 } from '@mysten/bcs'; import { secp256k1 } from '@noble/curves/secp256k1'; import { sha256 } from '@noble/hashes/sha256'; import { describe, expect, it } from 'vitest'; @@ -66,7 +66,7 @@ describe('secp256k1-keypair', () => { it('create keypair from secret key', () => { const secret_key = new Uint8Array(VALID_SECP256K1_SECRET_KEY); const pub_key = new Uint8Array(VALID_SECP256K1_PUBLIC_KEY); - let pub_key_base64 = toB64(pub_key); + let pub_key_base64 = toBase64(pub_key); const keypair = Secp256k1Keypair.fromSecretKey(secret_key); expect(keypair.getPublicKey().toRawBytes()).toEqual(new Uint8Array(pub_key)); expect(keypair.getPublicKey().toBase64()).toEqual(pub_key_base64); @@ -74,8 +74,8 @@ describe('secp256k1-keypair', () => { it('creating keypair from invalid secret key throws error', () => { const secret_key = new Uint8Array(INVALID_SECP256K1_SECRET_KEY); - let secret_key_base64 = toB64(secret_key); - const secretKey = fromB64(secret_key_base64); + let secret_key_base64 = toBase64(secret_key); + const secretKey = fromBase64(secret_key_base64); expect(() => { Secp256k1Keypair.fromSecretKey(secretKey); }).toThrow('private key must be 32 bytes, hex or bigint, not object'); @@ -169,7 +169,7 @@ describe('secp256k1-keypair', () => { { objectId: (Math.random() * 100000).toFixed(0).padEnd(64, '0'), version: String((Math.random() * 10000).toFixed(0)), - digest: toB58( + digest: toBase58( new Uint8Array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, diff --git a/sdk/typescript/test/unit/cryptography/secp256k1-publickey.test.ts b/sdk/typescript/test/unit/cryptography/secp256k1-publickey.test.ts index 6c887e8135b0e..c12821a40ed10 100644 --- a/sdk/typescript/test/unit/cryptography/secp256k1-publickey.test.ts +++ b/sdk/typescript/test/unit/cryptography/secp256k1-publickey.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { toB64, toHEX } from '@mysten/bcs'; +import { toBase64, toHex } from '@mysten/bcs'; import { describe, expect, it } from 'vitest'; import { Secp256k1PublicKey } from '../../../src/keypairs/secp256k1/publickey'; @@ -42,13 +42,13 @@ describe('Secp256k1PublicKey', () => { expect(() => { const invalid_pubkey_buffer = new Uint8Array(INVALID_SECP256K1_PUBLIC_KEY); - let invalid_pubkey_base64 = toB64(invalid_pubkey_buffer); + let invalid_pubkey_base64 = toBase64(invalid_pubkey_buffer); new Secp256k1PublicKey(invalid_pubkey_base64); }).toThrow(); expect(() => { const pubkey_buffer = new Uint8Array(VALID_SECP256K1_PUBLIC_KEY); - let wrong_encode = toHEX(pubkey_buffer); + let wrong_encode = toHex(pubkey_buffer); new Secp256k1PublicKey(wrong_encode); }).toThrow(); @@ -59,14 +59,14 @@ describe('Secp256k1PublicKey', () => { it('toBase64', () => { const pub_key = new Uint8Array(VALID_SECP256K1_PUBLIC_KEY); - let pub_key_base64 = toB64(pub_key); + let pub_key_base64 = toBase64(pub_key); const key = new Secp256k1PublicKey(pub_key_base64); expect(key.toBase64()).toEqual(pub_key_base64); }); it('toBuffer', () => { const pub_key = new Uint8Array(VALID_SECP256K1_PUBLIC_KEY); - let pub_key_base64 = toB64(pub_key); + let pub_key_base64 = toBase64(pub_key); const key = new Secp256k1PublicKey(pub_key_base64); expect(key.toRawBytes().length).toBe(33); expect(new Secp256k1PublicKey(key.toRawBytes()).equals(key)).toBe(true); diff --git a/sdk/typescript/test/unit/cryptography/secp256r1-keypair.test.ts b/sdk/typescript/test/unit/cryptography/secp256r1-keypair.test.ts index a94028e5652b3..e96b6d8832516 100644 --- a/sdk/typescript/test/unit/cryptography/secp256r1-keypair.test.ts +++ b/sdk/typescript/test/unit/cryptography/secp256r1-keypair.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64, toB58, toB64 } from '@mysten/bcs'; +import { fromBase64, toBase58, toBase64 } from '@mysten/bcs'; import { secp256r1 } from '@noble/curves/p256'; import { sha256 } from '@noble/hashes/sha256'; import { describe, expect, it } from 'vitest'; @@ -61,7 +61,7 @@ describe('secp256r1-keypair', () => { it('create keypair from secret key', () => { const secret_key = new Uint8Array(VALID_SECP256R1_SECRET_KEY); const pub_key = new Uint8Array(VALID_SECP256R1_PUBLIC_KEY); - let pub_key_base64 = toB64(pub_key); + let pub_key_base64 = toBase64(pub_key); const keypair = Secp256r1Keypair.fromSecretKey(secret_key); expect(keypair.getPublicKey().toRawBytes()).toEqual(new Uint8Array(pub_key)); expect(keypair.getPublicKey().toBase64()).toEqual(pub_key_base64); @@ -69,8 +69,8 @@ describe('secp256r1-keypair', () => { it('creating keypair from invalid secret key throws error', () => { const secret_key = new Uint8Array(INVALID_SECP256R1_SECRET_KEY); - let secret_key_base64 = toB64(secret_key); - const secretKey = fromB64(secret_key_base64); + let secret_key_base64 = toBase64(secret_key); + const secretKey = fromBase64(secret_key_base64); expect(() => { Secp256r1Keypair.fromSecretKey(secretKey); }).toThrow('private key must be 32 bytes, hex or bigint, not object'); @@ -164,7 +164,7 @@ describe('secp256r1-keypair', () => { { objectId: (Math.random() * 100000).toFixed(0).padEnd(64, '0'), version: String((Math.random() * 10000).toFixed(0)), - digest: toB58( + digest: toBase58( new Uint8Array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, diff --git a/sdk/typescript/test/unit/cryptography/secp256r1-publickey.test.ts b/sdk/typescript/test/unit/cryptography/secp256r1-publickey.test.ts index fc238799f2119..9bb7fc07f20f0 100644 --- a/sdk/typescript/test/unit/cryptography/secp256r1-publickey.test.ts +++ b/sdk/typescript/test/unit/cryptography/secp256r1-publickey.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { toB64, toHEX } from '@mysten/bcs'; +import { toBase64, toHex } from '@mysten/bcs'; import { describe, expect, it } from 'vitest'; import { Secp256r1PublicKey } from '../../../src/keypairs/secp256r1/publickey'; @@ -27,13 +27,13 @@ describe('Secp256r1PublicKey', () => { expect(() => { const invalid_pubkey_buffer = new Uint8Array(INVALID_SECP256R1_PUBLIC_KEY); - let invalid_pubkey_base64 = toB64(invalid_pubkey_buffer); + let invalid_pubkey_base64 = toBase64(invalid_pubkey_buffer); new Secp256r1PublicKey(invalid_pubkey_base64); }).toThrow(); expect(() => { const pubkey_buffer = new Uint8Array(VALID_SECP256R1_PUBLIC_KEY); - let wrong_encode = toHEX(pubkey_buffer); + let wrong_encode = toHex(pubkey_buffer); new Secp256r1PublicKey(wrong_encode); }).toThrow(); @@ -44,14 +44,14 @@ describe('Secp256r1PublicKey', () => { it('toBase64', () => { const pub_key = new Uint8Array(VALID_SECP256R1_PUBLIC_KEY); - let pub_key_base64 = toB64(pub_key); + let pub_key_base64 = toBase64(pub_key); const key = new Secp256r1PublicKey(pub_key_base64); expect(key.toBase64()).toEqual(pub_key_base64); }); it('toBuffer', () => { const pub_key = new Uint8Array(VALID_SECP256R1_PUBLIC_KEY); - let pub_key_base64 = toB64(pub_key); + let pub_key_base64 = toBase64(pub_key); const key = new Secp256r1PublicKey(pub_key_base64); expect(key.toRawBytes().length).toBe(33); expect(new Secp256r1PublicKey(key.toRawBytes()).equals(key)).toBe(true); diff --git a/sdk/typescript/test/unit/cryptography/signature.test.ts b/sdk/typescript/test/unit/cryptography/signature.test.ts index 11409936e59f7..1f2e3bdc4546b 100644 --- a/sdk/typescript/test/unit/cryptography/signature.test.ts +++ b/sdk/typescript/test/unit/cryptography/signature.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { fromB64, toB64 } from '@mysten/bcs'; +import { fromBase64, toBase64 } from '@mysten/bcs'; import { beforeAll, describe, expect, it } from 'vitest'; import { bcs } from '../../../src/bcs'; @@ -79,7 +79,7 @@ describe('Signature', () => { sig3.signature, ]); - const decoded = parsePartialSignatures(bcs.MultiSig.parse(fromB64(multisig).slice(1))); + const decoded = parsePartialSignatures(bcs.MultiSig.parse(fromBase64(multisig).slice(1))); const SerializeSignatureInput: SerializeSignatureInput[] = [ { @@ -125,7 +125,7 @@ describe('Signature', () => { const multisig = publicKey.combinePartialSignatures([sig1.signature]); - const decoded = parsePartialSignatures(bcs.MultiSig.parse(fromB64(multisig).slice(1))); + const decoded = parsePartialSignatures(bcs.MultiSig.parse(fromBase64(multisig).slice(1))); const SerializeSignatureInput: SerializeSignatureInput[] = [ { @@ -182,9 +182,9 @@ describe('Signature', () => { it('`parseSerializedSignature()` should handle unsupported schemes', async () => { const data = new Uint8Array([0, 0, 0, 5, 72, 101, 108, 108, 111]); const sig1 = await k1.signPersonalMessage(data); - const bytes = fromB64(sig1.signature); + const bytes = fromBase64(sig1.signature); bytes[0] = 0x06; - const invalidSignature = toB64(bytes); + const invalidSignature = toBase64(bytes); expect(() => parseSerializedSignature(invalidSignature)).toThrowError(); }); diff --git a/sdk/typescript/test/unit/object-inputs.test.ts b/sdk/typescript/test/unit/object-inputs.test.ts index ddde51d40331f..25d834c6065ad 100644 --- a/sdk/typescript/test/unit/object-inputs.test.ts +++ b/sdk/typescript/test/unit/object-inputs.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { toB58 } from '@mysten/bcs'; +import { toBase58 } from '@mysten/bcs'; import { describe, expect, it } from 'vitest'; import { Transaction } from '../../src/transactions'; @@ -17,7 +17,7 @@ describe('Transaction inputs', () => { tx.receivingRef({ objectId: '1', version: '123', - digest: toB58(new Uint8Array(32).fill(0x1)), + digest: toBase58(new Uint8Array(32).fill(0x1)), }), tx.sharedObjectRef({ objectId: '2', @@ -27,7 +27,7 @@ describe('Transaction inputs', () => { tx.objectRef({ objectId: '3', version: '123', - digest: toB58(new Uint8Array(32).fill(0x1)), + digest: toBase58(new Uint8Array(32).fill(0x1)), }), tx.pure.address('0x2'), tx.object.system(), diff --git a/sdk/typescript/test/unit/utils/dynamic-fields.test.ts b/sdk/typescript/test/unit/utils/dynamic-fields.test.ts new file mode 100644 index 0000000000000..2f275ec991959 --- /dev/null +++ b/sdk/typescript/test/unit/utils/dynamic-fields.test.ts @@ -0,0 +1,19 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +import { describe, expect, test } from 'vitest'; + +import { bcs } from '../../../src/bcs'; +import { deriveDynamicFieldID } from '../../../src/utils'; + +describe('dynamic field utils', () => { + test('deriveDynamicFieldID', () => { + const parentId = '0xbef336120c90707eb387d72dde9c0e9f6fea37b9f02b1ba8de271c64ad7b6db0'; + const key = bcs.u64().serialize(0).toBytes(); + const typeTag = 'u64'; + + const result = deriveDynamicFieldID(parentId, typeTag, key); + + expect(result).toBe('0x6552700b707460a3f48d8348f7531c4bba7a9546b4cf445ba265ad2ba06b1bb3'); + }); +}); diff --git a/sdk/typescript/test/unit/v1-json.test.ts b/sdk/typescript/test/unit/v1-json.test.ts index 7eb31dd1545f2..4886441718019 100644 --- a/sdk/typescript/test/unit/v1-json.test.ts +++ b/sdk/typescript/test/unit/v1-json.test.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { toB58 } from '@mysten/bcs'; +import { toBase58 } from '@mysten/bcs'; import { describe, expect, it } from 'vitest'; import { Inputs, Transaction } from '../../src/transactions'; @@ -18,7 +18,7 @@ describe('V1 JSON serialization', () => { Inputs.ReceivingRef({ objectId: '1', version: '123', - digest: toB58(new Uint8Array(32).fill(0x1)), + digest: toBase58(new Uint8Array(32).fill(0x1)), }), ), tx.object( @@ -32,7 +32,7 @@ describe('V1 JSON serialization', () => { Inputs.ObjectRef({ objectId: '3', version: '123', - digest: toB58(new Uint8Array(32).fill(0x1)), + digest: toBase58(new Uint8Array(32).fill(0x1)), }), ), tx.pure.address('0x2'), diff --git a/sdk/wallet-standard/CHANGELOG.md b/sdk/wallet-standard/CHANGELOG.md index dea2f51549338..0e4aea6b9f07f 100644 --- a/sdk/wallet-standard/CHANGELOG.md +++ b/sdk/wallet-standard/CHANGELOG.md @@ -1,5 +1,35 @@ # @mysten/wallet-standard +## 0.13.8 + +### Patch Changes + +- Updated dependencies [477d2a4] + - @mysten/sui@1.13.0 + +## 0.13.7 + +### Patch Changes + +- Updated dependencies [5436a90] +- Updated dependencies [5436a90] + - @mysten/sui@1.12.0 + +## 0.13.6 + +### Patch Changes + +- Updated dependencies [489f421] +- Updated dependencies [489f421] + - @mysten/sui@1.11.0 + +## 0.13.5 + +### Patch Changes + +- Updated dependencies [830b8d8] + - @mysten/sui@1.10.0 + ## 0.13.4 ### Patch Changes diff --git a/sdk/wallet-standard/package.json b/sdk/wallet-standard/package.json index 847a9d0dbfeed..f3dfcb12cc5b1 100644 --- a/sdk/wallet-standard/package.json +++ b/sdk/wallet-standard/package.json @@ -1,6 +1,6 @@ { "name": "@mysten/wallet-standard", - "version": "0.13.4", + "version": "0.13.8", "description": "A suite of standard utilities for implementing wallets based on the Wallet Standard.", "license": "Apache-2.0", "author": "Mysten Labs ", diff --git a/sdk/wallet-standard/src/wallet.ts b/sdk/wallet-standard/src/wallet.ts index 49ef020da9349..4ae52b9b94aa5 100644 --- a/sdk/wallet-standard/src/wallet.ts +++ b/sdk/wallet-standard/src/wallet.ts @@ -3,7 +3,7 @@ import { bcs } from '@mysten/sui/bcs'; import { Transaction } from '@mysten/sui/transactions'; -import { fromB64, toB64 } from '@mysten/sui/utils'; +import { fromBase64, toBase64 } from '@mysten/sui/utils'; import type { WalletWithFeatures } from '@wallet-standard/core'; import type { @@ -61,7 +61,7 @@ export async function signAndExecuteTransaction( txSignatures: [signature], intentMessage: { value: bcsTransaction }, }, - ] = bcs.SenderSignedData.parse(fromB64(rawTransaction!)); + ] = bcs.SenderSignedData.parse(fromBase64(rawTransaction!)); const bytes = bcs.TransactionData.serialize(bcsTransaction).toBase64(); @@ -69,7 +69,7 @@ export async function signAndExecuteTransaction( digest, signature, bytes, - effects: toB64(new Uint8Array(rawEffects!)), + effects: toBase64(new Uint8Array(rawEffects!)), }; } diff --git a/sdk/zklogin/CHANGELOG.md b/sdk/zklogin/CHANGELOG.md index 785be11d28536..94e9b9c9fb2fe 100644 --- a/sdk/zklogin/CHANGELOG.md +++ b/sdk/zklogin/CHANGELOG.md @@ -1,5 +1,36 @@ # @mysten/zklogin +## 0.7.23 + +### Patch Changes + +- Updated dependencies [477d2a4] + - @mysten/sui@1.13.0 + +## 0.7.22 + +### Patch Changes + +- Updated dependencies [5436a90] +- Updated dependencies [5436a90] + - @mysten/sui@1.12.0 + +## 0.7.21 + +### Patch Changes + +- Updated dependencies [489f421] +- Updated dependencies [489f421] + - @mysten/sui@1.11.0 + - @mysten/bcs@1.1.0 + +## 0.7.20 + +### Patch Changes + +- Updated dependencies [830b8d8] + - @mysten/sui@1.10.0 + ## 0.7.19 ### Patch Changes diff --git a/sdk/zklogin/package.json b/sdk/zklogin/package.json index c05f010f35975..381fb1eef207e 100644 --- a/sdk/zklogin/package.json +++ b/sdk/zklogin/package.json @@ -1,6 +1,6 @@ { "name": "@mysten/zklogin", - "version": "0.7.19", + "version": "0.7.23", "description": "Utilities for interacting with zkLogin in Sui", "license": "Apache-2.0", "author": "Mysten Labs ", diff --git a/sdk/zklogin/src/nonce.ts b/sdk/zklogin/src/nonce.ts index 1a89c9175ffe3..f22ddc85f82d6 100644 --- a/sdk/zklogin/src/nonce.ts +++ b/sdk/zklogin/src/nonce.ts @@ -1,7 +1,7 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -import { toHEX } from '@mysten/bcs'; +import { toHex } from '@mysten/bcs'; import type { PublicKey } from '@mysten/sui/cryptography'; import { toPaddedBigEndianBytes } from '@mysten/sui/zklogin'; import { randomBytes } from '@noble/hashes/utils'; @@ -12,7 +12,7 @@ import { poseidonHash } from './poseidon.js'; export const NONCE_LENGTH = 27; function toBigIntBE(bytes: Uint8Array) { - const hex = toHEX(bytes); + const hex = toHex(bytes); if (hex.length === 0) { return BigInt(0); } diff --git a/sdk/zksend/CHANGELOG.md b/sdk/zksend/CHANGELOG.md index d59ecfa1fecd1..678a1a2129fde 100644 --- a/sdk/zksend/CHANGELOG.md +++ b/sdk/zksend/CHANGELOG.md @@ -1,5 +1,46 @@ # @mysten/zksend +## 0.11.8 + +### Patch Changes + +- Updated dependencies [477d2a4] + - @mysten/sui@1.13.0 + - @mysten/wallet-standard@0.13.8 + +## 0.11.7 + +### Patch Changes + +- af39b6a: Update to reflect GraphQL schema renaming TransactionBlockFilter.signAddress to + .sentAddress. + +## 0.11.6 + +### Patch Changes + +- Updated dependencies [5436a90] +- Updated dependencies [5436a90] + - @mysten/sui@1.12.0 + - @mysten/wallet-standard@0.13.7 + +## 0.11.5 + +### Patch Changes + +- Updated dependencies [489f421] +- Updated dependencies [489f421] + - @mysten/sui@1.11.0 + - @mysten/wallet-standard@0.13.6 + +## 0.11.4 + +### Patch Changes + +- Updated dependencies [830b8d8] + - @mysten/sui@1.10.0 + - @mysten/wallet-standard@0.13.5 + ## 0.11.3 ### Patch Changes diff --git a/sdk/zksend/package.json b/sdk/zksend/package.json index 4879e88c5831c..c7becb82c936f 100644 --- a/sdk/zksend/package.json +++ b/sdk/zksend/package.json @@ -1,6 +1,6 @@ { "name": "@mysten/zksend", - "version": "0.11.3", + "version": "0.11.8", "description": "TODO: Write Description", "license": "Apache-2.0", "author": "Mysten Labs ", diff --git a/sdk/zksend/src/index.test.ts b/sdk/zksend/src/index.test.ts index 0d205fd0bde93..af219bbc0d07d 100644 --- a/sdk/zksend/src/index.test.ts +++ b/sdk/zksend/src/index.test.ts @@ -7,7 +7,7 @@ import { decodeSuiPrivateKey, Keypair } from '@mysten/sui/cryptography'; import { getFaucetHost, requestSuiFromFaucetV0 } from '@mysten/sui/faucet'; import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; import { Transaction } from '@mysten/sui/transactions'; -import { toB64 } from '@mysten/sui/utils'; +import { toBase64 } from '@mysten/sui/utils'; import { beforeAll, expect, test } from 'vitest'; import { getSentTransactionsWithLinks, ZkSendLink, ZkSendLinkBuilder } from './index.js'; @@ -481,7 +481,7 @@ describe('Non contract links', () => { expect(res.balanceChanges?.length).toEqual(2); const link2 = await ZkSendLink.fromUrl( - `https://zksend.con/claim#${toB64(decodeSuiPrivateKey(linkKp.getSecretKey()).secretKey)}`, + `https://zksend.con/claim#${toBase64(decodeSuiPrivateKey(linkKp.getSecretKey()).secretKey)}`, { contract: ZK_BAG_CONFIG, network: 'testnet', diff --git a/sdk/zksend/src/links/builder.ts b/sdk/zksend/src/links/builder.ts index d1c10feb40e12..6d08058092738 100644 --- a/sdk/zksend/src/links/builder.ts +++ b/sdk/zksend/src/links/builder.ts @@ -8,7 +8,7 @@ import type { Keypair, Signer } from '@mysten/sui/cryptography'; import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; import type { TransactionObjectArgument, TransactionObjectInput } from '@mysten/sui/transactions'; import { Transaction } from '@mysten/sui/transactions'; -import { normalizeStructTag, normalizeSuiAddress, SUI_TYPE_ARG, toB64 } from '@mysten/sui/utils'; +import { normalizeStructTag, normalizeSuiAddress, SUI_TYPE_ARG, toBase64 } from '@mysten/sui/utils'; import type { ZkBagContractOptions } from './zk-bag.js'; import { getContractIds, ZkBag } from './zk-bag.js'; @@ -106,7 +106,7 @@ export class ZkSendLinkBuilder { getLink(): string { const link = new URL(this.#host); link.pathname = this.#path; - link.hash = `${this.#contract ? '$' : ''}${toB64( + link.hash = `${this.#contract ? '$' : ''}${toBase64( decodeSuiPrivateKey(this.keypair.getSecretKey()).secretKey, )}`; diff --git a/sdk/zksend/src/links/claim.ts b/sdk/zksend/src/links/claim.ts index d80d6739bcfb2..1e1d2515a5f25 100644 --- a/sdk/zksend/src/links/claim.ts +++ b/sdk/zksend/src/links/claim.ts @@ -14,13 +14,13 @@ import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519'; import type { TransactionObjectArgument } from '@mysten/sui/transactions'; import { Transaction } from '@mysten/sui/transactions'; import { - fromB64, + fromBase64, normalizeStructTag, normalizeSuiAddress, normalizeSuiObjectId, parseStructTag, SUI_TYPE_ARG, - toB64, + toBase64, } from '@mysten/sui/utils'; import type { ZkSendLinkBuilderOptions } from './builder.js'; @@ -126,7 +126,7 @@ export class ZkSendLink { let link: ZkSendLink; if (isContractLink) { - const keypair = Ed25519Keypair.fromSecretKey(fromB64(parsed.hash.slice(2))); + const keypair = Ed25519Keypair.fromSecretKey(fromBase64(parsed.hash.slice(2))); link = new ZkSendLink({ ...options, keypair, @@ -137,7 +137,7 @@ export class ZkSendLink { }); } else { const keypair = Ed25519Keypair.fromSecretKey( - fromB64(isContractLink ? parsed.hash.slice(2) : parsed.hash.slice(1)), + fromBase64(isContractLink ? parsed.hash.slice(2) : parsed.hash.slice(1)), ); link = new ZkSendLink({ @@ -233,7 +233,7 @@ export class ZkSendLink { reclaim ? address : this.keypair!.toSuiAddress(), ); - const bytes = fromB64(sponsored.bytes); + const bytes = fromBase64(sponsored.bytes); const signature = sign ? await sign(bytes) : (await this.keypair!.signTransaction(bytes)).signature; @@ -546,7 +546,7 @@ export class ZkSendLink { network: this.#network, sender, claimer, - transactionBlockKindBytes: toB64( + transactionBlockKindBytes: toBase64( await tx.build({ onlyTransactionKind: true, client: this.#client, diff --git a/sdk/zksend/src/links/list-created-links.ts b/sdk/zksend/src/links/list-created-links.ts index 05a8f498e0046..2e8248f824e7c 100644 --- a/sdk/zksend/src/links/list-created-links.ts +++ b/sdk/zksend/src/links/list-created-links.ts @@ -5,7 +5,7 @@ import { bcs } from '@mysten/sui/bcs'; import type { SuiClient } from '@mysten/sui/client'; import { SuiGraphQLClient } from '@mysten/sui/graphql'; import { graphql } from '@mysten/sui/graphql/schemas/2024.4'; -import { fromB64, normalizeSuiAddress } from '@mysten/sui/utils'; +import { fromBase64, normalizeSuiAddress } from '@mysten/sui/utils'; import { ZkSendLink } from './claim.js'; import type { ZkBagContractOptions } from './zk-bag.js'; @@ -16,7 +16,7 @@ const ListCreatedLinksQuery = graphql(` transactionBlocks( last: 10 before: $cursor - filter: { signAddress: $address, function: $function, kind: PROGRAMMABLE_TX } + filter: { sentAddress: $address, function: $function, kind: PROGRAMMABLE_TX } ) { pageInfo { startCursor @@ -85,7 +85,7 @@ export async function listCreatedLinks({ return null; } - const kind = bcs.SenderSignedData.parse(fromB64(node.bcs))?.[0]?.intentMessage.value.V1 + const kind = bcs.SenderSignedData.parse(fromBase64(node.bcs))?.[0]?.intentMessage.value.V1 .kind; if (!kind.ProgrammableTransaction) { diff --git a/sdk/zksend/src/wallet/index.ts b/sdk/zksend/src/wallet/index.ts index 38496a2051bb1..cff4e1ec4a5ff 100644 --- a/sdk/zksend/src/wallet/index.ts +++ b/sdk/zksend/src/wallet/index.ts @@ -2,7 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 import { Transaction } from '@mysten/sui/transactions'; -import { toB64 } from '@mysten/sui/utils'; +import { toBase64 } from '@mysten/sui/utils'; import type { StandardConnectFeature, StandardConnectMethod, @@ -170,7 +170,7 @@ export class StashedWallet implements Wallet { origin: this.#origin, network: this.#network, }); - const bytes = toB64(message); + const bytes = toBase64(message); const response = await popup.send({ type: 'sign-personal-message', diff --git a/sui-execution/cut/src/plan.rs b/sui-execution/cut/src/plan.rs index 52737297c44ef..abff5eae68427 100644 --- a/sui-execution/cut/src/plan.rs +++ b/sui-execution/cut/src/plan.rs @@ -272,9 +272,8 @@ impl CutPlan { /// their destinations, and their dependencies will be fixed up. On failure, pending changes /// are rolled back. pub(crate) fn execute(&self) -> Result<()> { - self.execute_().map_err(|e| { + self.execute_().inspect_err(|_| { self.rollback(); - e }) } fn execute_(&self) -> Result<()> { diff --git a/sui-execution/latest/sui-adapter/Cargo.toml b/sui-execution/latest/sui-adapter/Cargo.toml index 7a58fe23095ff..89507f02d64d3 100644 --- a/sui-execution/latest/sui-adapter/Cargo.toml +++ b/sui-execution/latest/sui-adapter/Cargo.toml @@ -44,6 +44,5 @@ gas-profiler = [ "sui-types/gas-profiler", "move-vm-runtime/gas-profiler", "move-vm-profiler/gas-profiler", - "move-vm-types/gas-profiler", "move-vm-config/gas-profiler", ] diff --git a/sui-execution/latest/sui-adapter/src/programmable_transactions/execution.rs b/sui-execution/latest/sui-adapter/src/programmable_transactions/execution.rs index 2af37557cfbd6..23a29dbc747a5 100644 --- a/sui-execution/latest/sui-adapter/src/programmable_transactions/execution.rs +++ b/sui-execution/latest/sui-adapter/src/programmable_transactions/execution.rs @@ -304,10 +304,12 @@ mod checked { } let original_address = context.set_link_context(package)?; + let storage_id = ModuleId::new(*package, module.clone()); let runtime_id = ModuleId::new(original_address, module); let return_values = execute_move_call::( context, &mut argument_updates, + &storage_id, &runtime_id, &function, loaded_type_arguments, @@ -341,7 +343,8 @@ mod checked { fn execute_move_call( context: &mut ExecutionContext<'_, '_, '_>, argument_updates: &mut Mode::ArgumentUpdates, - module_id: &ModuleId, + storage_id: &ModuleId, + runtime_id: &ModuleId, function: &IdentStr, type_arguments: Vec, arguments: Vec, @@ -356,14 +359,14 @@ mod checked { last_instr, } = check_visibility_and_signature::( context, - module_id, + runtime_id, function, &type_arguments, is_init, )?; // build the arguments, storing meta data about by-mut-ref args let (tx_context_kind, by_mut_ref, serialized_arguments) = - build_move_args::(context, module_id, function, kind, &signature, &arguments)?; + build_move_args::(context, runtime_id, function, kind, &signature, &arguments)?; // invoke the VM let SerializedReturnValues { mutable_reference_outputs, @@ -371,7 +374,7 @@ mod checked { call_traces, } = vm_move_call( context, - module_id, + runtime_id, function, type_arguments, tx_context_kind, @@ -381,7 +384,12 @@ mod checked { by_mut_ref.len() == mutable_reference_outputs.len(), "lost mutable input" ); - context.take_user_events(module_id, index, last_instr)?; + + if context.protocol_config.relocate_event_module() { + context.take_user_events(storage_id, index, last_instr)?; + } else { + context.take_user_events(runtime_id, index, last_instr)?; + } context.tx_context.call_traces_mut().extend(call_traces); // save the link context because calls to `make_value` below can set new ones, and we don't want @@ -680,16 +688,7 @@ mod checked { UpgradePolicy::Additive => InclusionCheck::Subset.check(cur_module, new_module), UpgradePolicy::DepOnly => InclusionCheck::Equal.check(cur_module, new_module), UpgradePolicy::Compatible => { - let compatibility = Compatibility { - check_datatype_and_pub_function_linking: true, - check_datatype_layout: true, - check_friend_linking: false, - check_private_entry_linking: false, - disallowed_new_abilities: AbilitySet::ALL, - disallow_change_datatype_type_params: true, - // We disallow adding new variants to enums for now - disallow_new_variants: true, - }; + let compatibility = Compatibility::upgrade_check(); compatibility.check(cur_module, new_module) } @@ -881,6 +880,10 @@ mod checked { let return_values = execute_move_call::( context, argument_updates, + // `init` is currently only called on packages when they are published for the + // first time, meaning their runtime and storage IDs match. If this were to change + // for some reason, then we would need to perform relocation here. + &module_id, &module_id, INIT_FN_NAME, vec![], diff --git a/sui-execution/latest/sui-adapter/src/temporary_store.rs b/sui-execution/latest/sui-adapter/src/temporary_store.rs index 94e2ca70534ad..d7ff585890566 100644 --- a/sui-execution/latest/sui-adapter/src/temporary_store.rs +++ b/sui-execution/latest/sui-adapter/src/temporary_store.rs @@ -1058,9 +1058,9 @@ impl<'backing> BackingPackageStore for TemporaryStore<'backing> { if let Some(obj) = self.execution_results.written_objects.get(package_id) { Ok(Some(PackageObject::new(obj.clone()))) } else { - self.store.get_package_object(package_id).map(|obj| { + self.store.get_package_object(package_id).inspect(|obj| { // Track object but leave unchanged - if let Some(v) = &obj { + if let Some(v) = obj { if !self .runtime_packages_loaded_from_db .read() @@ -1075,7 +1075,6 @@ impl<'backing> BackingPackageStore for TemporaryStore<'backing> { .insert(*package_id, v.clone()); } } - obj }) } } diff --git a/sui-execution/latest/sui-move-natives/src/dynamic_field.rs b/sui-execution/latest/sui-move-natives/src/dynamic_field.rs index e6f1139d1519c..ba37beef3f014 100644 --- a/sui-execution/latest/sui-move-natives/src/dynamic_field.rs +++ b/sui-execution/latest/sui-move-natives/src/dynamic_field.rs @@ -302,9 +302,8 @@ pub fn borrow_child_object( if !global_value.exists()? { return Ok(NativeResult::err(context.gas_used(), E_KEY_DOES_NOT_EXIST)); } - let child_ref = global_value.borrow_global().map_err(|err| { + let child_ref = global_value.borrow_global().inspect_err(|err| { assert!(err.major_status() != StatusCode::MISSING_DATA); - err })?; native_charge_gas_early_exit!( @@ -371,9 +370,8 @@ pub fn remove_child_object( if !global_value.exists()? { return Ok(NativeResult::err(context.gas_used(), E_KEY_DOES_NOT_EXIST)); } - let child = global_value.move_from().map_err(|err| { + let child = global_value.move_from().inspect_err(|err| { assert!(err.major_status() != StatusCode::MISSING_DATA); - err })?; native_charge_gas_early_exit!( diff --git a/sui-execution/latest/sui-move-natives/src/object_runtime/mod.rs b/sui-execution/latest/sui-move-natives/src/object_runtime/mod.rs index e9b798197e552..5e252d0742a62 100644 --- a/sui-execution/latest/sui-move-natives/src/object_runtime/mod.rs +++ b/sui-execution/latest/sui-move-natives/src/object_runtime/mod.rs @@ -690,15 +690,18 @@ pub fn get_all_uids( bcs_bytes: &[u8], ) -> Result, /* invariant violation */ String> { let mut ids = BTreeSet::new(); - struct UIDTraversalV2<'i>(&'i mut BTreeSet); - struct UIDCollectorV2<'i>(&'i mut BTreeSet); + struct UIDTraversal<'i>(&'i mut BTreeSet); + struct UIDCollector<'i>(&'i mut BTreeSet); - impl<'i> AV::Traversal for UIDTraversalV2<'i> { + impl<'i, 'b, 'l> AV::Traversal<'b, 'l> for UIDTraversal<'i> { type Error = AV::Error; - fn traverse_struct(&mut self, driver: &mut AV::StructDriver) -> Result<(), Self::Error> { + fn traverse_struct( + &mut self, + driver: &mut AV::StructDriver<'_, 'b, 'l>, + ) -> Result<(), Self::Error> { if driver.struct_layout().type_ == UID::type_() { - while driver.next_field(&mut UIDCollectorV2(self.0))?.is_some() {} + while driver.next_field(&mut UIDCollector(self.0))?.is_some() {} } else { while driver.next_field(self)?.is_some() {} } @@ -706,9 +709,13 @@ pub fn get_all_uids( } } - impl<'i> AV::Traversal for UIDCollectorV2<'i> { + impl<'i, 'b, 'l> AV::Traversal<'b, 'l> for UIDCollector<'i> { type Error = AV::Error; - fn traverse_address(&mut self, value: AccountAddress) -> Result<(), Self::Error> { + fn traverse_address( + &mut self, + _driver: &AV::ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result<(), Self::Error> { self.0.insert(value.into()); Ok(()) } @@ -717,7 +724,7 @@ pub fn get_all_uids( MoveValue::visit_deserialize( bcs_bytes, fully_annotated_layout, - &mut UIDTraversalV2(&mut ids), + &mut UIDTraversal(&mut ids), ) .map_err(|e| format!("Failed to deserialize. {e:?}"))?; Ok(ids) diff --git a/sui-execution/latest/sui-move-natives/src/test_scenario.rs b/sui-execution/latest/sui-move-natives/src/test_scenario.rs index e536c051a68b9..fa7b8bab47381 100644 --- a/sui-execution/latest/sui-move-natives/src/test_scenario.rs +++ b/sui-execution/latest/sui-move-natives/src/test_scenario.rs @@ -10,8 +10,8 @@ use indexmap::{IndexMap, IndexSet}; use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_core_types::{ account_address::AccountAddress, - annotated_value::{MoveStruct, MoveValue, MoveVariant}, - identifier::Identifier, + annotated_value::{MoveFieldLayout, MoveStructLayout, MoveTypeLayout, MoveValue}, + annotated_visitor as AV, language_storage::StructTag, vm_status::StatusCode, }; @@ -864,113 +864,105 @@ fn pack_option(opt: Option) -> Value { )])) } -fn find_all_wrapped_objects<'a>( +fn find_all_wrapped_objects<'a, 'i>( context: &NativeContext, - ids: &mut BTreeSet, + ids: &'i mut BTreeSet, new_object_values: impl IntoIterator)>, ) { - for (_id, ty, value) in new_object_values { - let layout = match context.type_to_type_layout(ty) { - Ok(Some(layout)) => layout, - _ => { - debug_assert!(false); - continue; - } - }; - let annotated_layout = match context.type_to_fully_annotated_layout(ty) { - Ok(Some(layout)) => layout, - _ => { - debug_assert!(false); - continue; - } - }; - let blob = value.borrow().simple_serialize(&layout).unwrap(); - // TODO (annotated-visitor): Replace with a custom visitor. - let move_value = MoveValue::simple_deserialize(&blob, &annotated_layout).unwrap(); - let uid = UID::type_(); - visit_structs(&move_value, |depth, tag, fields| { - if tag != &uid { - return if depth == 0 { - debug_assert!(!fields.is_empty()); - // all object values so the first field is a UID that should be skipped - &fields[1..] - } else { - fields - }; - } - debug_assert!(fields.len() == 1); - let id = &fields[0].1; - let addr_field = match &id { - MoveValue::Struct(MoveStruct { fields, .. }) => { - debug_assert!(fields.len() == 1); - &fields[0].1 - } - v => unreachable!("Not reachable via Move type system: {:?}", v), - }; - let addr = match addr_field { - MoveValue::Address(a) => *a, - v => unreachable!("Not reachable via Move type system: {:?}", v), - }; - ids.insert(addr.into()); - fields - }) + #[derive(Copy, Clone)] + enum LookingFor { + Wrapped, + Uid, + Address, } -} -fn visit_structs(move_value: &MoveValue, mut visit_with_types: FVisitTypes) -where - for<'a> FVisitTypes: FnMut( - /* value depth */ usize, - &StructTag, - &'a Vec<(Identifier, MoveValue)>, - ) -> &'a [(Identifier, MoveValue)], -{ - visit_structs_impl(move_value, &mut visit_with_types, 0) -} + struct Traversal<'i, 'u> { + state: LookingFor, + ids: &'i mut BTreeSet, + uid: &'u MoveStructLayout, + } -fn visit_structs_impl( - move_value: &MoveValue, - visit_with_types: &mut FVisitTypes, - depth: usize, -) where - for<'a> FVisitTypes: FnMut( - /* value depth */ usize, - &StructTag, - &'a Vec<(Identifier, MoveValue)>, - ) -> &'a [(Identifier, MoveValue)], -{ - let next_depth = depth + 1; - match move_value { - MoveValue::U8(_) - | MoveValue::U16(_) - | MoveValue::U32(_) - | MoveValue::U64(_) - | MoveValue::U128(_) - | MoveValue::U256(_) - | MoveValue::Bool(_) - | MoveValue::Address(_) - | MoveValue::Signer(_) => (), - MoveValue::Vector(vs) => { - for v in vs { - visit_structs_impl(v, visit_with_types, next_depth) - } - } - MoveValue::Struct(MoveStruct { type_, fields }) => { - let fields = visit_with_types(depth, type_, fields); - for (_, v) in fields { - visit_structs_impl(v, visit_with_types, next_depth) + impl<'i, 'u, 'b, 'l> AV::Traversal<'b, 'l> for Traversal<'i, 'u> { + type Error = AV::Error; + + fn traverse_struct( + &mut self, + driver: &mut AV::StructDriver<'_, 'b, 'l>, + ) -> Result<(), Self::Error> { + match self.state { + // We're at the top-level of the traversal, looking for an object to recurse into. + // We can unconditionally switch to looking for UID fields at the level below, + // because we know that all the top-level values are objects. + LookingFor::Wrapped => { + while driver + .next_field(&mut Traversal { + state: LookingFor::Uid, + ids: self.ids, + uid: self.uid, + })? + .is_some() + {} + } + + // We are looking for UID fields. If we find one (which we confirm by checking its + // layout), switch to looking for addresses in its sub-structure. + LookingFor::Uid => { + while let Some(MoveFieldLayout { name: _, layout }) = driver.peek_field() { + if matches!(layout, MoveTypeLayout::Struct(s) if s.as_ref() == self.uid) { + driver.next_field(&mut Traversal { + state: LookingFor::Address, + ids: self.ids, + uid: self.uid, + })?; + } else { + driver.next_field(self)?; + } + } + } + + // When looking for addresses, recurse through structs, as the address is nested + // within the UID. + LookingFor::Address => while driver.next_field(self)?.is_some() {}, } + + Ok(()) } - MoveValue::Variant(MoveVariant { - type_, - variant_name: _, - tag: _, - fields, - }) => { - let fields = visit_with_types(depth, type_, fields); - for (_, v) in fields { - visit_structs_impl(v, visit_with_types, next_depth) + + fn traverse_address( + &mut self, + _: &AV::ValueDriver<'_, 'b, 'l>, + address: AccountAddress, + ) -> Result<(), Self::Error> { + // If we're looking for addresses, and we found one, then save it. + if matches!(self.state, LookingFor::Address) { + self.ids.insert(address.into()); } + Ok(()) } } + + let uid = UID::layout(); + for (_id, ty, value) in new_object_values { + let Ok(Some(layout)) = context.type_to_type_layout(ty) else { + debug_assert!(false); + continue; + }; + + let Ok(Some(annotated_layout)) = context.type_to_fully_annotated_layout(ty) else { + debug_assert!(false); + continue; + }; + + let blob = value.borrow().simple_serialize(&layout).unwrap(); + MoveValue::visit_deserialize( + &blob, + &annotated_layout, + &mut Traversal { + state: LookingFor::Wrapped, + ids, + uid: &uid, + }, + ) + .unwrap(); + } } diff --git a/sui-execution/latest/sui-move-natives/src/validator.rs b/sui-execution/latest/sui-move-natives/src/validator.rs index e6d914acb505b..6a46b167aec75 100644 --- a/sui-execution/latest/sui-move-natives/src/validator.rs +++ b/sui-execution/latest/sui-move-natives/src/validator.rs @@ -53,7 +53,7 @@ pub fn validate_metadata_bcs( let validator_metadata = bcs::from_bytes::(&metadata_bytes).map_err(|_| { PartialVMError::new(StatusCode::MALFORMED).with_message( - "ValidateMetadata Move struct does not much internal ValidateMetadata struct" + "ValidateMetadata Move struct does not match internal ValidateMetadata struct" .to_string(), ) })?; diff --git a/sui-execution/latest/sui-verifier/src/verifier.rs b/sui-execution/latest/sui-verifier/src/verifier.rs index 7f7be9e042d53..42a4505632682 100644 --- a/sui-execution/latest/sui-verifier/src/verifier.rs +++ b/sui-execution/latest/sui-verifier/src/verifier.rs @@ -56,7 +56,7 @@ pub fn sui_verify_module_unmetered( fn_info_map: &FnInfoMap, verifier_config: &VerifierConfig, ) -> Result<(), ExecutionError> { - sui_verify_module_metered(module, fn_info_map, &mut DummyMeter, verifier_config).map_err( + sui_verify_module_metered(module, fn_info_map, &mut DummyMeter, verifier_config).inspect_err( |err| { // We must never see timeout error in execution debug_assert!( @@ -66,7 +66,6 @@ pub fn sui_verify_module_unmetered( ), "Unexpected timeout error in execution" ); - err }, ) } diff --git a/sui-execution/v0/sui-adapter/Cargo.toml b/sui-execution/v0/sui-adapter/Cargo.toml index 745245766b109..2fff322014716 100644 --- a/sui-execution/v0/sui-adapter/Cargo.toml +++ b/sui-execution/v0/sui-adapter/Cargo.toml @@ -44,6 +44,5 @@ gas-profiler = [ "sui-types/gas-profiler", "move-vm-runtime/gas-profiler", "move-vm-profiler/gas-profiler", - "move-vm-types/gas-profiler", "move-vm-config/gas-profiler", ] diff --git a/sui-execution/v0/sui-adapter/src/programmable_transactions/execution.rs b/sui-execution/v0/sui-adapter/src/programmable_transactions/execution.rs index 7038c6a46ac6d..18b20ada7b3dc 100644 --- a/sui-execution/v0/sui-adapter/src/programmable_transactions/execution.rs +++ b/sui-execution/v0/sui-adapter/src/programmable_transactions/execution.rs @@ -743,14 +743,9 @@ mod checked { }; let compatibility = Compatibility { - check_datatype_and_pub_function_linking: true, check_datatype_layout: true, - check_friend_linking: false, check_private_entry_linking: false, disallowed_new_abilities, - disallow_change_datatype_type_params: protocol_config - .disallow_change_struct_type_params_on_upgrade(), - disallow_new_variants: true, }; compatibility.check(cur_module, new_module) diff --git a/sui-execution/v0/sui-adapter/src/temporary_store.rs b/sui-execution/v0/sui-adapter/src/temporary_store.rs index 5b4b9309a5a6c..d9357f632176e 100644 --- a/sui-execution/v0/sui-adapter/src/temporary_store.rs +++ b/sui-execution/v0/sui-adapter/src/temporary_store.rs @@ -1023,9 +1023,9 @@ impl<'backing> BackingPackageStore for TemporaryStore<'backing> { if let Some((obj, _)) = self.written.get(package_id) { Ok(Some(PackageObject::new(obj.clone()))) } else { - self.store.get_package_object(package_id).map(|obj| { + self.store.get_package_object(package_id).inspect(|obj| { // Track object but leave unchanged - if let Some(v) = &obj { + if let Some(v) = obj { if !self .runtime_packages_loaded_from_db .read() @@ -1038,7 +1038,6 @@ impl<'backing> BackingPackageStore for TemporaryStore<'backing> { .insert(*package_id, v.clone()); } } - obj }) } } diff --git a/sui-execution/v0/sui-move-natives/src/dynamic_field.rs b/sui-execution/v0/sui-move-natives/src/dynamic_field.rs index 009c5779d6121..264d71192cab3 100644 --- a/sui-execution/v0/sui-move-natives/src/dynamic_field.rs +++ b/sui-execution/v0/sui-move-natives/src/dynamic_field.rs @@ -303,9 +303,8 @@ pub fn borrow_child_object( if !global_value.exists()? { return Ok(NativeResult::err(context.gas_used(), E_KEY_DOES_NOT_EXIST)); } - let child_ref = global_value.borrow_global().map_err(|err| { + let child_ref = global_value.borrow_global().inspect_err(|err| { assert!(err.major_status() != StatusCode::MISSING_DATA); - err })?; native_charge_gas_early_exit!( @@ -372,9 +371,8 @@ pub fn remove_child_object( if !global_value.exists()? { return Ok(NativeResult::err(context.gas_used(), E_KEY_DOES_NOT_EXIST)); } - let child = global_value.move_from().map_err(|err| { + let child = global_value.move_from().inspect_err(|err| { assert!(err.major_status() != StatusCode::MISSING_DATA); - err })?; native_charge_gas_early_exit!( diff --git a/sui-execution/v0/sui-move-natives/src/object_runtime/mod.rs b/sui-execution/v0/sui-move-natives/src/object_runtime/mod.rs index 1b0a39e497090..1070e3b10dee1 100644 --- a/sui-execution/v0/sui-move-natives/src/object_runtime/mod.rs +++ b/sui-execution/v0/sui-move-natives/src/object_runtime/mod.rs @@ -5,7 +5,7 @@ use better_any::{Tid, TidAble}; use linked_hash_map::LinkedHashMap; use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_core_types::{ - account_address::AccountAddress, annotated_value as A, effects::Op, + account_address::AccountAddress, annotated_value as A, annotated_visitor as AV, effects::Op, language_storage::StructTag, runtime_value as R, vm_status::StatusCode, }; use move_vm_types::{ @@ -608,7 +608,6 @@ fn update_owner_map( Ok(()) } -// TODO use a custom DeserializerSeed and improve this performance /// WARNING! This function assumes that the bcs bytes have already been validated, /// and it will give an invariant violation otherwise. /// In short, we are relying on the invariant that the bytes are valid for objects @@ -619,40 +618,42 @@ pub fn get_all_uids( bcs_bytes: &[u8], ) -> Result, /* invariant violation */ String> { let mut ids = BTreeSet::new(); - // TODO (annotated-visitor): Replace with a custom visitor - let v = A::MoveValue::simple_deserialize(bcs_bytes, fully_annotated_layout) - .map_err(|e| format!("Failed to deserialize. {e:?}"))?; - get_all_uids_in_value(&mut ids, &v)?; - Ok(ids) -} - -fn get_all_uids_in_value( - acc: &mut BTreeSet, - v: &A::MoveValue, -) -> Result<(), /* invariant violation */ String> { - let mut stack = vec![v]; - while let Some(cur) = stack.pop() { - let s = match cur { - A::MoveValue::Struct(s) => s, - A::MoveValue::Vector(vec) => { - stack.extend(vec); - continue; + struct UIDTraversal<'i>(&'i mut BTreeSet); + struct UIDCollector<'i>(&'i mut BTreeSet); + + impl<'i, 'b, 'l> AV::Traversal<'b, 'l> for UIDTraversal<'i> { + type Error = AV::Error; + + fn traverse_struct( + &mut self, + driver: &mut AV::StructDriver<'_, 'b, 'l>, + ) -> Result<(), Self::Error> { + if driver.struct_layout().type_ == UID::type_() { + while driver.next_field(&mut UIDCollector(self.0))?.is_some() {} + } else { + while driver.next_field(self)?.is_some() {} } - _ => continue, - }; - let A::MoveStruct { type_, fields } = s; - if type_ == &UID::type_() { - let inner = match &fields[0].1 { - A::MoveValue::Struct(A::MoveStruct { fields, .. }) => fields, - v => return Err(format!("Unexpected UID layout. {v:?}")), - }; - match &inner[0].1 { - A::MoveValue::Address(id) => acc.insert((*id).into()), - v => return Err(format!("Unexpected ID layout. {v:?}")), - }; - } else { - stack.extend(fields.iter().map(|(_, v)| v)); + Ok(()) } } - Ok(()) + + impl<'i, 'b, 'l> AV::Traversal<'b, 'l> for UIDCollector<'i> { + type Error = AV::Error; + fn traverse_address( + &mut self, + _driver: &AV::ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result<(), Self::Error> { + self.0.insert(value.into()); + Ok(()) + } + } + + A::MoveValue::visit_deserialize( + bcs_bytes, + fully_annotated_layout, + &mut UIDTraversal(&mut ids), + ) + .map_err(|e| format!("Failed to deserialize. {e:?}"))?; + Ok(ids) } diff --git a/sui-execution/v0/sui-move-natives/src/test_scenario.rs b/sui-execution/v0/sui-move-natives/src/test_scenario.rs index 79b95b48e31cc..b058627837efe 100644 --- a/sui-execution/v0/sui-move-natives/src/test_scenario.rs +++ b/sui-execution/v0/sui-move-natives/src/test_scenario.rs @@ -9,9 +9,8 @@ use linked_hash_map::LinkedHashMap; use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_core_types::{ account_address::AccountAddress, - annotated_value::{MoveStruct, MoveValue}, - identifier::Identifier, - language_storage::StructTag, + annotated_value::{MoveFieldLayout, MoveStructLayout, MoveTypeLayout, MoveValue}, + annotated_visitor as AV, vm_status::StatusCode, }; use move_vm_runtime::native_functions::NativeContext; @@ -635,103 +634,105 @@ fn pack_option(opt: Option) -> Value { )])) } -fn find_all_wrapped_objects<'a>( +fn find_all_wrapped_objects<'a, 'i>( context: &NativeContext, - ids: &mut BTreeSet, + ids: &'i mut BTreeSet, new_object_values: impl IntoIterator)>, ) { - for (_id, ty, value) in new_object_values { - let layout = match context.type_to_type_layout(ty) { - Ok(Some(layout)) => layout, - _ => { - debug_assert!(false); - continue; - } - }; - let annotated_layout = match context.type_to_fully_annotated_layout(ty) { - Ok(Some(layout)) => layout, - _ => { - debug_assert!(false); - continue; - } - }; - let blob = value.borrow().simple_serialize(&layout).unwrap(); - // TODO (annotated-visitor): Replace with a custom visitor. - let move_value = MoveValue::simple_deserialize(&blob, &annotated_layout).unwrap(); - let uid = UID::type_(); - visit_structs(&move_value, |depth, tag, fields| { - if tag != &uid { - return if depth == 0 { - debug_assert!(!fields.is_empty()); - // all object values so the first field is a UID that should be skipped - &fields[1..] - } else { - fields - }; - } - debug_assert!(fields.len() == 1); - let id = &fields[0].1; - let addr_field = match &id { - MoveValue::Struct(MoveStruct { fields, .. }) => { - debug_assert!(fields.len() == 1); - &fields[0].1 - } - v => unreachable!("Not reachable via Move type system: {:?}", v), - }; - let addr = match addr_field { - MoveValue::Address(a) => *a, - v => unreachable!("Not reachable via Move type system: {:?}", v), - }; - ids.insert(addr.into()); - fields - }) + #[derive(Copy, Clone)] + enum LookingFor { + Wrapped, + Uid, + Address, } -} -fn visit_structs(move_value: &MoveValue, mut visit_with_types: FVisitTypes) -where - for<'a> FVisitTypes: FnMut( - /* value depth */ usize, - &StructTag, - &'a Vec<(Identifier, MoveValue)>, - ) -> &'a [(Identifier, MoveValue)], -{ - visit_structs_impl(move_value, &mut visit_with_types, 0) -} + struct Traversal<'i, 'u> { + state: LookingFor, + ids: &'i mut BTreeSet, + uid: &'u MoveStructLayout, + } + + impl<'i, 'u, 'b, 'l> AV::Traversal<'b, 'l> for Traversal<'i, 'u> { + type Error = AV::Error; + + fn traverse_struct( + &mut self, + driver: &mut AV::StructDriver<'_, 'b, 'l>, + ) -> Result<(), Self::Error> { + match self.state { + // We're at the top-level of the traversal, looking for an object to recurse into. + // We can unconditionally switch to looking for UID fields at the level below, + // because we know that all the top-level values are objects. + LookingFor::Wrapped => { + while driver + .next_field(&mut Traversal { + state: LookingFor::Uid, + ids: self.ids, + uid: self.uid, + })? + .is_some() + {} + } -fn visit_structs_impl( - move_value: &MoveValue, - visit_with_types: &mut FVisitTypes, - depth: usize, -) where - for<'a> FVisitTypes: FnMut( - /* value depth */ usize, - &StructTag, - &'a Vec<(Identifier, MoveValue)>, - ) -> &'a [(Identifier, MoveValue)], -{ - let next_depth = depth + 1; - match move_value { - MoveValue::U8(_) - | MoveValue::U16(_) - | MoveValue::U32(_) - | MoveValue::U64(_) - | MoveValue::U128(_) - | MoveValue::U256(_) - | MoveValue::Bool(_) - | MoveValue::Address(_) - | MoveValue::Signer(_) => (), - MoveValue::Vector(vs) => { - for v in vs { - visit_structs_impl(v, visit_with_types, next_depth) + // We are looking for UID fields. If we find one (which we confirm by checking its + // layout), switch to looking for addresses in its sub-structure. + LookingFor::Uid => { + while let Some(MoveFieldLayout { name: _, layout }) = driver.peek_field() { + if matches!(layout, MoveTypeLayout::Struct(s) if s.as_ref() == self.uid) { + driver.next_field(&mut Traversal { + state: LookingFor::Address, + ids: self.ids, + uid: self.uid, + })?; + } else { + driver.next_field(self)?; + } + } + } + + // When looking for addresses, recurse through structs, as the address is nested + // within the UID. + LookingFor::Address => while driver.next_field(self)?.is_some() {}, } + + Ok(()) } - MoveValue::Struct(MoveStruct { type_, fields }) => { - let fields = visit_with_types(depth, type_, fields); - for (_, v) in fields { - visit_structs_impl(v, visit_with_types, next_depth) + + fn traverse_address( + &mut self, + _: &AV::ValueDriver<'_, 'b, 'l>, + address: AccountAddress, + ) -> Result<(), Self::Error> { + // If we're looking for addresses, and we found one, then save it. + if matches!(self.state, LookingFor::Address) { + self.ids.insert(address.into()); } + Ok(()) } - MoveValue::Variant(_) => unreachable!("Enums not supported in v0"), + } + + let uid = UID::layout(); + for (_id, ty, value) in new_object_values { + let Ok(Some(layout)) = context.type_to_type_layout(ty) else { + debug_assert!(false); + continue; + }; + + let Ok(Some(annotated_layout)) = context.type_to_fully_annotated_layout(ty) else { + debug_assert!(false); + continue; + }; + + let blob = value.borrow().simple_serialize(&layout).unwrap(); + MoveValue::visit_deserialize( + &blob, + &annotated_layout, + &mut Traversal { + state: LookingFor::Wrapped, + ids, + uid: &uid, + }, + ) + .unwrap(); } } diff --git a/sui-execution/v0/sui-verifier/src/verifier.rs b/sui-execution/v0/sui-verifier/src/verifier.rs index af5491af47708..db4f3bc98d68a 100644 --- a/sui-execution/v0/sui-verifier/src/verifier.rs +++ b/sui-execution/v0/sui-verifier/src/verifier.rs @@ -56,7 +56,7 @@ pub fn sui_verify_module_unmetered( module: &CompiledModule, fn_info_map: &FnInfoMap, ) -> Result<(), ExecutionError> { - sui_verify_module_metered(config, module, fn_info_map, &mut DummyMeter).map_err(|err| { + sui_verify_module_metered(config, module, fn_info_map, &mut DummyMeter).inspect_err(|err| { // We must never see timeout error in execution debug_assert!( !matches!( @@ -65,6 +65,5 @@ pub fn sui_verify_module_unmetered( ), "Unexpected timeout error in execution" ); - err }) } diff --git a/sui-execution/v1/sui-adapter/Cargo.toml b/sui-execution/v1/sui-adapter/Cargo.toml index d986af97a9856..44a1409c61e5e 100644 --- a/sui-execution/v1/sui-adapter/Cargo.toml +++ b/sui-execution/v1/sui-adapter/Cargo.toml @@ -43,6 +43,5 @@ gas-profiler = [ "sui-types/gas-profiler", "move-vm-runtime/gas-profiler", "move-vm-profiler/gas-profiler", - "move-vm-types/gas-profiler", "move-vm-config/gas-profiler", ] diff --git a/sui-execution/v1/sui-adapter/src/programmable_transactions/execution.rs b/sui-execution/v1/sui-adapter/src/programmable_transactions/execution.rs index 9e95a3edf99eb..4f8243be74017 100644 --- a/sui-execution/v1/sui-adapter/src/programmable_transactions/execution.rs +++ b/sui-execution/v1/sui-adapter/src/programmable_transactions/execution.rs @@ -683,15 +683,7 @@ mod checked { UpgradePolicy::Additive => InclusionCheck::Subset.check(cur_module, new_module), UpgradePolicy::DepOnly => InclusionCheck::Equal.check(cur_module, new_module), UpgradePolicy::Compatible => { - let compatibility = Compatibility { - check_datatype_and_pub_function_linking: true, - check_datatype_layout: true, - check_friend_linking: false, - check_private_entry_linking: false, - disallowed_new_abilities: AbilitySet::ALL, - disallow_change_datatype_type_params: true, - disallow_new_variants: true, - }; + let compatibility = Compatibility::upgrade_check(); compatibility.check(cur_module, new_module) } diff --git a/sui-execution/v1/sui-adapter/src/temporary_store.rs b/sui-execution/v1/sui-adapter/src/temporary_store.rs index fd7a146e4cd1a..914ec6cecc1e8 100644 --- a/sui-execution/v1/sui-adapter/src/temporary_store.rs +++ b/sui-execution/v1/sui-adapter/src/temporary_store.rs @@ -1145,9 +1145,9 @@ impl<'backing> BackingPackageStore for TemporaryStore<'backing> { if let Some(obj) = self.read_object(package_id) { Ok(Some(PackageObject::new(obj.clone()))) } else { - self.store.get_package_object(package_id).map(|obj| { + self.store.get_package_object(package_id).inspect(|obj| { // Track object but leave unchanged - if let Some(v) = &obj { + if let Some(v) = obj { if !self .runtime_packages_loaded_from_db .read() @@ -1160,7 +1160,6 @@ impl<'backing> BackingPackageStore for TemporaryStore<'backing> { .insert(*package_id, v.clone()); } } - obj }) } } diff --git a/sui-execution/v1/sui-move-natives/src/dynamic_field.rs b/sui-execution/v1/sui-move-natives/src/dynamic_field.rs index e6f1139d1519c..ba37beef3f014 100644 --- a/sui-execution/v1/sui-move-natives/src/dynamic_field.rs +++ b/sui-execution/v1/sui-move-natives/src/dynamic_field.rs @@ -302,9 +302,8 @@ pub fn borrow_child_object( if !global_value.exists()? { return Ok(NativeResult::err(context.gas_used(), E_KEY_DOES_NOT_EXIST)); } - let child_ref = global_value.borrow_global().map_err(|err| { + let child_ref = global_value.borrow_global().inspect_err(|err| { assert!(err.major_status() != StatusCode::MISSING_DATA); - err })?; native_charge_gas_early_exit!( @@ -371,9 +370,8 @@ pub fn remove_child_object( if !global_value.exists()? { return Ok(NativeResult::err(context.gas_used(), E_KEY_DOES_NOT_EXIST)); } - let child = global_value.move_from().map_err(|err| { + let child = global_value.move_from().inspect_err(|err| { assert!(err.major_status() != StatusCode::MISSING_DATA); - err })?; native_charge_gas_early_exit!( diff --git a/sui-execution/v1/sui-move-natives/src/object_runtime/mod.rs b/sui-execution/v1/sui-move-natives/src/object_runtime/mod.rs index 138bc0b93b69c..ba330d06ababd 100644 --- a/sui-execution/v1/sui-move-natives/src/object_runtime/mod.rs +++ b/sui-execution/v1/sui-move-natives/src/object_runtime/mod.rs @@ -6,7 +6,8 @@ use linked_hash_map::LinkedHashMap; use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_core_types::{ account_address::AccountAddress, - annotated_value::{MoveStruct, MoveTypeLayout, MoveValue}, + annotated_value::{MoveTypeLayout, MoveValue}, + annotated_visitor as AV, effects::Op, language_storage::StructTag, runtime_value as R, @@ -660,7 +661,6 @@ fn check_circular_ownership( Ok(()) } -// TODO use a custom DeserializerSeed and improve this performance /// WARNING! This function assumes that the bcs bytes have already been validated, /// and it will give an invariant violation otherwise. /// In short, we are relying on the invariant that the bytes are valid for objects @@ -671,41 +671,42 @@ pub fn get_all_uids( bcs_bytes: &[u8], ) -> Result, /* invariant violation */ String> { let mut ids = BTreeSet::new(); - // TODO (annotated-visitor): Replace with a custom visitor - let v = MoveValue::simple_deserialize(bcs_bytes, fully_annotated_layout) - .map_err(|e| format!("Failed to deserialize. {e:?}"))?; - get_all_uids_in_value(&mut ids, &v)?; - Ok(ids) -} - -fn get_all_uids_in_value( - acc: &mut BTreeSet, - v: &MoveValue, -) -> Result<(), /* invariant violation */ String> { - let mut stack = vec![v]; - while let Some(cur) = stack.pop() { - let s = match cur { - MoveValue::Struct(s) => s, - MoveValue::Vector(vec) => { - stack.extend(vec); - continue; + struct UIDTraversal<'i>(&'i mut BTreeSet); + struct UIDCollector<'i>(&'i mut BTreeSet); + + impl<'i, 'b, 'l> AV::Traversal<'b, 'l> for UIDTraversal<'i> { + type Error = AV::Error; + + fn traverse_struct( + &mut self, + driver: &mut AV::StructDriver<'_, 'b, 'l>, + ) -> Result<(), Self::Error> { + if driver.struct_layout().type_ == UID::type_() { + while driver.next_field(&mut UIDCollector(self.0))?.is_some() {} + } else { + while driver.next_field(self)?.is_some() {} } - _ => continue, - }; + Ok(()) + } + } - let MoveStruct { type_, fields } = s; - if type_ == &UID::type_() { - let inner = match &fields[0].1 { - MoveValue::Struct(MoveStruct { fields, .. }) => fields, - v => return Err(format!("Unexpected UID layout. {v:?}")), - }; - match &inner[0].1 { - MoveValue::Address(id) => acc.insert((*id).into()), - v => return Err(format!("Unexpected ID layout. {v:?}")), - }; - } else { - stack.extend(fields.iter().map(|(_, v)| v)); + impl<'i, 'b, 'l> AV::Traversal<'b, 'l> for UIDCollector<'i> { + type Error = AV::Error; + fn traverse_address( + &mut self, + _driver: &AV::ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result<(), Self::Error> { + self.0.insert(value.into()); + Ok(()) } } - Ok(()) + + MoveValue::visit_deserialize( + bcs_bytes, + fully_annotated_layout, + &mut UIDTraversal(&mut ids), + ) + .map_err(|e| format!("Failed to deserialize. {e:?}"))?; + Ok(ids) } diff --git a/sui-execution/v1/sui-move-natives/src/test_scenario.rs b/sui-execution/v1/sui-move-natives/src/test_scenario.rs index 4255b1ff8b4f6..8a0a76f698f36 100644 --- a/sui-execution/v1/sui-move-natives/src/test_scenario.rs +++ b/sui-execution/v1/sui-move-natives/src/test_scenario.rs @@ -9,9 +9,8 @@ use linked_hash_map::LinkedHashMap; use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_core_types::{ account_address::AccountAddress, - annotated_value::{MoveStruct, MoveValue}, - identifier::Identifier, - language_storage::StructTag, + annotated_value::{MoveFieldLayout, MoveStructLayout, MoveTypeLayout, MoveValue}, + annotated_visitor as AV, vm_status::StatusCode, }; use move_vm_runtime::native_functions::NativeContext; @@ -630,103 +629,105 @@ fn pack_option(opt: Option) -> Value { )])) } -fn find_all_wrapped_objects<'a>( +fn find_all_wrapped_objects<'a, 'i>( context: &NativeContext, - ids: &mut BTreeSet, + ids: &'i mut BTreeSet, new_object_values: impl IntoIterator)>, ) { - for (_id, ty, value) in new_object_values { - let layout = match context.type_to_type_layout(ty) { - Ok(Some(layout)) => layout, - _ => { - debug_assert!(false); - continue; - } - }; - let annotated_layout = match context.type_to_fully_annotated_layout(ty) { - Ok(Some(layout)) => layout, - _ => { - debug_assert!(false); - continue; - } - }; - let blob = value.borrow().simple_serialize(&layout).unwrap(); - // TODO (annotated-visitor): Replace with a custom visitor. - let move_value = MoveValue::simple_deserialize(&blob, &annotated_layout).unwrap(); - let uid = UID::type_(); - visit_structs(&move_value, |depth, tag, fields| { - if tag != &uid { - return if depth == 0 { - debug_assert!(!fields.is_empty()); - // all object values so the first field is a UID that should be skipped - &fields[1..] - } else { - fields - }; - } - debug_assert!(fields.len() == 1); - let id = &fields[0].1; - let addr_field = match &id { - MoveValue::Struct(MoveStruct { fields, .. }) => { - debug_assert!(fields.len() == 1); - &fields[0].1 - } - v => unreachable!("Not reachable via Move type system: {:?}", v), - }; - let addr = match addr_field { - MoveValue::Address(a) => *a, - v => unreachable!("Not reachable via Move type system: {:?}", v), - }; - ids.insert(addr.into()); - fields - }) + #[derive(Copy, Clone)] + enum LookingFor { + Wrapped, + Uid, + Address, } -} -fn visit_structs(move_value: &MoveValue, mut visit_with_types: FVisitTypes) -where - for<'a> FVisitTypes: FnMut( - /* value depth */ usize, - &StructTag, - &'a Vec<(Identifier, MoveValue)>, - ) -> &'a [(Identifier, MoveValue)], -{ - visit_structs_impl(move_value, &mut visit_with_types, 0) -} + struct Traversal<'i, 'u> { + state: LookingFor, + ids: &'i mut BTreeSet, + uid: &'u MoveStructLayout, + } + + impl<'i, 'u, 'b, 'l> AV::Traversal<'b, 'l> for Traversal<'i, 'u> { + type Error = AV::Error; + + fn traverse_struct( + &mut self, + driver: &mut AV::StructDriver<'_, 'b, 'l>, + ) -> Result<(), Self::Error> { + match self.state { + // We're at the top-level of the traversal, looking for an object to recurse into. + // We can unconditionally switch to looking for UID fields at the level below, + // because we know that all the top-level values are objects. + LookingFor::Wrapped => { + while driver + .next_field(&mut Traversal { + state: LookingFor::Uid, + ids: self.ids, + uid: self.uid, + })? + .is_some() + {} + } -fn visit_structs_impl( - move_value: &MoveValue, - visit_with_types: &mut FVisitTypes, - depth: usize, -) where - for<'a> FVisitTypes: FnMut( - /* value depth */ usize, - &StructTag, - &'a Vec<(Identifier, MoveValue)>, - ) -> &'a [(Identifier, MoveValue)], -{ - let next_depth = depth + 1; - match move_value { - MoveValue::U8(_) - | MoveValue::U16(_) - | MoveValue::U32(_) - | MoveValue::U64(_) - | MoveValue::U128(_) - | MoveValue::U256(_) - | MoveValue::Bool(_) - | MoveValue::Address(_) - | MoveValue::Signer(_) => (), - MoveValue::Vector(vs) => { - for v in vs { - visit_structs_impl(v, visit_with_types, next_depth) + // We are looking for UID fields. If we find one (which we confirm by checking its + // layout), switch to looking for addresses in its sub-structure. + LookingFor::Uid => { + while let Some(MoveFieldLayout { name: _, layout }) = driver.peek_field() { + if matches!(layout, MoveTypeLayout::Struct(s) if s.as_ref() == self.uid) { + driver.next_field(&mut Traversal { + state: LookingFor::Address, + ids: self.ids, + uid: self.uid, + })?; + } else { + driver.next_field(self)?; + } + } + } + + // When looking for addresses, recurse through structs, as the address is nested + // within the UID. + LookingFor::Address => while driver.next_field(self)?.is_some() {}, } + + Ok(()) } - MoveValue::Struct(MoveStruct { type_, fields }) => { - let fields = visit_with_types(depth, type_, fields); - for (_, v) in fields { - visit_structs_impl(v, visit_with_types, next_depth) + + fn traverse_address( + &mut self, + _: &AV::ValueDriver<'_, 'b, 'l>, + address: AccountAddress, + ) -> Result<(), Self::Error> { + // If we're looking for addresses, and we found one, then save it. + if matches!(self.state, LookingFor::Address) { + self.ids.insert(address.into()); } + Ok(()) } - MoveValue::Variant(_) => unreachable!("Enums not supported in v1"), + } + + let uid = UID::layout(); + for (_id, ty, value) in new_object_values { + let Ok(Some(layout)) = context.type_to_type_layout(ty) else { + debug_assert!(false); + continue; + }; + + let Ok(Some(annotated_layout)) = context.type_to_fully_annotated_layout(ty) else { + debug_assert!(false); + continue; + }; + + let blob = value.borrow().simple_serialize(&layout).unwrap(); + MoveValue::visit_deserialize( + &blob, + &annotated_layout, + &mut Traversal { + state: LookingFor::Wrapped, + ids, + uid: &uid, + }, + ) + .unwrap(); } } diff --git a/sui-execution/v1/sui-verifier/src/verifier.rs b/sui-execution/v1/sui-verifier/src/verifier.rs index 4950541775631..16a7b3d103af1 100644 --- a/sui-execution/v1/sui-verifier/src/verifier.rs +++ b/sui-execution/v1/sui-verifier/src/verifier.rs @@ -51,7 +51,7 @@ pub fn sui_verify_module_unmetered( module: &CompiledModule, fn_info_map: &FnInfoMap, ) -> Result<(), ExecutionError> { - sui_verify_module_metered(module, fn_info_map, &mut DummyMeter).map_err(|err| { + sui_verify_module_metered(module, fn_info_map, &mut DummyMeter).inspect_err(|err| { // We must never see timeout error in execution debug_assert!( !matches!( @@ -60,6 +60,5 @@ pub fn sui_verify_module_unmetered( ), "Unexpected timeout error in execution" ); - err }) } diff --git a/sui-execution/v2/sui-adapter/Cargo.toml b/sui-execution/v2/sui-adapter/Cargo.toml index cb8bf360a0b12..3e3c2eb9f79b0 100644 --- a/sui-execution/v2/sui-adapter/Cargo.toml +++ b/sui-execution/v2/sui-adapter/Cargo.toml @@ -43,6 +43,5 @@ gas-profiler = [ "sui-types/gas-profiler", "move-vm-runtime/gas-profiler", "move-vm-profiler/gas-profiler", - "move-vm-types/gas-profiler", "move-vm-config/gas-profiler", ] diff --git a/sui-execution/v2/sui-adapter/src/programmable_transactions/execution.rs b/sui-execution/v2/sui-adapter/src/programmable_transactions/execution.rs index e5e99d8e83664..a3ad3be713ec7 100644 --- a/sui-execution/v2/sui-adapter/src/programmable_transactions/execution.rs +++ b/sui-execution/v2/sui-adapter/src/programmable_transactions/execution.rs @@ -689,15 +689,7 @@ mod checked { UpgradePolicy::Additive => InclusionCheck::Subset.check(cur_module, new_module), UpgradePolicy::DepOnly => InclusionCheck::Equal.check(cur_module, new_module), UpgradePolicy::Compatible => { - let compatibility = Compatibility { - check_datatype_and_pub_function_linking: true, - check_datatype_layout: true, - check_friend_linking: false, - check_private_entry_linking: false, - disallowed_new_abilities: AbilitySet::ALL, - disallow_change_datatype_type_params: true, - disallow_new_variants: true, - }; + let compatibility = Compatibility::upgrade_check(); compatibility.check(cur_module, new_module) } diff --git a/sui-execution/v2/sui-adapter/src/temporary_store.rs b/sui-execution/v2/sui-adapter/src/temporary_store.rs index 7a23ab3820298..c5fffd0ed5a71 100644 --- a/sui-execution/v2/sui-adapter/src/temporary_store.rs +++ b/sui-execution/v2/sui-adapter/src/temporary_store.rs @@ -1197,9 +1197,9 @@ impl<'backing> BackingPackageStore for TemporaryStore<'backing> { if let Some(obj) = self.execution_results.written_objects.get(package_id) { Ok(Some(PackageObject::new(obj.clone()))) } else { - self.store.get_package_object(package_id).map(|obj| { + self.store.get_package_object(package_id).inspect(|obj| { // Track object but leave unchanged - if let Some(v) = &obj { + if let Some(v) = obj { if !self .runtime_packages_loaded_from_db .read() @@ -1214,7 +1214,6 @@ impl<'backing> BackingPackageStore for TemporaryStore<'backing> { .insert(*package_id, v.clone()); } } - obj }) } } diff --git a/sui-execution/v2/sui-move-natives/src/dynamic_field.rs b/sui-execution/v2/sui-move-natives/src/dynamic_field.rs index e6f1139d1519c..ba37beef3f014 100644 --- a/sui-execution/v2/sui-move-natives/src/dynamic_field.rs +++ b/sui-execution/v2/sui-move-natives/src/dynamic_field.rs @@ -302,9 +302,8 @@ pub fn borrow_child_object( if !global_value.exists()? { return Ok(NativeResult::err(context.gas_used(), E_KEY_DOES_NOT_EXIST)); } - let child_ref = global_value.borrow_global().map_err(|err| { + let child_ref = global_value.borrow_global().inspect_err(|err| { assert!(err.major_status() != StatusCode::MISSING_DATA); - err })?; native_charge_gas_early_exit!( @@ -371,9 +370,8 @@ pub fn remove_child_object( if !global_value.exists()? { return Ok(NativeResult::err(context.gas_used(), E_KEY_DOES_NOT_EXIST)); } - let child = global_value.move_from().map_err(|err| { + let child = global_value.move_from().inspect_err(|err| { assert!(err.major_status() != StatusCode::MISSING_DATA); - err })?; native_charge_gas_early_exit!( diff --git a/sui-execution/v2/sui-move-natives/src/object_runtime/mod.rs b/sui-execution/v2/sui-move-natives/src/object_runtime/mod.rs index 249a30ccdfa18..d2f4bbf9582ec 100644 --- a/sui-execution/v2/sui-move-natives/src/object_runtime/mod.rs +++ b/sui-execution/v2/sui-move-natives/src/object_runtime/mod.rs @@ -11,7 +11,8 @@ use indexmap::set::IndexSet; use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_core_types::{ account_address::AccountAddress, - annotated_value::{MoveStruct, MoveTypeLayout, MoveValue}, + annotated_value::{MoveTypeLayout, MoveValue}, + annotated_visitor as AV, effects::Op, language_storage::StructTag, runtime_value as R, @@ -628,7 +629,6 @@ fn check_circular_ownership( Ok(()) } -// TODO use a custom DeserializerSeed and improve this performance /// WARNING! This function assumes that the bcs bytes have already been validated, /// and it will give an invariant violation otherwise. /// In short, we are relying on the invariant that the bytes are valid for objects @@ -639,41 +639,42 @@ pub fn get_all_uids( bcs_bytes: &[u8], ) -> Result, /* invariant violation */ String> { let mut ids = BTreeSet::new(); - // TODO (annotated-visitor): Replace with a custom visitor - let v = MoveValue::simple_deserialize(bcs_bytes, fully_annotated_layout) - .map_err(|e| format!("Failed to deserialize. {e:?}"))?; - get_all_uids_in_value(&mut ids, &v)?; - Ok(ids) -} - -fn get_all_uids_in_value( - acc: &mut BTreeSet, - v: &MoveValue, -) -> Result<(), /* invariant violation */ String> { - let mut stack = vec![v]; - while let Some(cur) = stack.pop() { - let s = match cur { - MoveValue::Struct(s) => s, - MoveValue::Vector(vec) => { - stack.extend(vec); - continue; + struct UIDTraversal<'i>(&'i mut BTreeSet); + struct UIDCollector<'i>(&'i mut BTreeSet); + + impl<'i, 'b, 'l> AV::Traversal<'b, 'l> for UIDTraversal<'i> { + type Error = AV::Error; + + fn traverse_struct( + &mut self, + driver: &mut AV::StructDriver<'_, 'b, 'l>, + ) -> Result<(), Self::Error> { + if driver.struct_layout().type_ == UID::type_() { + while driver.next_field(&mut UIDCollector(self.0))?.is_some() {} + } else { + while driver.next_field(self)?.is_some() {} } - _ => continue, - }; + Ok(()) + } + } - let MoveStruct { type_, fields } = s; - if type_ == &UID::type_() { - let inner = match &fields[0].1 { - MoveValue::Struct(MoveStruct { fields, .. }) => fields, - v => return Err(format!("Unexpected UID layout. {v:?}")), - }; - match &inner[0].1 { - MoveValue::Address(id) => acc.insert((*id).into()), - v => return Err(format!("Unexpected ID layout. {v:?}")), - }; - } else { - stack.extend(fields.iter().map(|(_, v)| v)); + impl<'i, 'b, 'l> AV::Traversal<'b, 'l> for UIDCollector<'i> { + type Error = AV::Error; + fn traverse_address( + &mut self, + _driver: &AV::ValueDriver<'_, 'b, 'l>, + value: AccountAddress, + ) -> Result<(), Self::Error> { + self.0.insert(value.into()); + Ok(()) } } - Ok(()) + + MoveValue::visit_deserialize( + bcs_bytes, + fully_annotated_layout, + &mut UIDTraversal(&mut ids), + ) + .map_err(|e| format!("Failed to deserialize. {e:?}"))?; + Ok(ids) } diff --git a/sui-execution/v2/sui-move-natives/src/test_scenario.rs b/sui-execution/v2/sui-move-natives/src/test_scenario.rs index 4c65feee5e487..8f7a6d7b2b758 100644 --- a/sui-execution/v2/sui-move-natives/src/test_scenario.rs +++ b/sui-execution/v2/sui-move-natives/src/test_scenario.rs @@ -9,9 +9,8 @@ use indexmap::{IndexMap, IndexSet}; use move_binary_format::errors::{PartialVMError, PartialVMResult}; use move_core_types::{ account_address::AccountAddress, - annotated_value::{MoveStruct, MoveValue, MoveVariant}, - identifier::Identifier, - language_storage::StructTag, + annotated_value::{MoveFieldLayout, MoveStructLayout, MoveTypeLayout, MoveValue}, + annotated_visitor as AV, vm_status::StatusCode, }; use move_vm_runtime::native_functions::NativeContext; @@ -632,108 +631,105 @@ fn pack_option(opt: Option) -> Value { )])) } -fn find_all_wrapped_objects<'a>( +fn find_all_wrapped_objects<'a, 'i>( context: &NativeContext, - ids: &mut BTreeSet, + ids: &'i mut BTreeSet, new_object_values: impl IntoIterator)>, ) { - for (_id, ty, value) in new_object_values { - let layout = match context.type_to_type_layout(ty) { - Ok(Some(layout)) => layout, - _ => { - debug_assert!(false); - continue; - } - }; - let annotated_layout = match context.type_to_fully_annotated_layout(ty) { - Ok(Some(layout)) => layout, - _ => { - debug_assert!(false); - continue; - } - }; - let blob = value.borrow().simple_serialize(&layout).unwrap(); - // TODO (annotated-visitor): Replace with a custom visitor. - let move_value = MoveValue::simple_deserialize(&blob, &annotated_layout).unwrap(); - let uid = UID::type_(); - visit_structs(&move_value, |depth, tag, fields| { - if tag != &uid { - return if depth == 0 { - debug_assert!(!fields.is_empty()); - // all object values so the first field is a UID that should be skipped - &fields[1..] - } else { - fields - }; - } - debug_assert!(fields.len() == 1); - let id = &fields[0].1; - let addr_field = match &id { - MoveValue::Struct(MoveStruct { fields, .. }) => { - debug_assert!(fields.len() == 1); - &fields[0].1 - } - v => unreachable!("Not reachable via Move type system: {:?}", v), - }; - let addr = match addr_field { - MoveValue::Address(a) => *a, - v => unreachable!("Not reachable via Move type system: {:?}", v), - }; - ids.insert(addr.into()); - fields - }) + #[derive(Copy, Clone)] + enum LookingFor { + Wrapped, + Uid, + Address, } -} -fn visit_structs(move_value: &MoveValue, mut visit_with_types: FVisitTypes) -where - for<'a> FVisitTypes: FnMut( - /* value depth */ usize, - &StructTag, - &'a Vec<(Identifier, MoveValue)>, - ) -> &'a [(Identifier, MoveValue)], -{ - visit_structs_impl(move_value, &mut visit_with_types, 0) -} + struct Traversal<'i, 'u> { + state: LookingFor, + ids: &'i mut BTreeSet, + uid: &'u MoveStructLayout, + } -fn visit_structs_impl( - move_value: &MoveValue, - visit_with_types: &mut FVisitTypes, - depth: usize, -) where - for<'a> FVisitTypes: FnMut( - /* value depth */ usize, - &StructTag, - &'a Vec<(Identifier, MoveValue)>, - ) -> &'a [(Identifier, MoveValue)], -{ - let next_depth = depth + 1; - match move_value { - MoveValue::U8(_) - | MoveValue::U16(_) - | MoveValue::U32(_) - | MoveValue::U64(_) - | MoveValue::U128(_) - | MoveValue::U256(_) - | MoveValue::Bool(_) - | MoveValue::Address(_) - | MoveValue::Signer(_) => (), - MoveValue::Vector(vs) => { - for v in vs { - visit_structs_impl(v, visit_with_types, next_depth) - } - } - MoveValue::Struct(MoveStruct { type_, fields }) => { - let fields = visit_with_types(depth, type_, fields); - for (_, v) in fields { - visit_structs_impl(v, visit_with_types, next_depth) + impl<'i, 'u, 'b, 'l> AV::Traversal<'b, 'l> for Traversal<'i, 'u> { + type Error = AV::Error; + + fn traverse_struct( + &mut self, + driver: &mut AV::StructDriver<'_, 'b, 'l>, + ) -> Result<(), Self::Error> { + match self.state { + // We're at the top-level of the traversal, looking for an object to recurse into. + // We can unconditionally switch to looking for UID fields at the level below, + // because we know that all the top-level values are objects. + LookingFor::Wrapped => { + while driver + .next_field(&mut Traversal { + state: LookingFor::Uid, + ids: self.ids, + uid: self.uid, + })? + .is_some() + {} + } + + // We are looking for UID fields. If we find one (which we confirm by checking its + // layout), switch to looking for addresses in its sub-structure. + LookingFor::Uid => { + while let Some(MoveFieldLayout { name: _, layout }) = driver.peek_field() { + if matches!(layout, MoveTypeLayout::Struct(s) if s.as_ref() == self.uid) { + driver.next_field(&mut Traversal { + state: LookingFor::Address, + ids: self.ids, + uid: self.uid, + })?; + } else { + driver.next_field(self)?; + } + } + } + + // When looking for addresses, recurse through structs, as the address is nested + // within the UID. + LookingFor::Address => while driver.next_field(self)?.is_some() {}, } + + Ok(()) } - MoveValue::Variant(MoveVariant { type_, fields, .. }) => { - let fields = visit_with_types(depth, type_, fields); - for (_, v) in fields { - visit_structs_impl(v, visit_with_types, next_depth) + + fn traverse_address( + &mut self, + _: &AV::ValueDriver<'_, 'b, 'l>, + address: AccountAddress, + ) -> Result<(), Self::Error> { + // If we're looking for addresses, and we found one, then save it. + if matches!(self.state, LookingFor::Address) { + self.ids.insert(address.into()); } + Ok(()) } } + + let uid = UID::layout(); + for (_id, ty, value) in new_object_values { + let Ok(Some(layout)) = context.type_to_type_layout(ty) else { + debug_assert!(false); + continue; + }; + + let Ok(Some(annotated_layout)) = context.type_to_fully_annotated_layout(ty) else { + debug_assert!(false); + continue; + }; + + let blob = value.borrow().simple_serialize(&layout).unwrap(); + MoveValue::visit_deserialize( + &blob, + &annotated_layout, + &mut Traversal { + state: LookingFor::Wrapped, + ids, + uid: &uid, + }, + ) + .unwrap(); + } } diff --git a/sui-execution/v2/sui-verifier/src/verifier.rs b/sui-execution/v2/sui-verifier/src/verifier.rs index 7f7be9e042d53..42a4505632682 100644 --- a/sui-execution/v2/sui-verifier/src/verifier.rs +++ b/sui-execution/v2/sui-verifier/src/verifier.rs @@ -56,7 +56,7 @@ pub fn sui_verify_module_unmetered( fn_info_map: &FnInfoMap, verifier_config: &VerifierConfig, ) -> Result<(), ExecutionError> { - sui_verify_module_metered(module, fn_info_map, &mut DummyMeter, verifier_config).map_err( + sui_verify_module_metered(module, fn_info_map, &mut DummyMeter, verifier_config).inspect_err( |err| { // We must never see timeout error in execution debug_assert!( @@ -66,7 +66,6 @@ pub fn sui_verify_module_unmetered( ), "Unexpected timeout error in execution" ); - err }, ) }